mirror of
https://github.com/dustinbrun/ESP32_Modbus_RTU_Power_Meter.git
synced 2026-07-05 09:00:38 +00:00
add software
This commit is contained in:
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<title>Powermeter Dashboard</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="icon" type="image/png" href="icon.png">
|
||||
<script src="chart.js"></script>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="topnav">
|
||||
<h1>Powermeter Dashboard</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<p class="card-title">Power</p>
|
||||
<p class="reading"><span id="P_W"></span> W</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="card-title">Current</p>
|
||||
<p class="reading"><span id="I_A"></span> A</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="card-title">Voltage</p>
|
||||
<p class="reading"><span id="U_V"></span> V</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p class="card-title">Configuration</p>
|
||||
<p>Update Interval: <input type="number" id="interval" min="1" value="500"></p>
|
||||
<p>Plot Diagrams?: <input type="checkbox" id="plot_diagrams" name="plot_diagrams" checked></p>
|
||||
<p>Diagram max. Number of Values: <input type="number" id="num_values" min="1" value="100"></p>
|
||||
<button onclick="button_export()">Export Data</button>
|
||||
<button onclick="button_clear()">Clear Diagrams</button>
|
||||
<button onclick="button_apply()">Apply</button>
|
||||
<p id="output_last_update"></p>
|
||||
<p id="output_online"></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="card-grid_graph">
|
||||
<div class="card">
|
||||
<p class="card-title">Power Graph</p>
|
||||
<canvas id="graph_P_W" width="8" height="6"></canvas>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="card-title">Current Graph</p>
|
||||
<canvas id="graph_I_A" width="8" height="6"></canvas>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="card-title">Voltage Graph</p>
|
||||
<canvas id="graph_U_V" width="8" height="6"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="updater_script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,55 @@
|
||||
html {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.topnav {
|
||||
overflow: hidden;
|
||||
background-color: #0A1128;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-gap: 2rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
.card-grid_graph {
|
||||
max-width: 2000px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-gap: 2rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: white;
|
||||
box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #034078
|
||||
}
|
||||
|
||||
.reading {
|
||||
font-size: 1.2rem;
|
||||
color: #1282A2;
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
|
||||
window.addEventListener('load', button_apply); // Virtually press apply button when webpage loads the first time
|
||||
|
||||
var myObj = 0;
|
||||
var was_error;
|
||||
|
||||
function get_readings() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (this.readyState == 4) { // request finished, response ready
|
||||
if (this.status == 200) {
|
||||
myObj = JSON.parse(this.responseText);
|
||||
was_error = false;
|
||||
console.log("Received JSON");
|
||||
console.log(myObj);
|
||||
}
|
||||
else {
|
||||
was_error = true;
|
||||
console.log("JSON response error or timeout");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open("GET", "/readings", true);
|
||||
xhr.timeout = 500;
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
|
||||
function update_values() {
|
||||
get_readings();
|
||||
|
||||
if (was_error == true) {
|
||||
document.getElementById("output_online").innerText = "Offline";
|
||||
document.getElementById("output_online").style.color = "#FF0000";
|
||||
|
||||
document.getElementById('P_W').innerHTML = "nan";
|
||||
document.getElementById('I_A').innerHTML = "nan";
|
||||
document.getElementById('U_V').innerHTML = "nan";
|
||||
}
|
||||
else {
|
||||
document.getElementById("output_online").innerText = "Online";
|
||||
document.getElementById("output_online").style.color = "#008000";
|
||||
|
||||
document.getElementById('P_W').innerHTML = myObj.P_W;
|
||||
document.getElementById('I_A').innerHTML = myObj.I_A;
|
||||
document.getElementById('U_V').innerHTML = myObj.U_V;
|
||||
|
||||
var timestamp = new Date().toLocaleTimeString();
|
||||
var plot_diagrams = document.getElementById("plot_diagrams");
|
||||
|
||||
if (plot_diagrams.checked == true) {
|
||||
chart_P_W.data.datasets[0].data.push(myObj.P_W);
|
||||
chart_I_A.data.datasets[0].data.push(myObj.I_A);
|
||||
chart_U_V.data.datasets[0].data.push(myObj.U_V);
|
||||
chart_P_W.data.labels.push(timestamp);
|
||||
chart_I_A.data.labels.push(timestamp);
|
||||
chart_U_V.data.labels.push(timestamp);
|
||||
|
||||
if (chart_P_W.data.labels.length >= MAX_DATA_SET_LENGTH) {
|
||||
chart_P_W.data.datasets[0].data.shift();
|
||||
chart_I_A.data.datasets[0].data.shift();
|
||||
chart_U_V.data.datasets[0].data.shift();
|
||||
chart_P_W.data.labels.shift();
|
||||
chart_I_A.data.labels.shift();
|
||||
chart_U_V.data.labels.shift();
|
||||
}
|
||||
|
||||
chart_P_W.update();
|
||||
chart_I_A.update();
|
||||
chart_U_V.update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
document.getElementById("output_last_update").innerText = "Last update: " + new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", fractionalSecondDigits: "3" });
|
||||
|
||||
}
|
||||
|
||||
let intervalId;
|
||||
|
||||
function button_apply() {
|
||||
let mseconds = parseInt(document.getElementById("interval").value);
|
||||
if (isNaN(mseconds) || mseconds < 100) {
|
||||
alert("Please enter a valid number 100ms or larger");
|
||||
return;
|
||||
}
|
||||
clearInterval(intervalId);
|
||||
intervalId = setInterval(update_values, mseconds);
|
||||
|
||||
MAX_DATA_SET_LENGTH = parseInt(document.getElementById("num_values").value);
|
||||
|
||||
}
|
||||
|
||||
function button_clear() {
|
||||
chart_P_W.data.datasets[0].data = [];
|
||||
chart_I_A.data.datasets[0].data = [];
|
||||
chart_U_V.data.datasets[0].data = [];
|
||||
chart_P_W.data.labels = [];
|
||||
chart_I_A.data.labels = [];
|
||||
chart_U_V.data.labels = [];
|
||||
chart_P_W.update();
|
||||
chart_I_A.update();
|
||||
chart_U_V.update();
|
||||
}
|
||||
|
||||
var MAX_DATA_SET_LENGTH = 100;
|
||||
let data_P_W = [];
|
||||
let labels_P_W = [];
|
||||
let labels_I_A = [];
|
||||
let labels_U_V = [];
|
||||
let data_I_A = [];
|
||||
let data_U_V = [];
|
||||
|
||||
var options = {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
type: 'linear',
|
||||
ticks: {
|
||||
maxTicksLimit: 5,
|
||||
},
|
||||
}],
|
||||
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
ticks: {
|
||||
maxTicksLimit: 3,
|
||||
},
|
||||
time: {
|
||||
unit: 'second',
|
||||
displayFormats: {
|
||||
'second': 'HH:mm:ss',
|
||||
},
|
||||
tooltipFormat: 'HH:mm:ss',
|
||||
}
|
||||
}]
|
||||
},
|
||||
scales: {
|
||||
x: { title: { display: true, text: "Time" } },
|
||||
y: { title: { display: false, text: "Value" } }
|
||||
},
|
||||
animation: {
|
||||
duration: 50,
|
||||
},
|
||||
pointRadius: 2,
|
||||
responsive: true,
|
||||
showLines: true
|
||||
};
|
||||
|
||||
|
||||
const graph_P_W = document.getElementById("graph_P_W").getContext("2d");
|
||||
const chart_P_W = new Chart(graph_P_W, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels_P_W,
|
||||
datasets: [{
|
||||
label: "Power [W]",
|
||||
data: data_P_W,
|
||||
borderColor: "blue",
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
}]
|
||||
},
|
||||
options: options
|
||||
});
|
||||
|
||||
const graph_I_A = document.getElementById("graph_I_A").getContext("2d");
|
||||
const chart_I_A = new Chart(graph_I_A, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels_I_A,
|
||||
datasets: [{
|
||||
label: "Current [A]",
|
||||
data: data_I_A,
|
||||
borderColor: "red",
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
}]
|
||||
},
|
||||
options: options
|
||||
});
|
||||
|
||||
const graph_U_V = document.getElementById("graph_U_V").getContext("2d");
|
||||
const chart_U_V = new Chart(graph_U_V, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels_U_V,
|
||||
datasets: [{
|
||||
label: "Voltage [V]",
|
||||
data: data_U_V,
|
||||
borderColor: "green",
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
}]
|
||||
},
|
||||
options: options
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function exportToCSV(filename, data) {
|
||||
|
||||
}
|
||||
|
||||
function button_export() {
|
||||
|
||||
const csvContent = chart_P_W.data.labels + "\n" +
|
||||
chart_P_W.data.datasets[0].data + "\n" +
|
||||
chart_I_A.data.datasets[0].data + "\n" +
|
||||
chart_U_V.data.datasets[0].data;
|
||||
|
||||
const blob = new Blob([csvContent], { type: "text/csv" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "data.csv.txt";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user