From 6b9b15970d124f1b923528921bf027ec0d686ff2 Mon Sep 17 00:00:00 2001 From: sergeyyarkov Date: Mon, 30 Oct 2023 21:22:54 +0300 Subject: [PATCH] progress --- controllers/modbus.controller.js | 166 ++++++++++++++++++------------- data.db | Bin 20480 -> 20480 bytes public/js/app.js | 64 +++++++----- 3 files changed, 132 insertions(+), 98 deletions(-) diff --git a/controllers/modbus.controller.js b/controllers/modbus.controller.js index 2847907..8deb5c9 100644 --- a/controllers/modbus.controller.js +++ b/controllers/modbus.controller.js @@ -3,27 +3,27 @@ import modbusClient from "#root/config/modbus-client.config.js"; import { appService } from "#root/services/index.js"; export const modbusController = { - /** - * Connect to modbus network using configuration from database - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next - */ + /** + * Connect to modbus network using configuration from database + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next + */ async connect(req, res, next) { try { const config = await appService.getConfig(db); if (!config) return res.status(400).json({ message: "Application is not configured" }); - + if (modbusClient.isOpen) { modbusClient.close(undefined); modbusClient.destroy(undefined); } - + if (config.mb_connection_type === "TCP") { await modbusClient.connectTCP(config.mb_tcp_ip, { port: config.mb_tcp_port }); return res.status(200).json({ message: "Connected." }); } - + if (config.mb_connection_type === "RTU") { await modbusClient.connectRTU(config.mb_rtu_path, { baudRate: config.mb_rtu_baud, @@ -33,7 +33,7 @@ export const modbusController = { }); return res.status(200).json({ message: "Connected." }); } - + return res.status(400).json({ message: "Connection type is invalid." }); } catch (error) { next(error); @@ -42,16 +42,16 @@ export const modbusController = { /** * Close the modbus connection - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async close(req, res, next) { try { if (!modbusClient.isOpen) return res.status(400).json({ message: "Already closed." }); modbusClient.close(undefined); modbusClient.destroy(undefined); - return res.status(200).json({ message: 'Closed.' }); + return res.status(200).json({ message: "Closed." }); } catch (error) { next(error); } @@ -59,9 +59,9 @@ export const modbusController = { /** * Get list of modbus slave devices - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async list(req, res, next) { try { @@ -81,46 +81,34 @@ export const modbusController = { LEFT JOIN display_values AS dv ON dv.slave_id = ms.id GROUP BY ms.id, ms.name; `); - slaves.forEach(s => (s.display_values = JSON.parse(s.display_values))) + slaves.forEach((s) => (s.display_values = JSON.parse(s.display_values))); return res.status(200).json(slaves); } catch (error) { - next(error) + next(error); } }, /** * Create new modbus slave device - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async createDevice(req, res, next) { try { const { id, name, g_display_reg_addr, g_display_reg_format, g_y_label, is_logging } = req.body; - await db.run(`INSERT INTO "modbus_slaves" ( + await db.run( + `INSERT INTO "modbus_slaves" ( "id", "name", "g_display_reg_addr", "g_display_reg_format", "g_y_label", "is_logging") - VALUES (?, ?, ?, ?, ?, ?)`, - [id, name, g_display_reg_addr, g_display_reg_format, g_y_label, is_logging]); - return res.status(200).json({ message: 'Device created.' }); - } catch (error) { - next(error) - } - }, - - /** - * Remove modbus slave device - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next - */ - async updateDevice(req, res, next) { - try { - + VALUES (?, ?, ?, ?, ?, ?)`, + [id, name, g_display_reg_addr, g_display_reg_format, g_y_label, is_logging], + ); + return res.status(200).json({ message: "Device created." }); } catch (error) { next(error); } @@ -128,16 +116,29 @@ export const modbusController = { /** * Remove modbus slave device - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next + */ + async updateDevice(req, res, next) { + try { + } catch (error) { + next(error); + } + }, + + /** + * Remove modbus slave device + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async removeDevice(req, res, next) { try { const device = await db.get(`SELECT id FROM "modbus_slaves" WHERE id = ?`, [req.body.id]); - if (!device) return res.status(404).json({ message: 'Device not found.' }); + if (!device) return res.status(404).json({ message: "Device not found." }); await db.run(`DELETE FROM "modbus_slaves" WHERE id = ?`, [req.body.id]); - return res.status(200).json({ message: 'Device removed.' }); + return res.status(200).json({ message: "Device removed." }); } catch (error) { next(error); } @@ -145,22 +146,22 @@ export const modbusController = { /** * Create display value for modbus slave device - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async createDisplayValue(req, res, next) {}, /** * Remove display value from modbus slave device - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async removeDisplayValue(req, res, next) { try { await db.run(`DELETE FROM "display_values" WHERE id = ?`, req.body.id); - return res.status(200).json({ message: 'Display value removed.' }); + return res.status(200).json({ message: "Display value removed." }); } catch (error) { next(error); } @@ -168,17 +169,17 @@ export const modbusController = { /** * Get latest data log from modbus slave device - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async getLatestLog(req, res, next) {}, /** * Creates event stream aboud modbus connection status - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async status(req, res, next) { try { @@ -188,7 +189,7 @@ export const modbusController = { let intervalId = setInterval(() => { res.write(`event: message\ndata: ${JSON.stringify(modbusClient.isOpen)}\n\n`); }, 500); - req.on('close', () => clearInterval(intervalId)); + req.on("close", () => clearInterval(intervalId)); } catch (error) { next(error); } @@ -196,27 +197,50 @@ export const modbusController = { /** * Creates event stream for sending data from modbus device - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {import('express').Request} req + * @param {import('express').Response} res + * @param {import('express').NextFunction} next */ async streamData(req, res, next) { try { - if (!modbusClient.isOpen) return res.status(503).json({ message: "Modbus connection closed." }); - + if (!modbusClient.isOpen) return res.status(503).json({ error: { message: "Modbus connection closed." } }); + + if (!req.query.slave_id) return res.status(400).json({ error: { message: "'slave_id' parameter is required." } }); + + const device = await db.get(`SELECT * FROM "modbus_slaves" WHERE id = ?`, [req.query.slave_id]); + + if (!device) return res.status(404).json({ error: { message: "Device not found." } }); + res.setHeader("Cache-Control", "no-cache"); res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Connection", "keep-alive"); - + let interval = setInterval(async () => { - const { data } = await modbusClient.readHoldingRegisters(0, 1); - res.write("event: messsage\n"); - res.write(`data: ${JSON.stringify({ data })}`); - res.write("\n\n"); + let value = 0; + let len = 1; // 16 bits default + + if (device.g_display_reg_format === 32) len = 2; + // ...64, 128, 256... + + const result = await modbusClient.readHoldingRegisters(device.g_display_reg_addr, len); + + switch (device.g_display_reg_format) { + case 32: + value = Buffer.from(result.buffer).readUInt32BE(); + break; + case 16: + value = Buffer.from(result.buffer).readUint16BE(); + break; + default: + break; + } + + res.write("event: message\n"); + res.write(`data: ${JSON.stringify({ graph: { value, format: device.g_display_reg_format } })}\n\n`); }, 1000); req.on("close", () => clearInterval(interval)); } catch (error) { next(error); } - } -} \ No newline at end of file + }, +}; diff --git a/data.db b/data.db index 607d474b58f96415b6a3ccfafcb93af475787175..84155ce5f9d09ad55b3bd3f2ebbdc52283e74b97 100644 GIT binary patch delta 183 zcmZozz}T>Wae_4Cfr&ECj0ZL*ER+}Lf5E`cKZAk)3;!MdbNmPRH}fy$pTR$4v!Fo( z|Ku6^0W1s*3_O$H=u5HOVBo(26h6&w3lvgg;ARk3XX0fNW#nWEaSkxFG}1FPv(Pip zGc+(}{HEHFm!Fr)!Tf?3sFsC)Is^YR{?q&$_^0zXZdOz%;ujJVXJix2@*# delta 183 zcmZozz}T>Wae_4C>WMPWjH@>$ER+{#U|`_jpTWTYh5rTr9sYCt2lzMhFXf-HS^rcwtF!0|23Lod67{G0&#K6rUEYHNtB+AIi6yh9UXk@Nu0EC8& z-&7m&^7B$Tm|yS$&1U6qVBmkof0};-|8)L_&58Z4> { Alpine.store("app", { @@ -56,38 +58,38 @@ document.addEventListener("alpine:init", async () => { }, }); - Alpine.data('deviceModal', () => ({ + Alpine.data("deviceModal", () => ({ isOpen: false, isLoading: false, error: null, data: { id: null, - name: '', + name: "", g_display_reg_addr: null, g_display_reg_format: 16, - g_y_label: '', - is_logging: false + g_y_label: "", + is_logging: false, }, resetDataFields() { this.data.id = null; - this.data.name = ''; + this.data.name = ""; this.data.g_display_reg_addr = null; this.data.g_display_reg_format = 16; - this.data.g_y_label = ''; + this.data.g_y_label = ""; this.data.is_logging = false; }, async create(cb) { try { this.isLoading = true; this.error = null; - await api.post('/modbus/create_device', utils.dellNullableKeys({...this.data})); - graphData = [] // todo: update graph data - this.$dispatch('add-device', {...this.data}); // add device to state + await api.post("/modbus/create_device", utils.dellNullableKeys({ ...this.data })); + graphData = []; // todo: update graph data + this.$dispatch("add-device", { ...this.data }); // add device to state this.resetDataFields(); this.close(); cb && cb(); } catch (error) { - this.error = error + this.error = error; console.error(error); } finally { this.isLoading = false; @@ -99,7 +101,7 @@ document.addEventListener("alpine:init", async () => { close() { this.isOpen = false; }, - })) + })); Alpine.data("monitoringPage", () => ({ selectedDevice: null, @@ -107,18 +109,11 @@ document.addEventListener("alpine:init", async () => { async init() { try { this.$store.app.currentPage = "loading"; - var g = new Dygraph(document.getElementById("div_g"), graphData, { - drawPoints: false, - showRoller: false, - ylabel: "Y Label", - valueRange: [0.0, 1.8], - // labels: ["Time", "Random"], - }); this.devices = await api.get("/modbus/devices"); - window.intervalId = setInterval(() => { - graphData.push([new Date(), Math.random()]); - g.updateOptions({ file: graphData }); - }, 500); + // window.intervalId = setInterval(() => { + // graphData.push([new Date(), Math.random()]); + // g.updateOptions({ file: graphData }); + // }, 500); } catch (error) { this.$store.app.error = error; } finally { @@ -126,6 +121,21 @@ document.addEventListener("alpine:init", async () => { } }, selectDevice(device) { + dataStreamSource?.close(); + dataStreamSource = new EventSource(`/api/modbus/data_stream?slave_id=${device.id}`); + g = new Dygraph(document.getElementById("div_g"), graphData, { + drawPoints: false, + showRoller: false, + ylabel: device.g_y_label, + // valueRange: [0, 1000], + // labels: ["Time", "Random"], + }); + dataStreamSource.onmessage = (e) => { + const data = JSON.parse(e.data); + console.log(data); + graphData.push([new Date(), data.graph.value]); + g.updateOptions({ file: graphData }); + }; if (device.id === this.selectedDevice?.id) return; graphData = []; // todo: reset graph this.selectedDevice = device; @@ -141,12 +151,12 @@ document.addEventListener("alpine:init", async () => { }, async removeDisplayValue(id) { try { - await api.post('/modbus/remove_display-value', { id }); - this.selectedDevice.display_values = this.selectedDevice.display_values.filter(v => v.id != id); + await api.post("/modbus/remove_display-value", { id }); + this.selectedDevice.display_values = this.selectedDevice.display_values.filter((v) => v.id != id); } catch (error) { console.error("Error while removing display value", error); } - } + }, })); Alpine.data("configAppPage", () => ({