mirror of
https://github.com/sergeyyarkov/modbus-logger.git
synced 2026-10-11 02:18:51 +03:00
progress
This commit is contained in:
parent
0252681847
commit
582cbd285e
@ -1,5 +1,6 @@
|
||||
import db from "#root/config/database.config.js";
|
||||
import modbusClient from "#root/config/modbus-client.config.js";
|
||||
import * as utils from '#root/utils/index.js'
|
||||
import { appService, modbusService } from "#root/services/index.js";
|
||||
|
||||
export const modbusController = {
|
||||
@ -70,7 +71,7 @@ export const modbusController = {
|
||||
"g_display_reg_format",
|
||||
"g_display_reg_type",
|
||||
"g_y_label")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[id, name, g_display_reg_addr, g_display_reg_format, g_display_reg_type, g_y_label],
|
||||
);
|
||||
return res.status(200).json({ message: "Device created." });
|
||||
@ -80,13 +81,47 @@ export const modbusController = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove modbus slave device
|
||||
* Update modbus slave device
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
async updateDevice(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
id,
|
||||
newId,
|
||||
name,
|
||||
g_display_reg_addr,
|
||||
g_display_reg_type,
|
||||
g_display_reg_format,
|
||||
g_y_label
|
||||
} = req.body;
|
||||
|
||||
await db.run(`UPDATE "modbus_slaves" SET
|
||||
id = ?,
|
||||
name = ?,
|
||||
g_display_reg_addr = NULL,
|
||||
g_display_reg_type = NULL,
|
||||
g_display_reg_format = NULL,
|
||||
g_y_label = NULL
|
||||
WHERE id = ? `,
|
||||
[newId, name, id]);
|
||||
|
||||
if (g_display_reg_addr !== null &&
|
||||
g_display_reg_type !== null &&
|
||||
g_display_reg_format !== null &&
|
||||
g_y_label !== null
|
||||
) {
|
||||
await db.run(`UPDATE "modbus_slaves" SET
|
||||
g_display_reg_addr = ?,
|
||||
g_display_reg_type = ?,
|
||||
g_display_reg_format = ?,
|
||||
g_y_label = ? WHERE id = ?`,
|
||||
[g_display_reg_addr, g_display_reg_type, g_display_reg_format, g_y_label, id]);
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: 'Updated.' })
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@ -152,12 +187,8 @@ export const modbusController = {
|
||||
*/
|
||||
async status(req, res, next) {
|
||||
try {
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
let intervalId = setInterval(() => {
|
||||
res.write(`event: message\ndata: ${JSON.stringify(modbusClient.isOpen)}\n\n`);
|
||||
}, 500);
|
||||
utils.setSSEHeaders(res);
|
||||
const intervalId = modbusService.startConnectionStatusInterval((status) => res.write(utils.serializeSSEData(status, 'message')))
|
||||
req.on("close", () => clearInterval(intervalId));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -180,38 +211,20 @@ export const modbusController = {
|
||||
const device = await db.get(`SELECT * FROM "modbus_slaves" WHERE id = ?`, [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");
|
||||
utils.setSSEHeaders(res);
|
||||
|
||||
const intervalId = modbusService.startDevicePollInterval(
|
||||
const intervalId = await modbusService.startDevicePollInterval(
|
||||
device,
|
||||
(data, error) => {
|
||||
if (error) {
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
res.write("event: message\n");
|
||||
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||
res.write(utils.serializeSSEData(data, 'message'));
|
||||
},
|
||||
1000,
|
||||
);
|
||||
|
||||
req.on("close", () => clearInterval(intervalId));
|
||||
|
||||
// const streamInterval = setInterval(async () => {
|
||||
// try {
|
||||
// device.display_values = await db.all(`SELECT * FROM "display_values" WHERE slave_id = ?`, [slave_id])
|
||||
// const data = await modbusService.readDataFromDevice(device);
|
||||
// res.write("event: message\n");
|
||||
// res.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||
// } catch (error) {
|
||||
// clearInterval(streamInterval);
|
||||
// next(error);
|
||||
// }
|
||||
// }, 1000);
|
||||
|
||||
// req.on("close", () => clearInterval(streamInterval));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ CREATE TABLE IF NOT EXISTS "app_config" (
|
||||
"mb_rtu_stop_bits" INTEGER NOT NULL DEFAULT 1,
|
||||
"log_interval_ms" INTEGER DEFAULT 1000 NOT NULL,
|
||||
CONSTRAINT "id_unique" CHECK(id=0),
|
||||
CONSTRAINT "min_interval_check" CHECK(log_interval_ms >= 1000)
|
||||
CONSTRAINT "min_interval_check" CHECK(log_interval_ms >= 500)
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
|
||||
@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS "display_values" (
|
||||
"reg_type" VARCHAR(3) NOT NULL CHECK(reg_type IN("HR", "IR", "DI", "DO")) DEFAULT "IR",
|
||||
"reg_format" VARCHAR(5) NOT NULL CHECK(reg_format IN("UI16", "I16", "UI32", "I32", "FP32", "BOOL")) DEFAULT "UI16",
|
||||
PRIMARY KEY("id"),
|
||||
FOREIGN KEY("slave_id") REFERENCES modbus_slaves("id") ON DELETE CASCADE
|
||||
FOREIGN KEY("slave_id") REFERENCES modbus_slaves("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CREATE TABLE IF NOT EXISTS "modbus_log" (
|
||||
|
||||
@ -46,14 +46,14 @@
|
||||
<template x-teleport="body">
|
||||
<div
|
||||
x-data="deviceModal"
|
||||
@open-create-device-modal.window="open()"
|
||||
@open-device-modal.window="open($event)"
|
||||
@keyup.escape.window="close()"
|
||||
class="backdrop z-50"
|
||||
x-show="isOpen"
|
||||
>
|
||||
<div class="modal">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-bold text-lg">Create new device</h2>
|
||||
<h2 x-text="labels.title" class="font-bold text-lg"></h2>
|
||||
<button @click="close()" aria-label="Close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path
|
||||
@ -64,7 +64,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<form @submit.prevent="create(() => graphView = 'none')" action="#">
|
||||
<form @submit.prevent="submit" action="#">
|
||||
<div
|
||||
x-id="['id', 'name', 'graph-display', 'g_display_reg_addr', 'g_display_reg_format', 'g_y_label']"
|
||||
class="flex flex-col space-y-2"
|
||||
@ -167,12 +167,12 @@
|
||||
<button
|
||||
x-bind:disabled="isLoading"
|
||||
x-bind:class="isLoading && 'opacity-70'"
|
||||
x-text="labels.submit"
|
||||
type="submit"
|
||||
class="btn"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
<button @click="close()" class="btn bg-red-500">Cancel</button>
|
||||
<button x-text="labels.cancel" @click="close()" class="btn bg-red-500"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -180,7 +180,10 @@
|
||||
</template>
|
||||
<!-- Create new device -->
|
||||
<button
|
||||
@click="$dispatch('open-create-device-modal')"
|
||||
@click="$dispatch('open-device-modal', {
|
||||
labels: { title: 'Create new device', submit: 'Create', cancel: 'Cancel' },
|
||||
actionType: 'CREATE'
|
||||
})"
|
||||
class="w-full min-h-[50px] text-center border-2 border-dashed rounded-sm hover:bg-gray-50 cursor-pointer"
|
||||
>
|
||||
<span class="text-4xl text-gray-400 select-none">+</span>
|
||||
@ -209,7 +212,13 @@
|
||||
</div>
|
||||
<!-- Device create, delete buttons -->
|
||||
<div>
|
||||
<a href="#">Edit</a>
|
||||
<a
|
||||
@click="$dispatch('open-device-modal', {
|
||||
labels: { title: `Edit device ${selectedDevice?.name}`, submit: 'Save', cancel: 'Cancel' },
|
||||
device: selectedDevice,
|
||||
actionType: 'EDIT'
|
||||
})"
|
||||
href="#">Edit</a>
|
||||
<a
|
||||
@click="$dispatch('dialog', {
|
||||
title: 'Remove device',
|
||||
@ -569,7 +578,8 @@
|
||||
id="log_interval_ms"
|
||||
name="log_interval_ms"
|
||||
type="number"
|
||||
min="1000"
|
||||
min="500"
|
||||
max="10000"
|
||||
x-model.number="$store.app.config.log_interval_ms"
|
||||
required
|
||||
/>
|
||||
|
||||
@ -64,13 +64,20 @@ document.addEventListener("alpine:init", async () => {
|
||||
error: null,
|
||||
graphView: "",
|
||||
data: {
|
||||
oldId: null,
|
||||
id: null,
|
||||
name: "",
|
||||
g_display_reg_addr: null,
|
||||
g_display_reg_addr: 0,
|
||||
g_display_reg_format: "UI16",
|
||||
g_display_reg_type: "IR",
|
||||
g_y_label: "",
|
||||
},
|
||||
labels: {
|
||||
title: '',
|
||||
submit: '',
|
||||
cancel: ''
|
||||
},
|
||||
actionType: 'CREATE',
|
||||
resetDataFields() {
|
||||
this.data.id = null;
|
||||
this.data.name = "";
|
||||
@ -95,7 +102,57 @@ document.addEventListener("alpine:init", async () => {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
open() {
|
||||
async edit() {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
if (this.graphView === 'none') {
|
||||
this.data.g_display_reg_addr = null;
|
||||
this.data.g_display_reg_format = null;
|
||||
this.data.g_display_reg_type = null;
|
||||
this.data.g_y_label = null;
|
||||
}
|
||||
await api.post('/modbus/update_device', utils.dellNullableKeys({ ...this.data, id: this.data.oldId, newId: this.data.id }));
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
console.error(error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
switch (this.actionType) {
|
||||
case 'CREATE':
|
||||
await this.create()
|
||||
this.graphView = 'none'
|
||||
break;
|
||||
case 'EDIT':
|
||||
await this.edit()
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
open(event) {
|
||||
const { detail } = event;
|
||||
this.labels = {
|
||||
title: detail.labels.title,
|
||||
body: detail.labels.body,
|
||||
submit: detail.labels.submit,
|
||||
cancel: detail.labels.cancel
|
||||
};
|
||||
this.actionType = detail.actionType;
|
||||
if (detail.device) {
|
||||
this.data.oldId = detail.device.id;
|
||||
this.data.id = detail.device.id;
|
||||
this.data.name = detail.device.name;
|
||||
this.data.g_display_reg_addr = detail.device.g_display_reg_addr || 0;
|
||||
this.data.g_display_reg_format = detail.device.g_display_reg_format || 'UI16';
|
||||
this.data.g_display_reg_type = detail.device.g_display_reg_type || 'IR';
|
||||
this.data.g_y_label = detail.device.g_y_label || '';
|
||||
if (detail.device.g_display_reg_addr !== null) this.graphView = 'new-value'
|
||||
}
|
||||
this.isOpen = true;
|
||||
},
|
||||
close() {
|
||||
|
||||
@ -147,9 +147,9 @@ export const modbusService = {
|
||||
/**
|
||||
* @param {import("..").ModbusDevice} device
|
||||
* @param {(data: import("..").EventDataStream, error: any) => void} callback
|
||||
* @param {number} intervalMs
|
||||
*/
|
||||
startDevicePollInterval(device, callback, intervalMs) {
|
||||
async startDevicePollInterval(device, callback) {
|
||||
const { log_interval_ms: intervalMs } = await db.get(`SELECT "log_interval_ms" FROM "app_config"`);
|
||||
const poll = async () => {
|
||||
try {
|
||||
device.display_values = await db.all(`SELECT * FROM "display_values" WHERE slave_id = ?`, [device.id]);
|
||||
@ -161,4 +161,11 @@ export const modbusService = {
|
||||
};
|
||||
return setInterval(poll, intervalMs);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {(isOpen: boolean) => void} callback
|
||||
*/
|
||||
startConnectionStatusInterval(callback) {
|
||||
return setInterval(() => callback(modbusClient.isOpen), 500)
|
||||
}
|
||||
};
|
||||
|
||||
17
utils/http.js
Normal file
17
utils/http.js
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
/**
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
export function setSSEHeaders(res) {
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} data
|
||||
* @param {string} eventType
|
||||
*/
|
||||
export function serializeSSEData(data, eventType) {
|
||||
return `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
export * from './number.js'
|
||||
export * from './http.js'
|
||||
@ -11,5 +11,5 @@ export const displayValueSchema = object({
|
||||
|
||||
export const removeDisplayValueSchema = displayValueSchema.pick(['id']);
|
||||
|
||||
// TODO: On creating "reg_format" can be BOOL if "reg_type" is "DI" or "DO"
|
||||
// TODO: "reg_format" can be BOOL if "reg_type" is "DI" or "DO"
|
||||
export const createDisplayValueSchema = displayValueSchema.omit(['id'])
|
||||
@ -14,7 +14,7 @@ export const appConfigSchema = object({
|
||||
mb_rtu_parity: string().oneOf(["none", "even", "odd"]).when("mb_connection_type", requiredIfConnTypeEquals("RTU")),
|
||||
mb_rtu_data_bits: number().oneOf([7, 8, 9]).when("mb_connection_type", requiredIfConnTypeEquals("RTU")),
|
||||
mb_rtu_stop_bits: number().oneOf([1, 2]).when("mb_connection_type", requiredIfConnTypeEquals("RTU")),
|
||||
log_interval_ms: number().min(1000).required(),
|
||||
log_interval_ms: number().min(500).max(10000).required(),
|
||||
});
|
||||
|
||||
export * from './modbus-device.schema.js'
|
||||
|
||||
@ -10,3 +10,6 @@ export const modbusDeviceSchema = object({
|
||||
});
|
||||
|
||||
export const removeModbusDeviceSchema = modbusDeviceSchema.pick(['id']);
|
||||
export const editModbusDeviceSchema = modbusDeviceSchema.shape({
|
||||
newId: number().required()
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user