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:
@@ -0,0 +1,301 @@
|
|||||||
|
#include <ModbusRTUMaster.h> // https://github.com/CMB27/ModbusRTUMaster/
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
|
||||||
|
#include <Adafruit_SSD1306.h> // https://github.com/adafruit/Adafruit_SSD1306
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
#include <Arduino_JSON.h> // http://github.com/arduino-libraries/Arduino_JSON
|
||||||
|
#include "LittleFS.h"
|
||||||
|
|
||||||
|
const char* ssid = "PWR_METER";
|
||||||
|
const char* password = "123456";
|
||||||
|
|
||||||
|
// Configure Modbus device to 9600 Baud, SERIAL_8N1
|
||||||
|
uint8_t unitId = 8; // Modubus device address
|
||||||
|
|
||||||
|
|
||||||
|
// Define RS485 pins
|
||||||
|
#define TX_PIN 5 // RS485 TX
|
||||||
|
#define RX_PIN 6 // RS485 RX
|
||||||
|
#define DE_PIN 8 // Driver Enable (tie RE & DE together)
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH 128
|
||||||
|
#define SCREEN_HEIGHT 32
|
||||||
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Configure Modbus Interface ---
|
||||||
|
|
||||||
|
// -- Code valid for usage with Arduino Nano ---
|
||||||
|
// #include <SoftwareSerial.h>
|
||||||
|
// SoftwareSerial mySerial(RX_PIN, TX_PIN);
|
||||||
|
// #define MODBUS_SERIAL mySerial
|
||||||
|
|
||||||
|
// --- Code valid for usage with ESP32
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
HardwareSerial MySerial1(1); // Define Serial device mapped to the on of the internal UARTs
|
||||||
|
#define MODBUS_SERIAL MySerial1
|
||||||
|
ModbusRTUMaster modbus(MODBUS_SERIAL, DE_PIN);
|
||||||
|
|
||||||
|
// Webserver
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
JSONVar readings_json;
|
||||||
|
|
||||||
|
|
||||||
|
// --- Configure Peak Hold ---
|
||||||
|
const int peakHoldTime = 500; // Peak hold duration (ms)
|
||||||
|
|
||||||
|
|
||||||
|
// --- Configure low pass filter ---
|
||||||
|
float cutoff_freq = 0.1; // Cutoff frequency in Hz
|
||||||
|
float dt = 0.1; // Sampling interval in seconds
|
||||||
|
|
||||||
|
|
||||||
|
// Other variable definitions
|
||||||
|
float filter_y_prev = 0.0;
|
||||||
|
float RC = 1.0 / (2.0 * M_PI * cutoff_freq);
|
||||||
|
float filter_alpha = dt / (RC + dt);
|
||||||
|
|
||||||
|
uint8_t error = 0;
|
||||||
|
uint16_t value[6]; // Buffer for multiple 16-bit registers
|
||||||
|
uint16_t address = 0x2000;
|
||||||
|
|
||||||
|
float peakValue = 0;
|
||||||
|
int peakIndex = 0; // Stores the highest LED level
|
||||||
|
unsigned long peakTime = 0; // Stores when peak LED was last updated
|
||||||
|
int dt_delay = dt * 1000;
|
||||||
|
|
||||||
|
const uint8_t numErrorStrings = 12;
|
||||||
|
const char* errorStrings[numErrorStrings] = {
|
||||||
|
"Response timeout",
|
||||||
|
"Frame error in response",
|
||||||
|
"CRC error in response",
|
||||||
|
"Unknown communication error",
|
||||||
|
"Unexpected unit ID in response",
|
||||||
|
"Exception response ",
|
||||||
|
"Unexpected function code in response",
|
||||||
|
"Unexpected response length",
|
||||||
|
"Unexpected byte count in response",
|
||||||
|
"Unexpected data address in response",
|
||||||
|
"Unexpected value in response",
|
||||||
|
"Unexpected quantity in response"
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t numExceptionResponseStrings = 4;
|
||||||
|
const char* exceptionResponseStrings[] = { "illegal function", "illegal data address", "illegal data value", "server device failure" };
|
||||||
|
|
||||||
|
union {
|
||||||
|
uint16_t words[2];
|
||||||
|
float value;
|
||||||
|
} U_V;
|
||||||
|
|
||||||
|
union {
|
||||||
|
uint16_t words[2];
|
||||||
|
float value;
|
||||||
|
} I_A;
|
||||||
|
|
||||||
|
union {
|
||||||
|
uint16_t words[2];
|
||||||
|
float value;
|
||||||
|
} P_W;
|
||||||
|
|
||||||
|
|
||||||
|
// Get Sensor Readings and return JSON object
|
||||||
|
String format_readings_json() {
|
||||||
|
readings_json["P_W"] = String(P_W.value, 1);
|
||||||
|
readings_json["U_V"] = String(U_V.value, 1);
|
||||||
|
readings_json["I_A"] = String(I_A.value, 3);
|
||||||
|
String jsonString = JSON.stringify(readings_json);
|
||||||
|
return jsonString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
|
||||||
|
Wire.begin(2, 3);
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("\n\nModbusRTUMasterProbe");
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
|
||||||
|
Serial.println(F("SSD1306 allocation failed"));
|
||||||
|
for (;;)
|
||||||
|
; // Don't proceed, loop forever
|
||||||
|
}
|
||||||
|
Serial.println(F("SSD1306 allocation OK"));
|
||||||
|
|
||||||
|
if (!LittleFS.begin(true)) {
|
||||||
|
Serial.println("An error has occurred while mounting LittleFS");
|
||||||
|
}
|
||||||
|
Serial.println("LittleFS mounted successfully");
|
||||||
|
|
||||||
|
|
||||||
|
MODBUS_SERIAL.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);
|
||||||
|
modbus.begin(9600); // SERIAL_8N1
|
||||||
|
|
||||||
|
// Setup Wifi
|
||||||
|
WiFi.softAP(ssid); //, password);
|
||||||
|
Serial.print("AP IP address: ");
|
||||||
|
Serial.println(WiFi.softAPIP());
|
||||||
|
|
||||||
|
// ----------- Spalsh Screen ---------------
|
||||||
|
display.clearDisplay();
|
||||||
|
display.setRotation(2);
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
display.println("Powermeter");
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(0, 10);
|
||||||
|
display.println("by dustinbrun, 2026");
|
||||||
|
display.setCursor(0, 20);
|
||||||
|
display.println(WiFi.softAPIP());
|
||||||
|
display.display();
|
||||||
|
delay(2000);
|
||||||
|
display.clearDisplay();
|
||||||
|
|
||||||
|
|
||||||
|
// main webpage handler
|
||||||
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(LittleFS, "/index.html", "text/html");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.serveStatic("/", LittleFS, "/");
|
||||||
|
|
||||||
|
// readings webpage handler
|
||||||
|
server.on("/readings", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "application/json", format_readings_json());
|
||||||
|
});
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_display() {
|
||||||
|
display.clearDisplay();
|
||||||
|
|
||||||
|
display.setRotation(2);
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
// Power value
|
||||||
|
display.setCursor(10, 0);
|
||||||
|
if (P_W.value < 10)
|
||||||
|
display.print("000");
|
||||||
|
else if (P_W.value < 100)
|
||||||
|
display.print("00");
|
||||||
|
else if (P_W.value < 1000)
|
||||||
|
display.print("0");
|
||||||
|
display.print(P_W.value, 1);
|
||||||
|
display.setCursor(95, 0);
|
||||||
|
display.print("W");
|
||||||
|
|
||||||
|
// Voltage value
|
||||||
|
display.setCursor(10, 15);
|
||||||
|
if (U_V.value < 10)
|
||||||
|
display.print("0");
|
||||||
|
else if (U_V.value < 10)
|
||||||
|
display.print("00");
|
||||||
|
display.print(U_V.value, 1);
|
||||||
|
display.setCursor(95, 15);
|
||||||
|
display.print("V");
|
||||||
|
|
||||||
|
// Current value
|
||||||
|
display.setRotation(3);
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
display.print(I_A.value, 2);
|
||||||
|
|
||||||
|
// error code (if possible)
|
||||||
|
if (error) {
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(115, 0);
|
||||||
|
display.print("ERR");
|
||||||
|
display.print(error);
|
||||||
|
}
|
||||||
|
//display.setRotation(0);
|
||||||
|
display.setRotation(2);
|
||||||
|
|
||||||
|
// Bar graph display
|
||||||
|
|
||||||
|
// Low pass filtering of the bar graph scale maximum value
|
||||||
|
float new_target = P_W.value * 2; // Target is double of the current value -> stable power value should end up as a line filling half of the screen width
|
||||||
|
filter_y_prev = filter_alpha * new_target + (1 - filter_alpha) * filter_y_prev;
|
||||||
|
// Serial.print(new_target);
|
||||||
|
// Serial.print("\t");
|
||||||
|
// Serial.println(filter_y_prev);
|
||||||
|
|
||||||
|
// map value to screen width
|
||||||
|
int percent = map(P_W.value, 0, int(filter_y_prev), 0, SCREEN_WIDTH);
|
||||||
|
display.fillRect(1, 29, percent, 3, SSD1306_WHITE); // x Top left corner x coordinate, y Top left corner y coordinate, w Width in pixels, h Height in pixels
|
||||||
|
|
||||||
|
// Peak hold and decay
|
||||||
|
if (P_W.value > peakValue) {
|
||||||
|
peakValue = P_W.value;
|
||||||
|
peakTime = millis();
|
||||||
|
} else if (millis() - peakTime > peakHoldTime) {
|
||||||
|
peakValue = max(peakValue - 1, P_W.value);
|
||||||
|
peakTime = millis();
|
||||||
|
}
|
||||||
|
// Draw peak value on bar graph display
|
||||||
|
peakIndex = peakValue * SCREEN_WIDTH / filter_y_prev; // map peak value to current index on display
|
||||||
|
if (peakIndex > SCREEN_WIDTH-2)
|
||||||
|
peakIndex = SCREEN_WIDTH-2;
|
||||||
|
display.fillRect(peakIndex, 30, 2, 1, SSD1306_WHITE);
|
||||||
|
display.fillRect(peakIndex+2, 29, 2, 3, SSD1306_WHITE);
|
||||||
|
|
||||||
|
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
|
||||||
|
// Query modbus device registers
|
||||||
|
error = modbus.readHoldingRegisters(unitId, address, value, 6);
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
|
||||||
|
// 0x2000 = Voltage [V]
|
||||||
|
U_V.words[0] = value[1];
|
||||||
|
U_V.words[1] = value[0];
|
||||||
|
|
||||||
|
// 0x2002 = Current [A]
|
||||||
|
I_A.words[0] = value[3];
|
||||||
|
I_A.words[1] = value[2];
|
||||||
|
|
||||||
|
// 0x2004 = active Power [kW]
|
||||||
|
P_W.words[0] = value[5];
|
||||||
|
P_W.words[1] = value[4];
|
||||||
|
P_W.value *= 1000; // convert to [W]
|
||||||
|
|
||||||
|
// Debug output to console
|
||||||
|
Serial.print(U_V.value, 1);
|
||||||
|
Serial.print("\t");
|
||||||
|
Serial.print(I_A.value, 3);
|
||||||
|
Serial.print("\t");
|
||||||
|
Serial.print(P_W.value, 1);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Serial.print("Error: ");
|
||||||
|
if (error >= 4 && error < (numErrorStrings + 4)) {
|
||||||
|
Serial.print(errorStrings[error - 4]);
|
||||||
|
if (error == MODBUS_RTU_MASTER_EXCEPTION_RESPONSE) {
|
||||||
|
uint8_t exceptionResponse = modbus.getExceptionResponse();
|
||||||
|
Serial.print(exceptionResponse);
|
||||||
|
if (exceptionResponse >= 1 && exceptionResponse <= numExceptionResponseStrings) {
|
||||||
|
Serial.print(" - ");
|
||||||
|
Serial.print(exceptionResponseStrings[exceptionResponse - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else Serial.print("Unknown error");
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_display();
|
||||||
|
|
||||||
|
delay(dt_delay);
|
||||||
|
}
|
||||||
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