Skip to content

Instantly share code, notes, and snippets.

@garethky
Created May 3, 2023 17:38
Show Gist options
  • Save garethky/3d07906cc5b423caba3cb75809cc46b8 to your computer and use it in GitHub Desktop.
Save garethky/3d07906cc5b423caba3cb75809cc46b8 to your computer and use it in GitHub Desktop.
Tool to dump data fron Moonraker's websicket and display in a browser
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graph Load Cell Data</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.2/dist/echarts.min.js"></script>
</head>
<body>
<h2>Moonraker Server:
<form id="serverForm">
<fieldset id="serverFormFields">
<input id="serverInput" type="text" placeholder="raspberrypi.local"/>
<input type="submit" value="Connect"/>
</fieldset>
</form>
</h2>
<h4>MCU Awake:<span id="mcuAwakeDisplay">--</span></h4>
<h4>MCU Load:<span id="mcuLoadDisplay">--</span></h4>
<h4>Grams:<span id="gramsDisplay">--</span></h4>
<button id="load_cell_log">load_cell_log.csv</button>
<div id="main-chart" style="height: 500px; width: 100%; margin: auto;"></div>
<p>Tip: press the space bar to pause data capture.</p>
<script>
var chartDom = document.getElementById('main-chart');
var myChart = echarts.init(chartDom);
var pauseGraph = false;
var loadCellSamples = [];
//var sampleAverageData = [];
//var trendAverageData = [];
//var trendDeadbandMin = [];
//var trendDeadbandMax = [];
//var deadband = 0;
var dates = [];
var load_cell_state = {
is_capturing: false,
is_calibrated: false,
sps: null,
tare_counts: 0,
counts_per_gram: 0
}
let series = [
{
data: loadCellSamples,
name: 'Load Cell Raw Value',
type: 'line',
step: 'start',
showSymbol: false,
sampling: 'lttb',
zlevel: 1,
lineStyle: {
opacity: 0.75,
width: 1
},
}/*,
{
name: 'Deadband Max',
type: 'line',
data: trendDeadbandMax,
zlevel: 0,
symbol: 'none',
tooltip: {
formatter: function (param) {
param = param[0];
return [
'whatdoesthisdo?', param.name, param.data[1] + param.data[2],
].join('');
}
}
},
{
name: 'Deadband Min',
type: 'line',
data: trendDeadbandMin,
zlevel: 0,
symbol: 'none'
},
{
data: sampleAverageData,
name: 'Smoothed Sample Value',
type: 'line',
showSymbol: false,
sampling: 'lttb',
zlevel: 3,
lineStyle: {
width: 3
},
},*/
];
let xAxis = {
type: 'category',
data: dates,
}
var option = {
xAxis: xAxis,
series: series,
yAxis: {
type: 'value',
min: 'dataMin',
},
animation: false,
dataZoom: [
{
type: 'slider',
show: true,
xAxisIndex: [0],
start: 80,
end: 100
},
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line'
},
position: { top: 0, right: 0 },
},
};
option && myChart.setOption(option);
// Add event listener to pause data collection on spacebar
document.addEventListener('keydown', (event) => {
if (event.keyCode == 32) {
pauseGraph = !pauseGraph;
// Consume the event so it doesn't get handled twice
event.preventDefault();
}
}, false);
const mcuAwakeDisplay = document.getElementById("mcuAwakeDisplay");
const mcuLoadDisplay = document.getElementById("mcuLoadDisplay");
const gramsDisplay = document.getElementById("gramsDisplay");
const serverInput = document.getElementById('serverInput');
const serverFormFields = document.getElementById('serverFormFields');
const serverForm = document.getElementById('serverForm');
const loadCellLogButton = document.getElementById('load_cell_log');
async function fetchDeadband(server) {
let jsonDoc = fetch("http://" + server + "/printer/objects/query?load_cell_endstop");
deadband = (await (await jsonDoc).json()).result.status.load_cell_endstop.deadband;
}
async function oneshotToken(server) {
let token = fetch("http://" + server + "/access/oneshot_token");
return (await (await token).json()).result;
}
let id = Date.now();
function nextId() {
id += 1;
return id;
}
function subscribeObject(socket) {
const id = nextId();
let message = JSON.stringify({
"jsonrpc": "2.0",
"method": "printer.objects.subscribe",
"params": {
"objects": {
"load_cell ads1263 tool1_adc": null,
"mcu": null
}
},
"id": id
}
);
return [id, socket.send(message)];
};
function addLoadCellData(data) {
let sampleGroups = data.samples;
delete data["samples"];
// copy any state data keys
if (Object.keys(data).length !== 0) {
load_cell_state = {...load_cell_state, ...data};
console.log(load_cell_state);
}
if (pauseGraph || !sampleGroups) {
return;
}
let grams = null;
var tare = load_cell_state.tare_counts;
sampleGroups.forEach(sampleGroup => {
sampleGroup.forEach(sample => {
dates.push(new Date(sample[0]));
loadCellSamples.push(sample[1] - tare);
grams = (sample[1] - tare) / load_cell_state.counts_per_gram;
});
});
if (grams != null) {
gramsDisplay.textContent = "" + Math.round(grams) + "g";
}
while (dates.length > 8000) {
dates.shift();
loadCellSamples.shift();
}
myChart.setOption({
xAxis: xAxis,
series: series
});
}
function updateMcuLoad(data) {
const STATS_INTERVAL = 5.0;
const TASK_MAX = 0.0025;
// this is awake ticks/frequency, so its a % of available clock cycles
let mcuAwake = ((data.last_stats.mcu_awake / STATS_INTERVAL) * 100.0).toFixed(2);
let mcuLoad = data.last_stats.mcu_task_avg + (3 * data.last_stats.mcu_task_stddev);
mcuLoad = (100.0 * mcuLoad / TASK_MAX).toFixed(2);
mcuAwakeDisplay.textContent = "" + (mcuAwake) + "%";
mcuLoadDisplay.textContent = "" + (mcuLoad) + "%";
}
function handleMessage(messageEvent) {
payload = JSON.parse(messageEvent.data);
if (payload && payload.method === "notify_proc_stat_update" && payload.params) {
} else if (payload.method === "notify_status_update" || payload.result) {
var keys = null;
if (payload.result) {
keys = payload.result.status;
} else {
keys = payload.params[0];
}
if (keys['mcu']) {
updateMcuLoad(keys.mcu);
}
if (keys['load_cell']) {
addLoadCellData(keys.load_cell);
}
if (keys['load_cell_endstop']) {
}
}
}
async function openSocket(server) {
let token = await oneshotToken(server)
if (!token) {
throw("oneshotToken error");
}
const socket_url = "ws://" + server + "/websocket?token=" + token;
//const socket_url = "ws://" + server + "/klippysocket?token=" + token;
console.log(socket_url);
const socket = new WebSocket(socket_url);
let opened, open = new Promise((resolve) => (opened = resolve));
// Callback of even listener used to resolve promise.
socket.addEventListener("open", (open) => {
opened();
});
await open;
return socket;
}
async function connect(event) {
event.preventDefault();
serverFormFields.disabled = true;
const server = serverInput.value;
let socket = await openSocket(server);
socket.addEventListener('message', event => handleMessage(event));
fetchDeadband(server);
subscribeObject(socket);
}
serverForm.addEventListener('submit', connect);
// Function to download data to a file
function download(data, filename, mimeType) {
var file = new Blob([data], {type: mimeType});
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
function download_log() {
let logCsv = 'DateTime,SampleInRawCounts\n';
logCsv += dates.join(',');
logCsv += '\n';
logCsv += loadCellSamples.join(',');
logCsv += '\n';
download(logCsv, 'load_cell_log.csv', 'text/csv');
}
loadCellLogButton.addEventListener('click', download_log);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment