Help needed with uPlot running on ESP32 microcontroller
@leeoniya Hi Leon,
Thank you for such a powerful graph software which can run on microcontroller where webserver itself is hosted.
With your help I was able to implement graphs for my home automation project. In my opinion it is easiest and fastest way to visualize sensor data of whole network with very little effort. Everything is working fine for me.
Now I wanted to add some filters to load graph according to three dropdown menu at the top and I am not sure it is even possible.
I have shared relevant code from my project with mock up of three drop down menus. If it is possible to load graph according to selection at top can you please show me how. My code is below: Thanks.
My data.json structure is : [12,"Epoch","Location","Voltage","SSID","T1","S1","T2","S2","T3","S3","T4","S4"]
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Proberequest, TinyMQTT, Websockets, Asyncwebserver, LittleFS Editor & uPlot</title>
<link rel="stylesheet" href="uPlot.min.css">
<style>
body {margin: 0;}
.u-legend.u-inline .u-value {width: 150px;text-align: left;}
#container { width: 100%; height: 49vh; background-color: #333; display: flex; align-items: center; justify-content: center; overflow: hidden; border-radius: 7px; touch-action: none; }
#item { width: 100px; height: 100px; background-color: rgb(245, 230, 99); border: 10px solid rgba(136, 136, 136, .5); border-radius: 50%; touch-action: none; user-select: none; }
#item:active { background-color: rgba(168, 218, 220, 1.00); }
#item:hover { cursor: pointer; border-width: 20px; }
#area { position: fixed; right: 33.33%; top: 50vh; }
#stream-container{ height: 49vh; padding:0; margin:0; margin-block-start:0; margin-block-end:0; margin-inline-start:0; margin-inline-end:0 }
#stream-container img{ display:block; max-width:100%; min-height:49vh; border-radius:4px; margin-top:8px }
</style>
</head>
<body>
<script type="text/javascript" src="uPlot.iife.min.js"></script>
<h2>Proberequest, TinyMQTT, Asyncwebserver, LittleFS Editor & uPlot</h2>
<SELECT class="combine" id ="period" name = "period">
<option value="all">Graph by Period</option>
<option value="1">Last Hour</option>
<option value="2">Today</option>
<option value="3">This Week</option>
<option value="4">This Month</option>
<option value="5">This Year</option>
</SELECT>
<SELECT class="combine" id ="deviceid" name = "deviceid">
<option value="all">Graph by Device</option>
<option value="1">Gateway</option>
<option value="6">Livingroom</option>
<option value="16">Kitchen</option>
<option value="26">Bedroom1</option>
<option value="36">Bedroom2</option>
<option value="46">Bathroom1</option>
<option value="56">Bathroom2</option>
<option value="66">Laundry</option>
<option value="76">Solar Tracker</option>
<option value="86">Water Tank</option>
<option value="96">Weather Station</option>
<option value="106">Greenhouse</option>
</SELECT>
<SELECT class="combine" id ="sensortype" name = "sensortype">
<option value="all">Graph by Sensor Type</option>
<option value="100">Voltage</option>
<option value="200">RSSI</option>
<option value="1">Temperature</option>
<option value="2">Humidity</option>
<option value="3">Pressure</option>
<option value="4">Light</option>
<option value="5">Level</option>
<option value="6">Switch Status</option>
<option value="7">Motion</option>
<option value="8">Azimuth</option>
<option value="9">Elevation</option>
<option value="10">Soil Moisture</option>
<option value="11">Wind Speed</option>
<option value="12">Wind Direction</option>
<option value="13">Rainfall</option>
<option value="14">Air Quality</option>
</SELECT>
<script>
var sensor1,sensor2,sensor3,sensor4,sensor5,sensor6,sensor7,sensor8,sensor9,sensor10,sensor11,sensor12,sensor13,sensor14;
// converts the legend into a simple tooltip
function legendAsTooltipPlugin({ className, style = { backgroundColor:"rgba(255, 249, 196, 0.92)", color: "black" } } = {}) {
let legendEl;
function init(u, opts) {
legendEl = u.root.querySelector(".u-legend");
legendEl.classList.remove("u-inline");
className && legendEl.classList.add(className);
uPlot.assign(legendEl.style, {
textAlign: "left",
pointerEvents: "",
display: "none",
position: "absolute",
left: 0,
top: 0,
zIndex: 100,
boxShadow: "2px 2px 10px rgba(0,0,0,0.5)",
...style
});
// hide series color markers
const idents = legendEl.querySelectorAll(".u-marker");
for (let i = 0; i < idents.length; i++)
idents[i].style.display = "";
const overEl = u.root.querySelector(".u-over");
overEl.style.overflow = "visible";
// move legend into plot bounds
overEl.appendChild(legendEl);
// show/hide tooltip on enter/exit
overEl.addEventListener("mouseenter", () => {legendEl.style.display = null;});
overEl.addEventListener("mouseleave", () => {legendEl.style.display = "none";});
// let tooltip exit plot
// overEl.style.overflow = "visible";
}
function update(u) {
const { left, top } = u.cursor;
legendEl.style.transform = "translate(" + left + "px, " + top + "px)";
}
return {
hooks: {
init: init,
setCursor: update,
}
};
}
function prepData(packed) {
console.time("prep");
// epoch,location,voltage,rssi,temperature,humidity,pressure,light
const numFields = packed[0];
console.log(numFields); //12
packed = packed.slice(numFields + 1);
console.log(packed); //single array of all sensor data.
let data = [
Array(packed.length/numFields), // Time
Array(packed.length/numFields), // Location
Array(packed.length/numFields), // Voltage
Array(packed.length/numFields), // RSSI
Array(packed.length/numFields), // Temperature
Array(packed.length/numFields), // Humidity
Array(packed.length/numFields), // Pressure
Array(packed.length/numFields), // Light
Array(packed.length/numFields), // Level
Array(packed.length/numFields), // Switch Status
Array(packed.length/numFields), // Motion
Array(packed.length/numFields), // Azimuth
Array(packed.length/numFields), // Elevation
Array(packed.length/numFields), // Soil Moisture
Array(packed.length/numFields), // Wind Speed
Array(packed.length/numFields), // Wind Direction
Array(packed.length/numFields), // Rainfall
Array(packed.length/numFields), // Air Quality
];
console.log(data); // (12)arrays of values by field
for (let i = 0, j = 0; i < packed.length; i += numFields, j++) {
data[0][j] = packed[i+0];
data[1][j] = packed[i+1];
data[2][j] = packed[i+2];
data[3][j] = packed[i+3];
if (packed[i+4] == 1) {data[4][j] = packed[i+5]; sensor1 = "Temperature";
} else if (packed[i+4] == 2) {data[5][j] = packed[i+5]; sensor2 = "Humidity";
} else if (packed[i+4] == 3) {data[6][j] = packed[i+5] * 4; sensor3 = "Pressure";
} else if (packed[i+4] == 4) {data[7][j] = packed[i+5]; sensor4 = "Light";
} else if (packed[i+4] == 5) {data[8][j] = packed[i+5]; sensor5 = "Level";
} else if (packed[i+4] == 6) {data[9][j] = packed[i+5]; sensor6 = "Switch Status";
} else if (packed[i+4] == 7) {data[10][j] = packed[i+5]; sensor7 = "Motion";
} else if (packed[i+4] == 8) {data[11][j] = packed[i+5]; sensor8 = "Azimuth";
} else if (packed[i+4] == 9) {data[12][j] = packed[i+5]; sensor9 = "Elevation";
} else if (packed[i+4] == 10) {data[13][j] = packed[i+5];sensor10 = "Soil Moisture";
} else if (packed[i+4] == 11) {data[14][j] = packed[i+5];sensor11 = "Wind Speed";
} else if (packed[i+4] == 12) {data[15][j] = packed[i+5];sensor12 = "Wind Direction";
} else if (packed[i+4] == 13) {data[16][j] = packed[i+5];sensor13 = "Rainfall";
} else if (packed[i+4] == 14) {data[17][j] = packed[i+5];sensor14 = "Air Quality";}
if (packed[i+6] == 1) {data[4][j] = packed[i+7]; sensor1 = "Temperature";
} else if (packed[i+6] == 2) {data[5][j] = packed[i+7]; sensor2 = "Humidity";
} else if (packed[i+6] == 3) {data[6][j] = packed[i+7] * 4; sensor3 = "Pressure";
} else if (packed[i+6] == 4) {data[7][j] = packed[i+7]; sensor4 = "Light";
} else if (packed[i+6] == 5) {data[8][j] = packed[i+7]; sensor5 = "Level";
} else if (packed[i+6] == 6) {data[9][j] = packed[i+7]; sensor6 = "Switch Status";
} else if (packed[i+6] == 7) {data[10][j] = packed[i+7]; sensor7 = "Motion";
} else if (packed[i+6] == 8) {data[11][j] = packed[i+7]; sensor8 = "Azimuth";
} else if (packed[i+6] == 9) {data[12][j] = packed[i+7]; sensor9 = "Elevation";
} else if (packed[i+6] == 10) {data[13][j] = packed[i+7];sensor10 = "Soil Moisture";
} else if (packed[i+6] == 11) {data[14][j] = packed[i+7];sensor11 = "Wind Speed";
} else if (packed[i+6] == 12) {data[15][j] = packed[i+7];sensor12 = "Wind Direction";
} else if (packed[i+6] == 13) {data[16][j] = packed[i+7];sensor13 = "Rainfall";
} else if (packed[i+6] == 14) {data[17][j] = packed[i+7];sensor14 = "Air Quality";}
if (packed[i+8] == 1) {data[4][j] = packed[i+9]; sensor1 = "Temperature";
} else if (packed[i+8] == 2) {data[5][j] = packed[i+9]; sensor2 = "Humidity";
} else if (packed[i+8] == 3) {data[6][j] = packed[i+9] * 4; sensor3 = "Pressure";
} else if (packed[i+8] == 4) {data[7][j] = packed[i+9]; sensor4 = "Light";
} else if (packed[i+8] == 5) {data[8][j] = packed[i+9]; sensor5 = "Level";
} else if (packed[i+8] == 6) {data[9][j] = packed[i+9]; sensor6 = "Switch Status";
} else if (packed[i+8] == 7) {data[10][j] = packed[i+9]; sensor7 = "Motion";
} else if (packed[i+8] == 8) {data[11][j] = packed[i+9]; sensor8 = "Azimuth";
} else if (packed[i+8] == 9) {data[12][j] = packed[i+9]; sensor9 = "Elevation";
} else if (packed[i+8] == 10) {data[13][j] = packed[i+9];sensor10 = "Soil Moisture";
} else if (packed[i+8] == 11) {data[14][j] = packed[i+9];sensor11 = "Wind Speed";
} else if (packed[i+8] == 12) {data[15][j] = packed[i+9];sensor12 = "Wind Direction";
} else if (packed[i+8] == 13) {data[16][j] = packed[i+9];sensor13 = "Rainfall";
} else if (packed[i+8] == 14) {data[17][j] = packed[i+9];sensor14 = "Air Quality";}
if (packed[i+10] == 1) {data[4][j] = packed[i+11]; sensor1 = "Temperature";
} else if (packed[i+10] == 2) {data[5][j] = packed[i+11]; sensor2 = "Humidity";
} else if (packed[i+10] == 3) {data[6][j] = packed[i+11] * 4; sensor3 = "Pressure";
} else if (packed[i+10] == 4) {data[7][j] = packed[i+11]; sensor4 = "Light";
} else if (packed[i+10] == 5) {data[8][j] = packed[i+11]; sensor5 = "Level";
} else if (packed[i+10] == 6) {data[9][j] = packed[i+11]; sensor6 = "Switch Status";
} else if (packed[i+10] == 7) {data[10][j] = packed[i+11]; sensor7 = "Motion";
} else if (packed[i+10] == 8) {data[11][j] = packed[i+11]; sensor8 = "Azimuth";
} else if (packed[i+10] == 9) {data[12][j] = packed[i+11]; sensor9 = "Elevation";
} else if (packed[i+10] == 10) {data[13][j] = packed[i+11];sensor10 = "Soil Moisture";
} else if (packed[i+10] == 11) {data[14][j] = packed[i+11];sensor11 = "Wind Speed";
} else if (packed[i+10] == 12) {data[15][j] = packed[i+11];sensor12 = "Wind Direction";
} else if (packed[i+10] == 13) {data[16][j] = packed[i+11];sensor13 = "Rainfall";
} else if (packed[i+10] == 14) {data[17][j] = packed[i+11];sensor14 = "Air Quality";}
}
console.timeEnd("prep");
return data;
}
function makeChart(data) {
console.time("chart");
console.log(data[4][1]); // SensorType1
var title = "Environment data from sensor network";
const opts = {
title: title,
width: 480,
height:600,
// ms: 1,
// cursor: {
// x: false,
// y: false,
// },
series: [
{},
{
label: "Device",
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " ",
stroke: "teal",
width: 1/devicePixelRatio,
},
{
label: "Voltage",
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
stroke: "green",
width: 1/devicePixelRatio,
},
{
label: "RSSI",
scale: "Right",
value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
stroke: "maroon",
width: 1/devicePixelRatio,
},
{
label: sensor1,
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " F",
stroke: "lime",
width: 1/devicePixelRatio,
},
{
label: sensor2,
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " %",
stroke: "olive",
width: 1/devicePixelRatio,
},
{
label: sensor3,
scale: "Right",
value: (u, v) => v == null ? "-" : v.toFixed(2) + " mb",
stroke: "blue",
width: 1/devicePixelRatio,
},
{
label: sensor4,
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " %",
stroke: "orange",
width: 1/devicePixelRatio,
},
{
label: "Level",
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " ",
stroke: "crimson",
width: 1/devicePixelRatio,
},
{
label: "Switch Status",
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
stroke: "deeppink",
width: 1/devicePixelRatio,
},
{
label: "Motion",
scale: "Right",
value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
stroke: "tomato",
width: 1/devicePixelRatio,
},
{
label: "Azimuth",
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
stroke: "yellow",
width: 1/devicePixelRatio,
},
{
label: "Elevation",
scale: "Right",
value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
stroke: "magenta",
width: 1/devicePixelRatio,
},
{
label: "Soil Moisture",
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
stroke: "indigo",
width: 1/devicePixelRatio,
},
{
label: "Wind Speed",
scale: "Right",
value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
stroke: "springgreen",
width: 1/devicePixelRatio,
},
{
label: "Wind Direction",
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
stroke: "cyan",
width: 1/devicePixelRatio,
},
{
label: "Rainfall",
scale: "Right",
value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
stroke: "navy",
width: 1/devicePixelRatio,
},
{
label: "Air Quality",
scale: "Left",
value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
stroke: "brown",
width: 1/devicePixelRatio,
}
],
plugins: [
legendAsTooltipPlugin()
],
axes: [
{},
{
scale: "Left",
values: (u, vals, space) => vals.map(v => +v.toFixed(1) + " "),
},
{
side: 1,
scale: "Right",
size: 60,
values: (u, vals, space) => vals.map(v => +v.toFixed(2) + ""),
grid: {show: false},
},
],
};
let uplot = new uPlot(opts, data, document.body);
Promise.resolve().then(() => {
console.timeEnd("chart");
});
}
fetch("data.json").then(r => r.json()).then(packed => {
let data = prepData(packed);
setTimeout(() => makeChart(data), 0);
});
</script>
</body>
</html>
Sorry I forgot to include data.json sample above.
[12,"Epoch","Location","Voltage","SSID","T1","S1","T2","S2","T3","S3","T4","S4", 1653449039,6,2.30,-48,1,73,2,54,3,252,4,68, 1653449040,16,2.30,-48,2,52,3,242,4,67,5,78, 1653449100,26,2.30,-48,3,241,4,50,5,1,6,0, 1653449160,36,2.30,-46,4,70,5,0,6,0,7,1, 1653449220,46,2.30,-47,5,1,6,1,7,0,8,77, 1653449280,56,2.30,-47,6,73,7,1,8,78,9,44, 1653449280,66,2.30,-48,7,0,8,79,9,45,10,60, 1653449340,76,2.30,-46,8,80,9,46,10,61,11,5, 1653449400,86,2.30,-47,9,47,10,62,11,7,12,23, 1653449400,96,2.30,-47,10,63,11,4,12,24,13,2, 1653449460,106,2.30,-45,11,6,12,23,13,2,14,49]
Thanks.