Initial commit
This commit is contained in:
parent
b0692f7548
commit
deba2b3d6a
14 changed files with 1634 additions and 1 deletions
2
.env
Normal file
2
.env
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Scrubbed by Glitch 2020-01-28T20:10:15+0000
|
||||
|
||||
|
|
@ -1 +1,2 @@
|
|||
# Adafruit_WebSerial_Plotter
|
||||
# Adafruit WebSerial Plotter
|
||||
Source files for the Adafruit WebSerial Plotter available at: https://adafruit-webserial-plotter.glitch.me/ and https://adafruit-imu-calibration.glitch.me/.
|
||||
|
|
|
|||
BIN
assets/thumbnail.png
Normal file
BIN
assets/thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
45
dark.css
Normal file
45
dark.css
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
.header {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #282828;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border-color: #666;
|
||||
background-color: #383838;
|
||||
}
|
||||
|
||||
input, select, button {
|
||||
background-color: #454545;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.serial-input input {
|
||||
background-color: #383838;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
.serial-input input:disabled,
|
||||
.serial-input button:disabled {
|
||||
border-color: #333;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#notSupported {
|
||||
background-color: red;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#log {
|
||||
border-color: #666;
|
||||
background-color: #383838;
|
||||
color: #ccc;
|
||||
}
|
||||
216
graph.js
Normal file
216
graph.js
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/* global Chart */
|
||||
|
||||
'use strict';
|
||||
|
||||
var Graph = function(canvas) {
|
||||
let adaChart;
|
||||
let plotType;
|
||||
|
||||
this.chart = canvas.getContext('2d');
|
||||
this.maxBufferSize = 100;
|
||||
|
||||
this.XTConfig = {
|
||||
type: 'line', // make it a line chart
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: []
|
||||
},
|
||||
options: {
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0,
|
||||
fill: false
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
hover: {
|
||||
enabled: false
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
bounds: 'data',
|
||||
distribution: 'series',
|
||||
gridLines: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
maxRotation: 0
|
||||
}
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
}
|
||||
};
|
||||
|
||||
this.XYConfig = {
|
||||
type: 'scatter', // make it a scatter chart
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: []
|
||||
},
|
||||
options: {
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0,
|
||||
fill: false
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
hover: {
|
||||
enabled: false
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'linear',
|
||||
bounds: 'data',
|
||||
distribution: 'series',
|
||||
ticks: {
|
||||
display: true,
|
||||
},
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
maxRotation: 0
|
||||
}
|
||||
}]
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Graph.prototype = {
|
||||
create: function (plotType) {
|
||||
if (this.plotType == undefined) {
|
||||
if (plotType != undefined) {
|
||||
this.setPlotType(plotType);
|
||||
} else {
|
||||
this.plotType = "xt";
|
||||
}
|
||||
} else if (plotType != undefined) {
|
||||
this.setPlotType(plotType);
|
||||
}
|
||||
|
||||
// Remove any existing chart
|
||||
if (this.adaChart != undefined) {
|
||||
this.adaChart.destroy();
|
||||
delete this.adaChart;
|
||||
}
|
||||
let config = this.getConfig();
|
||||
this.adaChart = new Chart(this.chart, config);
|
||||
this.resize();
|
||||
},
|
||||
getConfig: function() {
|
||||
if (this.plotType == 'xy') {
|
||||
return this.XYConfig;
|
||||
} else {
|
||||
return this.XTConfig;
|
||||
}
|
||||
},
|
||||
setPlotType: function(type) {
|
||||
if (type.toLowerCase() == "xy") {
|
||||
this.plotType = "xy";
|
||||
} else {
|
||||
this.plotType = "xt";
|
||||
}
|
||||
},
|
||||
updateLabelColor: function(color) {
|
||||
this.adaChart.options.scales.xAxes[0].ticks.fontColor = color;
|
||||
this.adaChart.options.scales.yAxes[0].ticks.fontColor = color;
|
||||
this.adaChart.update();
|
||||
},
|
||||
reset: function() {
|
||||
// Clear the data
|
||||
let dataSetLength = this.adaChart.data.datasets.length;
|
||||
for(let i = 0; i < dataSetLength; i++) {
|
||||
this.adaChart.data.datasets.pop();
|
||||
}
|
||||
this.adaChart.update();
|
||||
},
|
||||
addDataSet: function(label, color) {
|
||||
let dataConfig;
|
||||
if (this.plotType == 'xy') {
|
||||
dataConfig = {
|
||||
label: label,
|
||||
data: [],
|
||||
borderColor: color,
|
||||
borderWidth: 1,
|
||||
pointBackgroundColor: color,
|
||||
pointBorderColor: color,
|
||||
pointRadius: 5,
|
||||
pointHoverRadius: 5,
|
||||
fill: false,
|
||||
tension: 0,
|
||||
showLine: false
|
||||
}
|
||||
} else {
|
||||
dataConfig = {
|
||||
label: label,
|
||||
data: [],
|
||||
borderColor: color,
|
||||
borderWidth: 1,
|
||||
pointRadius: 0
|
||||
}
|
||||
}
|
||||
this.adaChart.data.datasets.push(dataConfig);
|
||||
},
|
||||
update: function() {
|
||||
this.adaChart.update();
|
||||
},
|
||||
resize: function() {
|
||||
if (this.plotType == 'xy') {
|
||||
this.chart.canvas.parentNode.style.width = '40vh';
|
||||
} else {
|
||||
this.chart.canvas.parentNode.style.width = '100%';
|
||||
}
|
||||
},
|
||||
addValue: function(dataSetIndex, value) {
|
||||
if (this.plotType == 'xy' && Array.isArray(value)) {
|
||||
this.adaChart.data.datasets[dataSetIndex].data.push({
|
||||
x: value[0],
|
||||
y: value[1]
|
||||
});
|
||||
} else if (this.plotType == 'xt') {
|
||||
let time = new Date();
|
||||
this.adaChart.data.datasets[dataSetIndex].data.push({
|
||||
t: time,
|
||||
y: value
|
||||
});
|
||||
}
|
||||
this.flushBuffer();
|
||||
},
|
||||
flushBuffer: function() {
|
||||
// Make sure to shift out old data
|
||||
this.adaChart.data.datasets.forEach(
|
||||
dataset => {
|
||||
if (dataset.data.length > this.maxBufferSize) {
|
||||
dataset.data.shift()
|
||||
}
|
||||
}
|
||||
)
|
||||
this.update();
|
||||
},
|
||||
dataset: function(dataSetIndex) {
|
||||
return this.adaChart.data.datasets[dataSetIndex];
|
||||
},
|
||||
setBufferSize: function(size) {
|
||||
this.maxBufferSize = size;
|
||||
}
|
||||
}
|
||||
3
imu-calibration/.env
Normal file
3
imu-calibration/.env
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Scrubbed by Glitch 2020-01-28T20:10:15+0000
|
||||
# Scrubbed by Glitch 2020-02-07T01:37:47+0000
|
||||
|
||||
78
imu-calibration/index.html
Normal file
78
imu-calibration/index.html
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Adafruit IMU Calibration</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script>
|
||||
// Redirect to HTTPS if HTTP is requested.
|
||||
if (window.location.protocol === 'http:') {
|
||||
window.location.href = 'https:' + window.location.href.substring(5);
|
||||
}
|
||||
</script>
|
||||
<!-- import Chart.js library via CDN, per Chart.js documentation -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js" defer></script>
|
||||
<script src="../graph.js" defer></script>
|
||||
|
||||
<!-- import the webpage's stylesheet -->
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
<link rel="stylesheet" href="../light.css" id="light" class="alternate" disabled>
|
||||
<link rel="stylesheet" href="../dark.css" id="dark" class="alternate" disabled>
|
||||
|
||||
<!-- import the webpage's javascript file -->
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<h1>Adafruit IMU Calibration</h1>
|
||||
</header>
|
||||
<main class="main">
|
||||
<div id="notSupported">
|
||||
Sorry, <b>Web Serial</b> is not supported on this device, make sure you're
|
||||
running Chrome 78 or later and have enabled the
|
||||
<code>#enable-experimental-web-platform-features</code> flag in
|
||||
<code>chrome://flags</code>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="butConnect" type="button">Connect</button>
|
||||
<select id="baudRate"></select>
|
||||
<span>
|
||||
Plot Type
|
||||
<select id="plotType">
|
||||
<option value="xt">XT Plot</option>
|
||||
<option value="xy">XY Plot</option>
|
||||
</select>
|
||||
</span>
|
||||
<span>
|
||||
Buffer Size
|
||||
<select id="bufferSize">
|
||||
</select>
|
||||
</span>
|
||||
<input type="checkbox" id="darkmode"> Dark Mode
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas id="myChart" aria-label="A line chart showing data output from a CPX" role="img">
|
||||
</canvas>
|
||||
</div>
|
||||
<div class="serial-input">
|
||||
<input id="serCommand" type="text" disabled />
|
||||
<button id="butSend" type="button" disabled>Send</button>
|
||||
</div>
|
||||
<div id="log"></div>
|
||||
<div class="controls">
|
||||
<input type="checkbox" id="autoscroll"> Autoscroll
|
||||
<input type="checkbox" id="showTimestamp"> Show Timestamp
|
||||
<button id="butClear" type="button">Clear</button>
|
||||
</div>
|
||||
<!--<textarea id="log" readonly></textarea>-->
|
||||
<!-- include the Glitch button to show what the webpage is about and
|
||||
to make it easier for folks to view source and remix -->
|
||||
<div class="glitchButton" style="position:fixed;top:20px;right:20px;"></div>
|
||||
<script src="https://button.glitch.me/button.js"></script>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
480
imu-calibration/script.js
Normal file
480
imu-calibration/script.js
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
// let the editor know that `Chart` is defined by some code
|
||||
// included in another file (in this case, `index.html`)
|
||||
// Note: the code will still work without this line, but without it you
|
||||
// will see an error in the editor
|
||||
/* global Chart */
|
||||
/* global Graph */
|
||||
/* global TransformStream */
|
||||
/* global TextEncoderStream */
|
||||
/* global TextDecoderStream */
|
||||
'use strict';
|
||||
|
||||
let port;
|
||||
let reader;
|
||||
let inputDone;
|
||||
let outputDone;
|
||||
let inputStream;
|
||||
let outputStream;
|
||||
|
||||
const maxLogLength = 500;
|
||||
|
||||
const colors = ['#0000FF', '#FF0000', '#009900', '#FF9900', '#CC00CC', '#666666', '#00CCFF', '#000000'];
|
||||
let dataSets = [];
|
||||
|
||||
const baudRates = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 74880, 115200, 230400, 250000, 500000, 1000000, 2000000];
|
||||
const bufferSizes = [250, 500, 1000, 2500, 5000];
|
||||
const log = document.getElementById('log');
|
||||
const butConnect = document.getElementById('butConnect');
|
||||
const butClear = document.getElementById('butClear');
|
||||
const serCommand = document.getElementById('serCommand');
|
||||
const butSend = document.getElementById('butSend');
|
||||
const baudRate = document.getElementById('baudRate');
|
||||
const autoscroll = document.getElementById('autoscroll');
|
||||
const showTimestamp = document.getElementById('showTimestamp');
|
||||
const plotType = document.getElementById('plotType');
|
||||
const bufferSize = document.getElementById('bufferSize');
|
||||
const lightSS = document.getElementById('light');
|
||||
const darkSS = document.getElementById('dark');
|
||||
const darkMode = document.getElementById('darkmode');
|
||||
let graph;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
butConnect.addEventListener('click', clickConnect);
|
||||
butSend.addEventListener('click', clickSend);
|
||||
butClear.addEventListener('click', clickClear);
|
||||
plotType.addEventListener('change', changePlotType);
|
||||
autoscroll.addEventListener('click', clickAutoscroll);
|
||||
showTimestamp.addEventListener('click', clickTimestamp);
|
||||
baudRate.addEventListener('change', changeBaudRate);
|
||||
bufferSize.addEventListener('change', changeBufferSize);
|
||||
darkMode.addEventListener('click', clickDarkMode);
|
||||
|
||||
if ('serial' in navigator) {
|
||||
const notSupported = document.getElementById('notSupported');
|
||||
notSupported.classList.add('hidden');
|
||||
}
|
||||
|
||||
initBaudRate();
|
||||
initBufferSize();
|
||||
graph = new Graph(document.getElementById('myChart'));
|
||||
loadAllSettings();
|
||||
createChart();
|
||||
});
|
||||
|
||||
// Update the label color only after CSS is finished
|
||||
log.addEventListener('transitionend', function() {
|
||||
graph.updateLabelColor(window.getComputedStyle(log).color);
|
||||
}, false);
|
||||
|
||||
/**
|
||||
* @name connect
|
||||
* Opens a Web Serial connection to a micro:bit and sets up the input and
|
||||
* output stream.
|
||||
*/
|
||||
async function connect() {
|
||||
// - Request a port and open a connection.
|
||||
port = await navigator.serial.requestPort();
|
||||
// - Wait for the port to open.toggleUIConnected
|
||||
await port.open({ baudrate: baudRate.value });
|
||||
|
||||
//const encoder = new TextEncoderStream();
|
||||
//outputDone = encoder.readable.pipeTo(port.writable);
|
||||
//outputStream = encoder.writable;
|
||||
|
||||
let decoder = new TextDecoderStream();
|
||||
inputDone = port.readable.pipeTo(decoder.writable);
|
||||
inputStream = decoder.readable
|
||||
.pipeThrough(new TransformStream(new LineBreakTransformer()));
|
||||
//.pipeThrough(new TransformStream(new ObjectTransformer()));
|
||||
|
||||
reader = inputStream.getReader();
|
||||
readLoop().catch(async function(error) {
|
||||
toggleUIConnected(false);
|
||||
await disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name disconnect
|
||||
* Closes the Web Serial connection.
|
||||
*/
|
||||
async function disconnect() {
|
||||
if (reader) {
|
||||
await reader.cancel();
|
||||
await inputDone.catch(() => {});
|
||||
reader = null;
|
||||
inputDone = null;
|
||||
}
|
||||
|
||||
if (outputStream) {
|
||||
await outputStream.getWriter().close();
|
||||
await outputDone;
|
||||
outputStream = null;
|
||||
outputDone = null;
|
||||
}
|
||||
|
||||
await port.close();
|
||||
port = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name readLoop
|
||||
* Reads data from the input stream and displays it on screen.
|
||||
*/
|
||||
async function readLoop() {
|
||||
while (true) {
|
||||
const {value, done} = await reader.read();
|
||||
if (value) {
|
||||
let plotdata;
|
||||
if (value.substr(0, 4) == "Raw:") {
|
||||
const magnetometer = value.substr(4).trim().split(",").slice(-3).map(x=>+x);
|
||||
plotdata = {
|
||||
xy: [magnetometer[0], magnetometer[1]],
|
||||
yz: [magnetometer[1], magnetometer[2]],
|
||||
zx: [magnetometer[2], magnetometer[0]],
|
||||
}
|
||||
|
||||
// Initialize the chart if we haven't already
|
||||
if (graph.adaChart.data.datasets.length < 1) {
|
||||
setupChart(plotdata);
|
||||
}
|
||||
addJSONValue(plotdata);
|
||||
}
|
||||
}
|
||||
if (done) {
|
||||
console.log('[readLoop] DONE', done);
|
||||
reader.releaseLock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logData(line) {
|
||||
// Update the Log
|
||||
if (showTimestamp.checked) {
|
||||
let d = new Date();
|
||||
let timestamp = d.getHours() + ":" + `${d.getMinutes()}`.padStart(2, 0) + ":" +
|
||||
`${d.getSeconds()}`.padStart(2, 0) + "." + `${d.getMilliseconds()}`.padStart(3, 0);
|
||||
log.innerHTML += '<span class="timestamp">' + timestamp + ' -> </span>';
|
||||
d = null;
|
||||
}
|
||||
log.innerHTML += line+ "<br>";
|
||||
|
||||
// Remove old log content
|
||||
if (log.textContent.split("\n").length > maxLogLength + 1) {
|
||||
let logLines = log.innerHTML.replace(/(\n)/gm, "").split("<br>");
|
||||
log.innerHTML = logLines.splice(-maxLogLength).join("<br>\n");
|
||||
}
|
||||
|
||||
if (autoscroll.checked) {
|
||||
log.scrollTop = log.scrollHeight
|
||||
}
|
||||
}
|
||||
|
||||
let addJSONValue = function(value) {
|
||||
dataSets.forEach((dataSet, index) => {
|
||||
if (value[dataSet.field] != undefined) {
|
||||
graph.addValue(index, value[dataSet.field]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let addCSVValue = function(value) {
|
||||
if (graph.plotType == 'xy') {
|
||||
graph.addValue(0, value.csvdata);
|
||||
} else {
|
||||
dataSets.forEach((dataSet, index) => {
|
||||
if (value.csvdata[dataSet.field] != undefined) {
|
||||
graph.addValue(index, value.csvdata[dataSet.field]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name updateTheme
|
||||
* Sets the theme to Adafruit (dark) mode. Can be refactored later for more themes
|
||||
*/
|
||||
function updateTheme() {
|
||||
// Disable all themes
|
||||
document
|
||||
.querySelectorAll('link[rel=stylesheet].alternate')
|
||||
.forEach((styleSheet) => {
|
||||
enableStyleSheet(styleSheet, false);
|
||||
});
|
||||
|
||||
if (darkMode.checked) {
|
||||
enableStyleSheet(darkSS, true);
|
||||
} else {
|
||||
enableStyleSheet(lightSS, true);
|
||||
}
|
||||
}
|
||||
|
||||
function enableStyleSheet(node, enabled) {
|
||||
node.disabled = !enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name writeToStream
|
||||
* Gets a writer from the output stream and send the lines to the serial device.
|
||||
* @param {...string} lines lines to send to the serial device
|
||||
*/
|
||||
function writeToStream(...lines) {
|
||||
const writer = outputStream.getWriter();
|
||||
lines.forEach((line) => {
|
||||
console.log('[SEND]', line);
|
||||
writer.write(line + '\n');
|
||||
});
|
||||
writer.releaseLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name reset
|
||||
* Reset the Plotter, Log, and associated data
|
||||
*/
|
||||
async function reset() {
|
||||
// Clear the data
|
||||
dataSets = [];
|
||||
graph.reset();
|
||||
log.innerHTML = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickConnect
|
||||
* Click handler for the connect/disconnect button.
|
||||
*/
|
||||
async function clickConnect() {
|
||||
if (port) {
|
||||
await disconnect();
|
||||
toggleUIConnected(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await connect();
|
||||
|
||||
reset();
|
||||
|
||||
toggleUIConnected(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickSend
|
||||
* Click handler for the send button.
|
||||
*/
|
||||
async function clickSend() {
|
||||
let command = serCommand.value;
|
||||
serCommand.value = '';
|
||||
writeToStream(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickAutoscroll
|
||||
* Change handler for the Autoscroll checkbox.
|
||||
*/
|
||||
async function clickAutoscroll() {
|
||||
saveSetting('autoscroll', autoscroll.checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickTimestamp
|
||||
* Change handler for the Show Timestamp checkbox.
|
||||
*/
|
||||
async function clickTimestamp() {
|
||||
saveSetting('timestamp', showTimestamp.checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name changeBaudRate
|
||||
* Change handler for the Baud Rate selector.
|
||||
*/
|
||||
async function changeBaudRate() {
|
||||
saveSetting('baudrate', baudRate.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name changeBufferSize
|
||||
* Change handler for the Buffer Size selector.
|
||||
*/
|
||||
async function changeBufferSize() {
|
||||
saveSetting('buffersize', bufferSize.value);
|
||||
graph.setBufferSize(bufferSize.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickDarkMode
|
||||
* Change handler for the Dark Mode checkbox.
|
||||
*/
|
||||
async function clickDarkMode() {
|
||||
updateTheme();
|
||||
saveSetting('darkmode', darkMode.checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name changePlotType
|
||||
* Change handler for the Plot Type selector.
|
||||
*/
|
||||
async function changePlotType() {
|
||||
saveSetting('plottype', plotType.value);
|
||||
graph.setPlotType(plotType.value);
|
||||
reset();
|
||||
createChart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickClear
|
||||
* Click handler for the clear button.
|
||||
*/
|
||||
async function clickClear() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name LineBreakTransformer
|
||||
* TransformStream to parse the stream into lines.
|
||||
*/
|
||||
class LineBreakTransformer {
|
||||
constructor() {
|
||||
// A container for holding stream data until a new line.
|
||||
this.container = '';
|
||||
}
|
||||
|
||||
transform(chunk, controller) {
|
||||
this.container += chunk;
|
||||
const lines = this.container.split('\n');
|
||||
this.container = lines.pop();
|
||||
lines.forEach(line => {
|
||||
controller.enqueue(line)
|
||||
logData(line);
|
||||
});
|
||||
}
|
||||
|
||||
flush(controller) {
|
||||
controller.enqueue(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ObjectTransformer
|
||||
* TransformStream to parse the stream into a valid object.
|
||||
*/
|
||||
/*class ObjectTransformer {
|
||||
transform(chunk, controller) {
|
||||
let plotdata;
|
||||
if (chunk.substr(0, 4) == "Raw:") {
|
||||
const magnetometer = chunk.substr(4).trim().split(",").slice(-3).map(x=>+x);
|
||||
plotdata = {
|
||||
xy: [magnetometer[0], magnetometer[1]],
|
||||
yz: [magnetometer[1], magnetometer[2]],
|
||||
zx: [magnetometer[2], magnetometer[0]],
|
||||
}
|
||||
controller.enqueue(plotdata);
|
||||
} else {
|
||||
controller.enqueue(null);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
function convertJSON(chunk) {
|
||||
try {
|
||||
let jsonObj = JSON.parse(chunk);
|
||||
jsonObj._raw = chunk;
|
||||
return jsonObj;
|
||||
} catch (e) {
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleUIConnected(connected) {
|
||||
let lbl = 'Connect';
|
||||
if (connected) {
|
||||
lbl = 'Disconnect';
|
||||
}
|
||||
serCommand.disabled = !connected
|
||||
butSend.disabled = !connected
|
||||
butConnect.textContent = lbl;
|
||||
}
|
||||
|
||||
function setupChart(value) {
|
||||
// Use the value as a template
|
||||
if (value.csvdata) {
|
||||
if (graph.plotType == "xt") {
|
||||
value.csvdata.forEach((item, index) => {
|
||||
dataSets.push({
|
||||
label: "",
|
||||
field: index,
|
||||
borderColor: colors[index % colors.length]
|
||||
});
|
||||
});
|
||||
} else {
|
||||
dataSets.push({
|
||||
label: "",
|
||||
field: 0,
|
||||
borderColor: colors[0]
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Object.entries(value).forEach(([key, item], index) => {
|
||||
if (key != "_raw") {
|
||||
dataSets.push({
|
||||
label: key,
|
||||
field: key,
|
||||
borderColor: colors[index % colors.length]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dataSets.forEach((dataSet) => {
|
||||
graph.addDataSet(dataSet.label, dataSet.borderColor);
|
||||
});
|
||||
|
||||
graph.update();
|
||||
}
|
||||
|
||||
function initBaudRate() {
|
||||
for (let rate of baudRates) {
|
||||
var option = document.createElement("option");
|
||||
option.text = rate + " Baud";
|
||||
option.value = rate;
|
||||
baudRate.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
function initBufferSize() {
|
||||
for (let size of bufferSizes) {
|
||||
var option = document.createElement("option");
|
||||
option.text = size + " Data Points";
|
||||
option.value = size;
|
||||
bufferSize.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
function loadAllSettings() {
|
||||
// Load all saved settings or defaults
|
||||
autoscroll.checked = loadSetting('autoscroll', true);
|
||||
showTimestamp.checked = loadSetting('timestamp', false);
|
||||
plotType.value = loadSetting('plottype', 'xy');
|
||||
graph.setPlotType(plotType.value);
|
||||
baudRate.value = loadSetting('baudrate', 9600);
|
||||
bufferSize.value = loadSetting('buffersize', 2500);
|
||||
graph.setBufferSize(bufferSize.value);
|
||||
darkMode.checked = loadSetting('darkmode', false);
|
||||
}
|
||||
|
||||
function loadSetting(setting, defaultValue) {
|
||||
let value = JSON.parse(window.localStorage.getItem(setting));
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function saveSetting(setting, value) {
|
||||
window.localStorage.setItem(setting, JSON.stringify(value));
|
||||
}
|
||||
|
||||
function createChart() {
|
||||
graph.create();
|
||||
updateTheme();
|
||||
}
|
||||
BIN
imu-calibration/thumbnail.png
Normal file
BIN
imu-calibration/thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
78
index.html
Normal file
78
index.html
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Adafruit WebSerial Plotter</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script>
|
||||
// Redirect to HTTPS if HTTP is requested.
|
||||
if (window.location.protocol === 'http:') {
|
||||
window.location.href = 'https:' + window.location.href.substring(5);
|
||||
}
|
||||
</script>
|
||||
<!-- import Chart.js library via CDN, per Chart.js documentation -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js" defer></script>
|
||||
<script src="/graph.js" defer></script>
|
||||
|
||||
<!-- import the webpage's stylesheet -->
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="stylesheet" href="/light.css" id="light" class="alternate" disabled>
|
||||
<link rel="stylesheet" href="/dark.css" id="dark" class="alternate" disabled>
|
||||
|
||||
<!-- import the webpage's javascript file -->
|
||||
<script src="/script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<h1>Adafruit WebSerial Plotter</h1>
|
||||
</header>
|
||||
<main class="main">
|
||||
<div id="notSupported">
|
||||
Sorry, <b>Web Serial</b> is not supported on this device, make sure you're
|
||||
running Chrome 78 or later and have enabled the
|
||||
<code>#enable-experimental-web-platform-features</code> flag in
|
||||
<code>chrome://flags</code>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="butConnect" type="button">Connect</button>
|
||||
<select id="baudRate"></select>
|
||||
<span>
|
||||
Plot Type
|
||||
<select id="plotType">
|
||||
<option value="xt">XT Plot</option>
|
||||
<option value="xy">XY Plot</option>
|
||||
</select>
|
||||
</span>
|
||||
<span>
|
||||
Buffer Size
|
||||
<select id="bufferSize">
|
||||
</select>
|
||||
</span>
|
||||
<input type="checkbox" id="darkmode"> Dark Mode
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas id="myChart" aria-label="A line chart showing data output from a CPX" role="img">
|
||||
</canvas>
|
||||
</div>
|
||||
<div class="serial-input">
|
||||
<input id="serCommand" type="text" disabled />
|
||||
<button id="butSend" type="button" disabled>Send</button>
|
||||
</div>
|
||||
<div id="log"></div>
|
||||
<div class="controls">
|
||||
<input type="checkbox" id="autoscroll"> Autoscroll
|
||||
<input type="checkbox" id="showTimestamp"> Show Timestamp
|
||||
<button id="butClear" type="button">Clear</button>
|
||||
</div>
|
||||
<!--<textarea id="log" readonly></textarea>-->
|
||||
<!-- include the Glitch button to show what the webpage is about and
|
||||
to make it easier for folks to view source and remix -->
|
||||
<div class="glitchButton" style="position:fixed;top:20px;right:20px;"></div>
|
||||
<script src="https://button.glitch.me/button.js"></script>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
42
light.css
Normal file
42
light.css
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
.header {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border-color: purple;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
input, select, button {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.serial-input input {
|
||||
border-color: purple;
|
||||
}
|
||||
|
||||
.serial-input input:disabled {
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
#notSupported {
|
||||
background-color: red;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#log {
|
||||
border-color: purple;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
469
script.js
Normal file
469
script.js
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
// let the editor know that `Chart` is defined by some code
|
||||
// included in another file (in this case, `index.html`)
|
||||
// Note: the code will still work without this line, but without it you
|
||||
// will see an error in the editor
|
||||
/* global Chart */
|
||||
/* global Graph */
|
||||
/* global TransformStream */
|
||||
/* global TextEncoderStream */
|
||||
/* global TextDecoderStream */
|
||||
'use strict';
|
||||
|
||||
let port;
|
||||
let reader;
|
||||
let inputDone;
|
||||
let outputDone;
|
||||
let inputStream;
|
||||
let outputStream;
|
||||
let addValue;
|
||||
|
||||
const colors = ['#0000FF', '#FF0000', '#009900', '#FF9900', '#CC00CC', '#666666', '#00CCFF', '#000000'];
|
||||
let dataSets = [];
|
||||
|
||||
const baudRates = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 74880, 115200, 230400, 250000, 500000, 1000000, 2000000];
|
||||
const bufferSizes = [250, 500, 1000, 2500, 5000];
|
||||
const log = document.getElementById('log');
|
||||
const butConnect = document.getElementById('butConnect');
|
||||
const butClear = document.getElementById('butClear');
|
||||
const serCommand = document.getElementById('serCommand');
|
||||
const butSend = document.getElementById('butSend');
|
||||
const baudRate = document.getElementById('baudRate');
|
||||
const autoscroll = document.getElementById('autoscroll');
|
||||
const showTimestamp = document.getElementById('showTimestamp');
|
||||
const plotType = document.getElementById('plotType');
|
||||
const bufferSize = document.getElementById('bufferSize');
|
||||
const lightSS = document.getElementById('light');
|
||||
const darkSS = document.getElementById('dark');
|
||||
const darkMode = document.getElementById('darkmode');
|
||||
let graph;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
butConnect.addEventListener('click', clickConnect);
|
||||
butSend.addEventListener('click', clickSend);
|
||||
butClear.addEventListener('click', clickClear);
|
||||
plotType.addEventListener('change', changePlotType);
|
||||
autoscroll.addEventListener('click', clickAutoscroll);
|
||||
showTimestamp.addEventListener('click', clickTimestamp);
|
||||
baudRate.addEventListener('change', changeBaudRate);
|
||||
bufferSize.addEventListener('change', changeBufferSize);
|
||||
darkMode.addEventListener('click', clickDarkMode);
|
||||
|
||||
if ('serial' in navigator) {
|
||||
const notSupported = document.getElementById('notSupported');
|
||||
notSupported.classList.add('hidden');
|
||||
}
|
||||
|
||||
initBaudRate();
|
||||
initBufferSize();
|
||||
graph = new Graph(document.getElementById('myChart'));
|
||||
loadAllSettings();
|
||||
createChart();
|
||||
});
|
||||
|
||||
// Update the label color only after CSS is finished
|
||||
log.addEventListener('transitionend', function() {
|
||||
graph.updateLabelColor(window.getComputedStyle(log).color);
|
||||
}, false);
|
||||
|
||||
/**
|
||||
* @name connect
|
||||
* Opens a Web Serial connection to a micro:bit and sets up the input and
|
||||
* output stream.
|
||||
*/
|
||||
async function connect() {
|
||||
// - Request a port and open a connection.
|
||||
port = await navigator.serial.requestPort();
|
||||
// - Wait for the port to open.toggleUIConnected
|
||||
await port.open({ baudrate: baudRate.value });
|
||||
|
||||
const encoder = new TextEncoderStream();
|
||||
outputDone = encoder.readable.pipeTo(port.writable);
|
||||
outputStream = encoder.writable;
|
||||
|
||||
let decoder = new TextDecoderStream();
|
||||
inputDone = port.readable.pipeTo(decoder.writable);
|
||||
inputStream = decoder.readable
|
||||
.pipeThrough(new TransformStream(new LineBreakTransformer()))
|
||||
.pipeThrough(new TransformStream(new ObjectTransformer()));
|
||||
|
||||
reader = inputStream.getReader();
|
||||
readLoop().catch((error) => {
|
||||
toggleUIConnected(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name disconnect
|
||||
* Closes the Web Serial connection.
|
||||
*/
|
||||
async function disconnect() {
|
||||
|
||||
if (reader) {
|
||||
await reader.cancel();
|
||||
await inputDone.catch(() => {});
|
||||
reader = null;
|
||||
inputDone = null;
|
||||
}
|
||||
|
||||
if (outputStream) {
|
||||
await outputStream.getWriter().close();
|
||||
await outputDone;
|
||||
outputStream = null;
|
||||
outputDone = null;
|
||||
}
|
||||
|
||||
await port.close();
|
||||
port = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name readLoop
|
||||
* Reads data from the input stream and displays it on screen.
|
||||
*/
|
||||
async function readLoop() {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (value) {
|
||||
// Initialize the chart if we haven't already
|
||||
if (graph.adaChart.data.datasets.length < 1) {
|
||||
setupChart(value);
|
||||
}
|
||||
addValue(value);
|
||||
}
|
||||
|
||||
if (done) {
|
||||
console.log('[readLoop] DONE', done);
|
||||
reader.releaseLock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logData(line) {
|
||||
// Update the Log
|
||||
if (showTimestamp.checked) {
|
||||
let d = new Date();
|
||||
let timestamp = d.getHours() + ":" + `${d.getMinutes()}`.padStart(2, 0) + ":" +
|
||||
`${d.getSeconds()}`.padStart(2, 0) + "." + `${d.getMilliseconds()}`.padStart(3, 0);
|
||||
log.innerHTML += '<span class="timestamp">' + timestamp + ' -> </span>';
|
||||
d = null;
|
||||
}
|
||||
log.innerHTML += line+ "<br>";
|
||||
|
||||
// Remove old log content
|
||||
if (log.textContent.split("\n").length > bufferSize.value + 1) {
|
||||
let logLines = log.innerHTML.replace(/(\n)/gm, "").split("<br>");
|
||||
log.innerHTML = logLines.splice(-bufferSize.value).join("<br>\n");
|
||||
}
|
||||
|
||||
if (autoscroll.checked) {
|
||||
log.scrollTop = log.scrollHeight
|
||||
}
|
||||
}
|
||||
|
||||
let addJSONValue = function(value) {
|
||||
dataSets.forEach((dataSet, index) => {
|
||||
let dataItem = value[dataSet.field];
|
||||
graph.addValue(index, dataItem);
|
||||
});
|
||||
}
|
||||
|
||||
let addCSVValue = function(value) {
|
||||
if (graph.plotType == 'xy') {
|
||||
graph.addValue(0, value.csvdata);
|
||||
} else {
|
||||
dataSets.forEach((dataSet, index) => {
|
||||
graph.addValue(index, value.csvdata[dataSet.field]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name updateTheme
|
||||
* Sets the theme to Adafruit (dark) mode. Can be refactored later for more themes
|
||||
*/
|
||||
function updateTheme() {
|
||||
// Disable all themes
|
||||
document
|
||||
.querySelectorAll('link[rel=stylesheet].alternate')
|
||||
.forEach((styleSheet) => {
|
||||
enableStyleSheet(styleSheet, false);
|
||||
});
|
||||
|
||||
if (darkMode.checked) {
|
||||
enableStyleSheet(darkSS, true);
|
||||
} else {
|
||||
enableStyleSheet(lightSS, true);
|
||||
}
|
||||
|
||||
graph.updateLabelColor(window.getComputedStyle(log).color);
|
||||
}
|
||||
|
||||
function enableStyleSheet(node, enabled) {
|
||||
node.disabled = !enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name writeToStream
|
||||
* Gets a writer from the output stream and send the lines to the serial device.
|
||||
* @param {...string} lines lines to send to the serial device
|
||||
*/
|
||||
function writeToStream(...lines) {
|
||||
const writer = outputStream.getWriter();
|
||||
lines.forEach((line) => {
|
||||
console.log('[SEND]', line);
|
||||
writer.write(line + '\n');
|
||||
});
|
||||
writer.releaseLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name reset
|
||||
* Reset the Plotter, Log, and associated data
|
||||
*/
|
||||
async function reset() {
|
||||
// Clear the data
|
||||
dataSets = [];
|
||||
graph.reset();
|
||||
log.innerHTML = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickConnect
|
||||
* Click handler for the connect/disconnect button.
|
||||
*/
|
||||
async function clickConnect() {
|
||||
if (port) {
|
||||
await disconnect();
|
||||
toggleUIConnected(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await connect();
|
||||
|
||||
reset();
|
||||
|
||||
toggleUIConnected(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickSend
|
||||
* Click handler for the send button.
|
||||
*/
|
||||
async function clickSend() {
|
||||
let command = serCommand.value;
|
||||
serCommand.value = '';
|
||||
writeToStream(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickAutoscroll
|
||||
* Change handler for the Autoscroll checkbox.
|
||||
*/
|
||||
async function clickAutoscroll() {
|
||||
saveSetting('autoscroll', autoscroll.checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickTimestamp
|
||||
* Change handler for the Show Timestamp checkbox.
|
||||
*/
|
||||
async function clickTimestamp() {
|
||||
saveSetting('timestamp', showTimestamp.checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name changeBaudRate
|
||||
* Change handler for the Baud Rate selector.
|
||||
*/
|
||||
async function changeBaudRate() {
|
||||
saveSetting('baudrate', baudRate.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name changeBufferSize
|
||||
* Change handler for the Buffer Size selector.
|
||||
*/
|
||||
async function changeBufferSize() {
|
||||
saveSetting('buffersize', bufferSize.value);
|
||||
graph.setBufferSize(bufferSize.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickDarkMode
|
||||
* Change handler for the Dark Mode checkbox.
|
||||
*/
|
||||
async function clickDarkMode() {
|
||||
updateTheme();
|
||||
saveSetting('darkmode', darkMode.checked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name changePlotType
|
||||
* Change handler for the Plot Type selector.
|
||||
*/
|
||||
async function changePlotType() {
|
||||
saveSetting('plottype', plotType.value);
|
||||
graph.setPlotType(plotType.value);
|
||||
reset();
|
||||
createChart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name clickClear
|
||||
* Click handler for the clear button.
|
||||
*/
|
||||
async function clickClear() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name LineBreakTransformer
|
||||
* TransformStream to parse the stream into lines.
|
||||
*/
|
||||
class LineBreakTransformer {
|
||||
constructor() {
|
||||
// A container for holding stream data until a new line.
|
||||
this.container = '';
|
||||
}
|
||||
|
||||
transform(chunk, controller) {
|
||||
this.container += chunk;
|
||||
const lines = this.container.split('\n');
|
||||
this.container = lines.pop();
|
||||
lines.forEach(line => controller.enqueue(line));
|
||||
}
|
||||
|
||||
flush(controller) {
|
||||
controller.enqueue(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ObjectTransformer
|
||||
* TransformStream to parse the stream into a valid object.
|
||||
*/
|
||||
class ObjectTransformer {
|
||||
transform(chunk, controller) {
|
||||
// Log Raw Data
|
||||
logData(chunk);
|
||||
|
||||
let jsobj = convertJSON(chunk)
|
||||
// Define the correct function ahead of time
|
||||
if (jsobj == chunk) {
|
||||
jsobj = convertCSV(chunk)
|
||||
addValue = addCSVValue;
|
||||
} else {
|
||||
addValue = addJSONValue;
|
||||
}
|
||||
controller.enqueue(jsobj);
|
||||
}
|
||||
}
|
||||
|
||||
function convertJSON(chunk) {
|
||||
try {
|
||||
let jsonObj = JSON.parse(chunk);
|
||||
return jsonObj;
|
||||
} catch (e) {
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
function convertCSV(chunk) {
|
||||
let jsobj = {};
|
||||
jsobj.csvdata = chunk.split(",");
|
||||
return jsobj;
|
||||
}
|
||||
|
||||
function toggleUIConnected(connected) {
|
||||
let lbl = 'Connect';
|
||||
if (connected) {
|
||||
lbl = 'Disconnect';
|
||||
}
|
||||
serCommand.disabled = !connected
|
||||
butSend.disabled = !connected
|
||||
butConnect.textContent = lbl;
|
||||
}
|
||||
|
||||
function setupChart(value) {
|
||||
// Use the value as a template
|
||||
if (value.csvdata) {
|
||||
if (graph.plotType == "xt") {
|
||||
value.csvdata.forEach((item, index) => {
|
||||
dataSets.push({
|
||||
label: "",
|
||||
field: index,
|
||||
borderColor: colors[index % colors.length]
|
||||
});
|
||||
});
|
||||
} else {
|
||||
dataSets.push({
|
||||
label: "",
|
||||
field: 0,
|
||||
borderColor: colors[0]
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Object.entries(value).forEach(([key, item], index) => {
|
||||
dataSets.push({
|
||||
label: key,
|
||||
field: key,
|
||||
borderColor: colors[index % colors.length]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
dataSets.forEach((dataSet) => {
|
||||
graph.addDataSet(dataSet.label, dataSet.borderColor);
|
||||
});
|
||||
|
||||
graph.update();
|
||||
}
|
||||
|
||||
function initBaudRate() {
|
||||
for (let rate of baudRates) {
|
||||
var option = document.createElement("option");
|
||||
option.text = rate + " Baud";
|
||||
option.value = rate;
|
||||
baudRate.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
function initBufferSize() {
|
||||
for (let size of bufferSizes) {
|
||||
var option = document.createElement("option");
|
||||
option.text = size + " Data Points";
|
||||
option.value = size;
|
||||
bufferSize.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
function loadAllSettings() {
|
||||
// Load all saved settings or defaults
|
||||
autoscroll.checked = loadSetting('autoscroll', true);
|
||||
showTimestamp.checked = loadSetting('timestamp', false);
|
||||
plotType.value = loadSetting('plottype', 'xt');
|
||||
graph.setPlotType(plotType.value);
|
||||
baudRate.value = loadSetting('baudrate', 9600);
|
||||
bufferSize.value = loadSetting('buffersize', 500);
|
||||
graph.setBufferSize(bufferSize.value);
|
||||
darkMode.checked = loadSetting('darkmode', false);
|
||||
}
|
||||
|
||||
function loadSetting(setting, defaultValue) {
|
||||
let value = JSON.parse(window.localStorage.getItem(setting));
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function saveSetting(setting, value) {
|
||||
window.localStorage.setItem(setting, JSON.stringify(value));
|
||||
}
|
||||
|
||||
function createChart() {
|
||||
graph.create();
|
||||
updateTheme();
|
||||
}
|
||||
124
style.css
Normal file
124
style.css
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Header
|
||||
*/
|
||||
|
||||
.header {
|
||||
align-content: center;
|
||||
align-items: stretch;
|
||||
box-shadow:
|
||||
0 4px 5px 0 rgba(0, 0, 0, 0.14),
|
||||
0 2px 9px 1px rgba(0, 0, 0, 0.12),
|
||||
0 4px 2px -2px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
font-size: 20px;
|
||||
height: 5vh;
|
||||
min-height: 50px;
|
||||
justify-content: flex-start;
|
||||
padding: 16px 16px 0 16px;
|
||||
position: fixed;
|
||||
transition: transform 0.233s cubic-bezier(0, 0, 0.21, 1) 0.1s;
|
||||
width: 100%;
|
||||
will-change: transform;
|
||||
z-index: 1000;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
flex: 1;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-family: "Benton Sans", "Helvetica Neue", helvetica, arial, sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 0.9em;
|
||||
margin: 5px 10px;
|
||||
}
|
||||
|
||||
.serial-input {
|
||||
margin: 10px 0;
|
||||
height: 3vh;
|
||||
line-height: 3vh;
|
||||
}
|
||||
|
||||
.serial-input input {
|
||||
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
|
||||
font-size: 0.8em;
|
||||
width: 90%;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.serial-input input:disabled {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.serial-input button {
|
||||
width: 8%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-top: 80px;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
height: 3vh;
|
||||
line-height: 3vh;
|
||||
}
|
||||
|
||||
.controls span {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height:40vh;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
#notSupported {
|
||||
padding: 1em;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#log {
|
||||
height: 40vh;
|
||||
width: 100%;
|
||||
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
|
||||
font-size: 0.8em;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
overflow-x: hidden;
|
||||
overflow-x: auto;
|
||||
transition : color 0.1s linear;
|
||||
}
|
||||
|
||||
95
webserial.js
Normal file
95
webserial.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* @name connect
|
||||
* Opens a Web Serial connection to a micro:bit and sets up the input and
|
||||
* output stream.
|
||||
|
||||
*/
|
||||
|
||||
var WebSerial = function (obj) {
|
||||
let port;
|
||||
let reader;
|
||||
let inputDone;
|
||||
let outputDone;
|
||||
let inputStream;
|
||||
let outputStream;
|
||||
let addValue;
|
||||
|
||||
const baudRates = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 74880, 115200, 230400, 250000, 500000, 1000000, 2000000];
|
||||
const bufferSizes = [250, 500, 1000, 2500, 5000];
|
||||
|
||||
if (obj instanceof WebSerial) {
|
||||
return obj;
|
||||
}
|
||||
if (!(this instanceof WebSerial)) {
|
||||
return new WebSerial(obj);
|
||||
}
|
||||
};
|
||||
|
||||
WebSerial.prototype = {
|
||||
connect: async function () {
|
||||
// - Request a port and open a connection.
|
||||
this.port = await navigator.serial.requestPort();
|
||||
// - Wait for the port to open.toggleUIConnected
|
||||
await this.port.open({ baudrate: this.baudrate });
|
||||
|
||||
const encoder = new TextEncoderStream();
|
||||
this.outputDone = encoder.readable.pipeTo(this.port.writable);
|
||||
this.outputStream = encoder.writable;
|
||||
|
||||
let decoder = new TextDecoderStream();
|
||||
this.inputDone = this.port.readable.pipeTo(decoder.writable);
|
||||
this.inputStream = decoder.readable
|
||||
.pipeThrough(new TransformStream(new LineBreakTransformer()))
|
||||
.pipeThrough(new TransformStream(new ObjectTransformer()));
|
||||
|
||||
this.reader = inputStream.getReader();
|
||||
readLoop().catch((error) => {
|
||||
this.disconnect();
|
||||
toggleUIConnected(false);
|
||||
});
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @name LineBreakTransformer
|
||||
* TransformStream to parse the stream into lines.
|
||||
*/
|
||||
class LineBreakTransformer {
|
||||
constructor() {
|
||||
// A container for holding stream data until a new line.
|
||||
this.container = '';
|
||||
}
|
||||
|
||||
transform(chunk, controller) {
|
||||
this.container += chunk;
|
||||
const lines = this.container.split('\n');
|
||||
this.container = lines.pop();
|
||||
lines.forEach(line => controller.enqueue(line));
|
||||
}
|
||||
|
||||
flush(controller) {
|
||||
controller.enqueue(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ObjectTransformer
|
||||
* TransformStream to parse the stream into a valid object.
|
||||
*/
|
||||
class ObjectTransformer {
|
||||
transform(chunk, controller) {
|
||||
let jsobj = convertJSON(chunk)
|
||||
// Define the correct function ahead of time
|
||||
if (jsobj.raw === undefined) {
|
||||
jsobj = convertCSV(chunk)
|
||||
addValue = addCSVValue;
|
||||
} else {
|
||||
addValue = addJSONValue;
|
||||
}
|
||||
controller.enqueue(jsobj);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue