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
67ce4abf78
commit
8e1481016e
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"editor.tabSize": 2
|
||||||
|
}
|
||||||
48
README.md
48
README.md
@ -1,47 +1 @@
|
|||||||
```
|
# Modbus Logger App
|
||||||
modbus-logger-app
|
|
||||||
├─ .gitignore
|
|
||||||
├─ .prettierrc
|
|
||||||
├─ app
|
|
||||||
│ └─ index.js
|
|
||||||
├─ config
|
|
||||||
│ ├─ database.config.js
|
|
||||||
│ ├─ env.config.js
|
|
||||||
│ └─ modbus-client.config.js
|
|
||||||
├─ data.db
|
|
||||||
├─ index.d.ts
|
|
||||||
├─ jsconfig.json
|
|
||||||
├─ middlewares
|
|
||||||
│ ├─ index.js
|
|
||||||
│ └─ validate-body.middleware.js
|
|
||||||
├─ nodemon.json
|
|
||||||
├─ package-lock.json
|
|
||||||
├─ package.json
|
|
||||||
├─ public
|
|
||||||
│ ├─ css
|
|
||||||
│ │ ├─ dist.css
|
|
||||||
│ │ ├─ dygraphs.css
|
|
||||||
│ │ └─ main.css
|
|
||||||
│ ├─ index.html
|
|
||||||
│ └─ js
|
|
||||||
│ ├─ alpine.js
|
|
||||||
│ ├─ app.js
|
|
||||||
│ └─ dygraphs.min.js
|
|
||||||
├─ routes
|
|
||||||
│ ├─ api.router.js
|
|
||||||
│ └─ index.js
|
|
||||||
├─ scripts
|
|
||||||
│ └─ db-init.script.js
|
|
||||||
├─ server.js
|
|
||||||
├─ services
|
|
||||||
│ ├─ app.service.js
|
|
||||||
│ └─ index.js
|
|
||||||
├─ sql
|
|
||||||
│ └─ init-schema.sql
|
|
||||||
├─ tailwind.config.js
|
|
||||||
├─ utils
|
|
||||||
│ └─ database.util.js
|
|
||||||
└─ validators
|
|
||||||
└─ index.js
|
|
||||||
|
|
||||||
```
|
|
||||||
102
controllers/app.controller.js
Normal file
102
controllers/app.controller.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import db from "#root/config/database.config.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Services
|
||||||
|
*/
|
||||||
|
import { appService } from "#root/services/index.js";
|
||||||
|
|
||||||
|
export const appController = {
|
||||||
|
/**
|
||||||
|
* Get application config from database
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
|
async getConfig(req, res, next) {
|
||||||
|
try {
|
||||||
|
const data = await appService.getConfig(db);
|
||||||
|
if (!data) return res.status(200).json({ config: null });
|
||||||
|
return res.status(200).json({ config: data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update application config
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
|
async updateConfig(req, res, next) {
|
||||||
|
try {
|
||||||
|
const config = await appService.getConfig(db);
|
||||||
|
const payload = req.body;
|
||||||
|
|
||||||
|
if (!config) await db.run(`INSERT INTO "app_config" (id) VALUES (?)`, [0]);
|
||||||
|
|
||||||
|
/* Update TCP configuration */
|
||||||
|
if (payload.mb_connection_type === "TCP") {
|
||||||
|
const { mb_tcp_ip, mb_tcp_port } = payload;
|
||||||
|
await db.run(
|
||||||
|
`
|
||||||
|
UPDATE "app_config" SET id = 0, mb_connection_type = "TCP", mb_tcp_ip = ?, mb_tcp_port = ?, log_interval_ms = ?
|
||||||
|
`,
|
||||||
|
[mb_tcp_ip, mb_tcp_port, payload.log_interval_ms],
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "Config Updated." });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update RTU configuration */
|
||||||
|
if (payload.mb_connection_type === "RTU") {
|
||||||
|
const { mb_rtu_path, mb_rtu_baud, mb_rtu_parity, mb_rtu_data_bits, mb_rtu_stop_bits } = payload;
|
||||||
|
await db.run(
|
||||||
|
`
|
||||||
|
UPDATE "app_config" SET id = 0,
|
||||||
|
mb_connection_type = "RTU",
|
||||||
|
mb_rtu_path = ?,
|
||||||
|
mb_rtu_baud = ?,
|
||||||
|
mb_rtu_parity = ?,
|
||||||
|
mb_rtu_data_bits = ?,
|
||||||
|
mb_rtu_stop_bits = ?,
|
||||||
|
log_interval_ms = ?
|
||||||
|
`,
|
||||||
|
[mb_rtu_path, mb_rtu_baud, mb_rtu_parity, mb_rtu_data_bits, mb_rtu_stop_bits, payload.log_interval_ms],
|
||||||
|
);
|
||||||
|
return res.status(200).json({ message: "Config updated." });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({ message: "Undefined Modbus connection type." });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the application is configured or not
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
|
async isConfigured(req, res, next) {
|
||||||
|
try {
|
||||||
|
const config = await appService.getConfig(db);
|
||||||
|
if (!config) return res.status(200).json({ is_configured: false });
|
||||||
|
|
||||||
|
for (const colPrefix of ['mb_tcp', 'mb_rtu']) {
|
||||||
|
if (config.mb_connection_type.toLocaleLowerCase() !== colPrefix.substring(3))
|
||||||
|
continue;
|
||||||
|
const keys = Object.keys(config).filter((k) => k.includes(colPrefix));
|
||||||
|
for (const k of keys) {
|
||||||
|
if (!config[k])
|
||||||
|
return res.status(200).json({ is_configured: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ is_configured: true });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
controllers/index.js
Normal file
2
controllers/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './app.controller.js'
|
||||||
|
export * from './modbus.controller.js'
|
||||||
139
controllers/modbus.controller.js
Normal file
139
controllers/modbus.controller.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import db from "#root/config/database.config.js";
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
dataBits: config.mb_rtu_data_bits,
|
||||||
|
parity: config.mb_rtu_parity,
|
||||||
|
stopBits: config.mb_rtu_stop_bits,
|
||||||
|
});
|
||||||
|
return res.status(200).json({ message: "Connected." });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({ message: "Connection type is invalid." });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the modbus connection
|
||||||
|
* @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);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of modbus slave devices
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
|
async list(req, res, next) {
|
||||||
|
try {
|
||||||
|
const slaves = await db.all('SELECT * from modbus_slaves');
|
||||||
|
return res.status(200).json(slaves);
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new modbus slave device
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
|
async createSlaveDevice(req, res, next) {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove modbus slave device
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
|
async removeSlaveDevice(req, res, next) {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create display value for modbus slave device
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
async removeDisplayValue(req, res, next) {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get latest data log from modbus slave device
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
|
async getLatestLog(req, res, next) {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create event stream for sending data from modbus device
|
||||||
|
* @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." });
|
||||||
|
|
||||||
|
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");
|
||||||
|
}, 1000);
|
||||||
|
req.on("close", () => clearInterval(interval));
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
index.d.ts
vendored
7
index.d.ts
vendored
@ -9,3 +9,10 @@ export type AppConfig = {
|
|||||||
mb_rtu_stop_bits: number | null;
|
mb_rtu_stop_bits: number | null;
|
||||||
log_interval_ms: number;
|
log_interval_ms: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ModbusSlave = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
display_addr: number;
|
||||||
|
is_logging: boolean;
|
||||||
|
}
|
||||||
@ -13,7 +13,9 @@ export function validateBodyMiddleware(schema) {
|
|||||||
await schema.validate(req.body);
|
await schema.validate(req.body);
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return res.status(400).send(error.message);
|
return res.status(400).json({ error: {
|
||||||
|
message: error.errors[0]
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
45
migrations/001-init.sql
Normal file
45
migrations/001-init.sql
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Up
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "app_config" (
|
||||||
|
"id" INTEGER,
|
||||||
|
"mb_connection_type" VARCHAR(3) NOT NULL DEFAULT "RTU" CHECK(mb_connection_type IN ('TCP','RTU')),
|
||||||
|
"mb_tcp_ip" VARCHAR(15) NOT NULL DEFAULT "127.0.0.1",
|
||||||
|
"mb_tcp_port" INTEGER NOT NULL DEFAULT 502,
|
||||||
|
"mb_rtu_path" VARCHAR(10) NOT NULL DEFAULT "",
|
||||||
|
"mb_rtu_baud" INTEGER NOT NULL DEFAULT 9600,
|
||||||
|
"mb_rtu_parity" VARCHAR(5) NOT NULL DEFAULT "none" CHECK(mb_rtu_parity IN ("even", "odd", "none")),
|
||||||
|
"mb_rtu_data_bits" INTEGER NOT NULL DEFAULT 8,
|
||||||
|
"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)
|
||||||
|
PRIMARY KEY("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "modbus_slaves" (
|
||||||
|
"id" INTEGER CHECK (id >= 1 AND id <= 255),
|
||||||
|
"name" VARCHAR(256) NOT NULL,
|
||||||
|
"display_addr" INTEGER CHECK(display_addr >= 0 AND display_addr <= 65534),
|
||||||
|
-- "display_val_format" INTEGER CHECK IN(16, 32) NOT NULL,
|
||||||
|
"is_logging" BOOLEAN NOT NULL,
|
||||||
|
PRIMARY KEY("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "display_values" (
|
||||||
|
"id" INTEGER,
|
||||||
|
"name" VARCHAR(32),
|
||||||
|
"slave_id" INTEGER NOT NULL,
|
||||||
|
"reg_addr" INTEGER DEFAULT 0 NOT NULL CHECK(reg_addr >= 0 AND reg_addr <= 65534),
|
||||||
|
PRIMARY KEY("id"),
|
||||||
|
FOREIGN KEY("slave_id") REFERENCES modbus_slaves("id") ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Down
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
DROP TABLE app_config;
|
||||||
|
DROP TABLE modbus_slaves;
|
||||||
|
DROP TABLE display_values;
|
||||||
@ -7,6 +7,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"dev": "nodemon server.js",
|
"dev": "nodemon server.js",
|
||||||
|
"tailwind-build": "tailwindcss -i ./public/css/main.css -o ./public/css/dist.css --watch",
|
||||||
"db:init": "node ./scripts/db-init.script.js"
|
"db:init": "node ./scripts/db-init.script.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
|||||||
@ -616,6 +616,10 @@ video {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mr-1 {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mr-2 {
|
.mr-2 {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
@ -660,6 +664,10 @@ video {
|
|||||||
height: 5rem;
|
height: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-screen {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
.w-1 {
|
.w-1 {
|
||||||
width: 0.25rem;
|
width: 0.25rem;
|
||||||
}
|
}
|
||||||
@ -668,6 +676,34 @@ video {
|
|||||||
width: 5rem;
|
width: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-48 {
|
||||||
|
width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-72 {
|
||||||
|
width: 18rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-24 {
|
||||||
|
width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-16 {
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-12 {
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-64 {
|
||||||
|
width: 16rem;
|
||||||
|
}
|
||||||
|
|
||||||
.max-w-2xl {
|
.max-w-2xl {
|
||||||
max-width: 42rem;
|
max-width: 42rem;
|
||||||
}
|
}
|
||||||
@ -676,6 +712,10 @@ video {
|
|||||||
max-width: 72rem;
|
max-width: 72rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-auto {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.transform {
|
.transform {
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
@ -710,6 +750,12 @@ video {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-x-reverse: 0;
|
||||||
|
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
||||||
|
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
|
}
|
||||||
|
|
||||||
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
|
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
|
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
@ -724,6 +770,14 @@ video {
|
|||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-l {
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-r {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.fill-blue-600 {
|
.fill-blue-600 {
|
||||||
fill: #2563eb;
|
fill: #2563eb;
|
||||||
}
|
}
|
||||||
@ -752,6 +806,26 @@ video {
|
|||||||
padding: 20rem;
|
padding: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.px-2 {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-2\.5 {
|
||||||
|
padding-left: 0.625rem;
|
||||||
|
padding-right: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-1 {
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-1\.5 {
|
||||||
|
padding-top: 0.375rem;
|
||||||
|
padding-bottom: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.py-8 {
|
.py-8 {
|
||||||
padding-top: 2rem;
|
padding-top: 2rem;
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
@ -761,10 +835,6 @@ video {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-middle {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-2xl {
|
.text-2xl {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
@ -775,6 +845,11 @@ video {
|
|||||||
line-height: 2.25rem;
|
line-height: 2.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.font-bold {
|
.font-bold {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,28 +8,42 @@
|
|||||||
<script defer type="module" src="/js/app.js"></script>
|
<script defer type="module" src="/js/app.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="m-2">
|
<body class="m-2">
|
||||||
|
<!-- <div class="border p-4" x-data>
|
||||||
|
<span>app state:</span>
|
||||||
|
<pre x-text="JSON.stringify($store.app, undefined, 2)"></pre>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
<!-- Root element -->
|
<!-- Root element -->
|
||||||
<div x-data x-cloak class="mx-auto max-w-6xl m-2 py-8">
|
<div x-data x-cloak class="mx-auto max-w-6xl m-2 py-8">
|
||||||
<!-- Monitoring content -->
|
<!-- Monitoring content -->
|
||||||
<main x-bind:hidden="$store.app.currentPage !== 'monitoring'">
|
<main x-bind:hidden="$store.app.currentPage !== 'monitoring'">
|
||||||
<div>
|
<!-- <div>
|
||||||
<h1 class="text-3xl font-bold">Monitoring page</h1>
|
<h1 class="text-3xl font-bold">Monitoring page</h1>
|
||||||
<button @click="$store.app.currentPage = 'configuration'" class="btn">Config</button>
|
<button @click="$store.app.setPage('configuration')" class="btn">Config</button>
|
||||||
|
</div> -->
|
||||||
|
<div class="flex flex-auto flex-row h-screen space-x-2">
|
||||||
|
<div class="border-r w-64 p-4">
|
||||||
|
<div class="w-full border px-2.5 py-1.5 rounded-sm">
|
||||||
|
<h2 class="text-sm">Modbus slave #1</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
graph...
|
||||||
|
</div>
|
||||||
|
<div class="border-l w-48 p-4">
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Configuration content -->
|
<!-- Configuration content -->
|
||||||
<main x-data="configApp" x-bind:hidden="$store.app.currentPage !== 'configuration'">
|
<main x-data="configAppPage" x-bind:hidden="$store.app.currentPage !== 'configuration'">
|
||||||
<div class="border rounded-sm p-4 max-w-2xl mx-auto">
|
<div class="border rounded-sm p-4 max-w-2xl mx-auto">
|
||||||
<h2 class="text-2xl font-bold">Setup configuration</h2>
|
<h2 class="text-2xl font-bold">Setup configuration</h2>
|
||||||
<p class="text-gray-700 italic">
|
<p class="text-gray-700 italic">
|
||||||
Fill in the required configuration settings to connect to the Modbus network.
|
Fill in the required configuration settings to connect to the Modbus network.
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<!-- <div>
|
|
||||||
<span>app config state:</span>
|
|
||||||
<pre x-text="JSON.stringify($store.app.config, undefined, 2)"></pre>
|
|
||||||
</div> -->
|
|
||||||
<form @submit.prevent="configurateApp()" action="#">
|
<form @submit.prevent="configurateApp()" action="#">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
@ -96,7 +110,7 @@
|
|||||||
<option value="57600">57600</option>
|
<option value="57600">57600</option>
|
||||||
<option value="38400">38400</option>
|
<option value="38400">38400</option>
|
||||||
<option value="19200">19200</option>
|
<option value="19200">19200</option>
|
||||||
<option selected value="9600">9600</option>
|
<option value="9600">9600</option>
|
||||||
<option value="1200">1200</option>
|
<option value="1200">1200</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -123,7 +137,7 @@
|
|||||||
x-model.number="$store.app.config.mb_rtu_data_bits"
|
x-model.number="$store.app.config.mb_rtu_data_bits"
|
||||||
x-bind:required="$store.app.config.mb_connection_type === 'RTU'"
|
x-bind:required="$store.app.config.mb_connection_type === 'RTU'"
|
||||||
>
|
>
|
||||||
<option selected value="8">8</option>
|
<option value="8">8</option>
|
||||||
<option value="7">7</option>
|
<option value="7">7</option>
|
||||||
<option value="6">6</option>
|
<option value="6">6</option>
|
||||||
<option value="5">5</option>
|
<option value="5">5</option>
|
||||||
@ -158,7 +172,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 flex flex-row justify-between items-center">
|
<div class="mt-3 flex flex-row justify-between items-center">
|
||||||
<div>
|
<div class="mr-1">
|
||||||
<button
|
<button
|
||||||
x-bind:disabled="isLoading"
|
x-bind:disabled="isLoading"
|
||||||
x-bind:class="isLoading && 'opacity-70'"
|
x-bind:class="isLoading && 'opacity-70'"
|
||||||
@ -169,7 +183,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-red-600" x-text="errorMessage">Error while connection to Modbus network</span>
|
<span class="text-red-600" x-text="errorMessage"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -200,6 +214,26 @@
|
|||||||
<p class="mt-4">Loading...</p>
|
<p class="mt-4">Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- Error content -->
|
||||||
|
<main x-bind:hidden="$store.app.currentPage !== 'error'">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-2xl font-bold">Something went wrong.</h1>
|
||||||
|
<p>Refresh page to try again.</p>
|
||||||
|
<br />
|
||||||
|
<div class="border p-4">
|
||||||
|
<pre x-text="JSON.stringify($store.app.error, undefined, 2)" x-show="$store.app.error">
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- 404 content -->
|
||||||
|
<main x-bind:hidden="$store.app.currentPage !== '404'">
|
||||||
|
<div>
|
||||||
|
<h1 class="text 2xl font-bold">Page not found</h1>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import "./alpine.js";
|
import "./alpine.js";
|
||||||
import api from "./api.js";
|
import api from "./api.js";
|
||||||
|
import * as utils from './utils/index.js'
|
||||||
|
|
||||||
|
const pages = ['loading', 'monitoring', 'configuration', '404']
|
||||||
|
|
||||||
document.addEventListener("alpine:init", async () => {
|
document.addEventListener("alpine:init", async () => {
|
||||||
Alpine.store("app", {
|
Alpine.store("app", {
|
||||||
is_configured: false,
|
is_configured: false,
|
||||||
/**
|
|
||||||
* Default configuration
|
|
||||||
*/
|
|
||||||
config: {
|
config: {
|
||||||
mb_connection_type: "TCP",
|
mb_connection_type: "TCP",
|
||||||
mb_tcp_ip: "127.0.0.1",
|
mb_tcp_ip: "127.0.0.1",
|
||||||
@ -20,6 +20,8 @@ document.addEventListener("alpine:init", async () => {
|
|||||||
log_interval_ms: 1000,
|
log_interval_ms: 1000,
|
||||||
},
|
},
|
||||||
currentPage: "loading",
|
currentPage: "loading",
|
||||||
|
error: null,
|
||||||
|
devices: [],
|
||||||
async init() {
|
async init() {
|
||||||
try {
|
try {
|
||||||
const { config } = await api.get("/app/config");
|
const { config } = await api.get("/app/config");
|
||||||
@ -29,70 +31,50 @@ document.addEventListener("alpine:init", async () => {
|
|||||||
|
|
||||||
if (config) this.config = config;
|
if (config) this.config = config;
|
||||||
if (is_configured) {
|
if (is_configured) {
|
||||||
|
const devices = await api.get('/modbus/devices');
|
||||||
|
this.devices = devices;
|
||||||
this.currentPage = "monitoring";
|
this.currentPage = "monitoring";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentPage = "configuration";
|
this.currentPage = "configuration";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error("Error on initializing app.");
|
this.currentPage = 'error'
|
||||||
|
this.error = error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setPage(page) {
|
||||||
|
if (!pages.includes(page)) {
|
||||||
|
this.currentPage = '404'
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentPage = page;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Alpine.data("configApp", () => ({
|
Alpine.data("configAppPage", () => ({
|
||||||
selectedConnectionType: "TCP",
|
selectedConnectionType: "TCP",
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
async configurateApp() {
|
async configurateApp() {
|
||||||
this.isLoading = true;
|
try {
|
||||||
this.errorMessage = "";
|
this.isLoading = true;
|
||||||
let data = {};
|
this.errorMessage = "";
|
||||||
const {
|
const data = { ...this.$store.app.config };
|
||||||
mb_connection_type,
|
const body = {
|
||||||
mb_tcp_ip,
|
RTU: utils.objKeysExclude(data, ['mb_tcp_ip', 'mb_tcp_port']),
|
||||||
mb_tcp_port,
|
TCP: utils.objKeysExclude(data, ['mb_rtu_path', 'mb_rtu_baud', 'mb_rtu_parity', 'mb_rtu_data_bits', 'mb_rtu_stop_bits'])
|
||||||
mb_rtu_path,
|
|
||||||
mb_rtu_baud,
|
|
||||||
mb_rtu_parity,
|
|
||||||
mb_rtu_data_bits,
|
|
||||||
mb_rtu_stop_bits,
|
|
||||||
log_interval_ms,
|
|
||||||
} = this.$store.app.config;
|
|
||||||
|
|
||||||
if (mb_connection_type === "TCP") {
|
|
||||||
data = {
|
|
||||||
mb_connection_type,
|
|
||||||
mb_tcp_ip,
|
|
||||||
mb_tcp_port,
|
|
||||||
log_interval_ms,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mb_connection_type === "RTU") {
|
|
||||||
data = {
|
|
||||||
mb_connection_type,
|
|
||||||
mb_rtu_path,
|
|
||||||
mb_rtu_parity,
|
|
||||||
mb_rtu_data_bits,
|
|
||||||
mb_rtu_stop_bits,
|
|
||||||
mb_rtu_baud,
|
|
||||||
log_interval_ms,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
await api.patch("/app/config", data);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await api.post("/modbus/connect");
|
|
||||||
this.$store.app.currentPage = "monitoring";
|
|
||||||
} catch (error) {
|
|
||||||
this.errorMessage = `Modbus connection error: ${error.message}`;
|
|
||||||
}
|
}
|
||||||
|
await api.patch("/app/config", body[data.mb_connection_type]);
|
||||||
|
await api.post("/modbus/connect");
|
||||||
|
this.$store.app.currentPage = "monitoring";
|
||||||
|
} catch (error) {
|
||||||
|
this.errorMessage = `${error.message}`;
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isLoading = false;
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|||||||
1
public/js/utils/index.js
Normal file
1
public/js/utils/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './object.js'
|
||||||
11
public/js/utils/object.js
Normal file
11
public/js/utils/object.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Deletes specified keys from object
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string[]} keys
|
||||||
|
* @returns new object
|
||||||
|
*/
|
||||||
|
export function objKeysExclude(obj, keys) {
|
||||||
|
const res = structuredClone(obj);
|
||||||
|
Object.keys(res).forEach(k => keys.includes(k) && delete res[k]);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import db from "#root/config/database.config.js";
|
|
||||||
import modbusClient from "#root/config/modbus-client.config.js";
|
import modbusClient from "#root/config/modbus-client.config.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8,9 +7,9 @@ import modbusClient from "#root/config/modbus-client.config.js";
|
|||||||
import { appConfigSchema } from "#root/validators/index.js";
|
import { appConfigSchema } from "#root/validators/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Services
|
* Controllers
|
||||||
*/
|
*/
|
||||||
import { appService } from "#root/services/index.js";
|
import { appController, modbusController } from '#root/controllers/index.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middlewares
|
* Middlewares
|
||||||
@ -19,154 +18,13 @@ import { validateBodyMiddleware } from "#root/middlewares/index.js";
|
|||||||
|
|
||||||
const apiRouter = express.Router();
|
const apiRouter = express.Router();
|
||||||
|
|
||||||
/**
|
apiRouter.get("/app/config", appController.getConfig);
|
||||||
* Get application config
|
apiRouter.patch("/app/config", validateBodyMiddleware(appConfigSchema), appController.updateConfig);
|
||||||
*/
|
apiRouter.get("/app/is_configured", appController.isConfigured);
|
||||||
apiRouter.get("/app/config", async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const data = await appService.getConfig(db);
|
|
||||||
if (!data) return res.status(200).json({ config: null });
|
|
||||||
return res.status(200).json({ config: data });
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
apiRouter.get('/modbus/devices', modbusController.list);
|
||||||
* Update app configuration
|
apiRouter.post("/modbus/connect", modbusController.connect);
|
||||||
*/
|
apiRouter.post("/modbus/close", modbusController.close);
|
||||||
apiRouter.patch("/app/config", validateBodyMiddleware(appConfigSchema), async (req, res, next) => {
|
apiRouter.get("/modbus/data_stream", modbusController.streamData);
|
||||||
try {
|
|
||||||
const config = await appService.getConfig(db);
|
|
||||||
const payload = req.body;
|
|
||||||
|
|
||||||
if (!config) await db.run(`INSERT INTO "app_config" (id) VALUES (?)`, [0]);
|
|
||||||
|
|
||||||
/* Update TCP configuration */
|
|
||||||
if (payload.mb_connection_type === "TCP") {
|
|
||||||
const { mb_tcp_ip, mb_tcp_port } = payload;
|
|
||||||
await db.run(
|
|
||||||
`
|
|
||||||
UPDATE "app_config" SET id = 0, mb_connection_type = "TCP", mb_tcp_ip = ?, mb_tcp_port = ?, log_interval_ms = ?
|
|
||||||
`,
|
|
||||||
[mb_tcp_ip, mb_tcp_port, payload.log_interval_ms],
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({ message: "Config Updated." });
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update RTU configuration */
|
|
||||||
if (payload.mb_connection_type === "RTU") {
|
|
||||||
const { mb_rtu_path, mb_rtu_baud, mb_rtu_parity, mb_rtu_data_bits, mb_rtu_stop_bits } = payload;
|
|
||||||
await db.run(
|
|
||||||
`
|
|
||||||
UPDATE "app_config" SET id = 0,
|
|
||||||
mb_connection_type = "RTU",
|
|
||||||
mb_rtu_path = ?,
|
|
||||||
mb_rtu_baud = ?,
|
|
||||||
mb_rtu_parity = ?,
|
|
||||||
mb_rtu_data_bits = ?,
|
|
||||||
mb_rtu_stop_bits = ?,
|
|
||||||
log_interval_ms = ?
|
|
||||||
`,
|
|
||||||
[mb_rtu_path, mb_rtu_baud, mb_rtu_parity, mb_rtu_data_bits, mb_rtu_stop_bits, payload.log_interval_ms],
|
|
||||||
);
|
|
||||||
return res.status(200).json({ message: "Config updated." });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(400).json({ message: "Undefined Modbus connection type." });
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the application is configured or not
|
|
||||||
*/
|
|
||||||
apiRouter.get("/app/is_configured", async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const config = await appService.getConfig(db);
|
|
||||||
let is_configured = false;
|
|
||||||
|
|
||||||
if (!config) return res.status(200).json({ is_configured: false });
|
|
||||||
|
|
||||||
const isValuesExists = (colPrefix) => {
|
|
||||||
const keys = Object.keys(config).filter((k) => k.includes(colPrefix));
|
|
||||||
for (const k of keys) if (!config[k]) return false;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (config.mb_connection_type === "TCP") is_configured = isValuesExists("mb_tcp");
|
|
||||||
if (config.mb_connection_type === "RTU") is_configured = isValuesExists("mb_rtu");
|
|
||||||
|
|
||||||
return res.status(200).json({ is_configured });
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to modbus network using configuration from database
|
|
||||||
*/
|
|
||||||
apiRouter.post("/modbus/connect", async (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,
|
|
||||||
dataBits: config.mb_rtu_data_bits,
|
|
||||||
parity: config.mb_rtu_parity,
|
|
||||||
stopBits: config.mb_rtu_stop_bits,
|
|
||||||
});
|
|
||||||
return res.status(200).json({ message: "Connected." });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(400).json({ message: "Connection type is invalid." });
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
apiRouter.post("/modbus/close", (req, res, next) => {
|
|
||||||
try {
|
|
||||||
if (!modbusClient.isOpen) return res.status(400).json({ message: "Already closed." });
|
|
||||||
modbusClient.close(undefined);
|
|
||||||
modbusClient.destroy(undefined);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
apiRouter.get("/modbus/data_stream", async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
if (!modbusClient.isOpen) return res.status(503).json({ message: "Modbus connection closed." });
|
|
||||||
|
|
||||||
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");
|
|
||||||
}, 1000);
|
|
||||||
req.on("close", () => clearInterval(interval));
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export { apiRouter };
|
export { apiRouter };
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import { runQueryFromFile } from "#root/utils/database.util.js";
|
import db from '#root/config/database.config.js'
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
await runQueryFromFile("sql/init-schema.sql");
|
try {
|
||||||
|
await db.migrate({ force: false })
|
||||||
|
console.log('Database initialized.');
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import app from "#root/app/index.js";
|
import app from "#root/app/index.js";
|
||||||
import env from "#root/config/env.config.js";
|
import env from "#root/config/env.config.js";
|
||||||
|
|
||||||
app.listen(env.SERVER_PORT, () => console.log(`Web server started: http://127.0.0.1:${env.SERVER_PORT}.`));
|
app.listen(env.SERVER_PORT, () => console.log(`Web server started: http://localhost:${env.SERVER_PORT}.`));
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "app_config" (
|
|
||||||
"id" INTEGER,
|
|
||||||
"mb_connection_type" VARCHAR(3) NOT NULL DEFAULT "RTU" CHECK(mb_connection_type IN ('TCP','RTU')),
|
|
||||||
"mb_tcp_ip" VARCHAR(15) NOT NULL DEFAULT "127.0.0.1",
|
|
||||||
"mb_tcp_port" INTEGER NOT NULL DEFAULT 502,
|
|
||||||
"mb_rtu_path" VARCHAR(10) NOT NULL DEFAULT "",
|
|
||||||
"mb_rtu_baud" INTEGER NOT NULL DEFAULT 9600,
|
|
||||||
"mb_rtu_parity" VARCHAR(5) NOT NULL DEFAULT "none" CHECK(mb_rtu_parity IN ("even", "odd", "none")),
|
|
||||||
"mb_rtu_data_bits" INTEGER NOT NULL DEFAULT 8,
|
|
||||||
"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)
|
|
||||||
PRIMARY KEY("id")
|
|
||||||
);
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import fs from "fs/promises";
|
|
||||||
import db from "#root/config/database.config.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the `.sql` file and executes the query
|
|
||||||
* @param {string} filepath
|
|
||||||
*/
|
|
||||||
export async function runQueryFromFile(filepath) {
|
|
||||||
try {
|
|
||||||
const query = await fs.readFile(filepath);
|
|
||||||
await db.run(query.toString());
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user