From cbe33500cfd41c1373261bb6ba9b0d709d4dfeff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Fri, 19 Jun 2026 09:41:16 +0300 Subject: [PATCH 1/3] remove default env values. correct package json with env vars . --- app/Dockerfile | 2 +- app/package.json | 4 ++-- app/src/cloud-ingest.ts | 5 +---- app/src/handlers/edge5-modbus-v2.ts | 4 +--- app/src/http/server.ts | 2 +- app/src/mqtt.ts | 2 +- app/src/stream/edge-chunk-relay.ts | 2 +- 7 files changed, 8 insertions(+), 13 deletions(-) diff --git a/app/Dockerfile b/app/Dockerfile index 1699fc5..723aacb 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -10,4 +10,4 @@ COPY public ./public # Runtime mappers read JSON files from src/mappings, so they must be copied into the image. COPY src ./src -CMD ["npm", "run", "dev"] +CMD ["npm", "run", "start"] diff --git a/app/package.json b/app/package.json index df3f589..3399ecd 100644 --- a/app/package.json +++ b/app/package.json @@ -3,9 +3,9 @@ "private": true, "type": "module", "scripts": { - "dev": "tsx watch --env-file=.env src/index.ts", + "dev": "tsx watch src/index.ts", "build": "tsc", - "start": "node --env-file=.env dist/index.js" + "start": "tsx src/index.ts" }, "dependencies": { "@types/node": "^25.9.1", diff --git a/app/src/cloud-ingest.ts b/app/src/cloud-ingest.ts index 120c53c..abd18bc 100644 --- a/app/src/cloud-ingest.ts +++ b/app/src/cloud-ingest.ts @@ -1,7 +1,4 @@ -const DEFAULT_CLOUD_INGEST_URL = 'http://localhost:3101/ingest' - -export const cloudIngestUrl = - process.env.CLOUD_INGEST_URL ?? DEFAULT_CLOUD_INGEST_URL +export const cloudIngestUrl = process.env.CLOUD_INGEST_URL as string export function postCloudIngest(body: unknown): Promise { return fetch(cloudIngestUrl, { diff --git a/app/src/handlers/edge5-modbus-v2.ts b/app/src/handlers/edge5-modbus-v2.ts index 2167fe4..ee6d67a 100644 --- a/app/src/handlers/edge5-modbus-v2.ts +++ b/app/src/handlers/edge5-modbus-v2.ts @@ -5,9 +5,7 @@ import type { TopicHandler } from '../types.js' export const TOPIC = 'data/edge5/modbus/v2' const mapper = createMetricMapper({ - filePath: - process.env.EDGE5_MODBUS_V2_MAPPING_FILE ?? - new URL('../mappings/edge5-modbus.json', import.meta.url), + filePath: process.env.EDGE5_MODBUS_V2_MAPPING_FILE as string, }) const REGISTER_COUNT = mapper.tagCount diff --git a/app/src/http/server.ts b/app/src/http/server.ts index 42ec1de..edf9d15 100644 --- a/app/src/http/server.ts +++ b/app/src/http/server.ts @@ -3,7 +3,7 @@ import { createServer } from 'node:http' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' -const httpPort = Number(process.env.HTTP_PORT ?? 80) +const httpPort = Number(process.env.HTTP_PORT) const publicDir = join(dirname(fileURLToPath(import.meta.url)), '../../public') const indexPath = join(publicDir, 'index.html') diff --git a/app/src/mqtt.ts b/app/src/mqtt.ts index a178558..994ef29 100644 --- a/app/src/mqtt.ts +++ b/app/src/mqtt.ts @@ -3,7 +3,7 @@ import mqtt from 'mqtt' import { getRegisteredTopics } from './registry.js' import { routeMessage } from './router.js' -const mqttUrl = process.env.MQTT_URL ?? 'mqtt://localhost:1883' +const mqttUrl = process.env.MQTT_URL as string export const mqttClient = mqtt.connect(mqttUrl) diff --git a/app/src/stream/edge-chunk-relay.ts b/app/src/stream/edge-chunk-relay.ts index 040ae55..c61ad32 100644 --- a/app/src/stream/edge-chunk-relay.ts +++ b/app/src/stream/edge-chunk-relay.ts @@ -1,7 +1,7 @@ import type { IncomingMessage } from 'http' import { WebSocket, WebSocketServer } from 'ws' -const wsPort = Number(process.env.WS_PORT ?? 9090) +const wsPort = Number(process.env.WS_PORT) const wss = new WebSocketServer({ port: wsPort, perMessageDeflate: false }) const clients = new Set() From 9b53f602ce7404be13593079a77e60327ea77e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Fri, 19 Jun 2026 10:30:13 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BB?= =?UTF-8?q?=D0=B8=D1=88=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D0=B8.=20=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=B4?= =?UTF-8?q?=D0=B5=D1=84=D0=BE=D0=BB=D1=82=D0=BD=D1=8B=D0=B5=20=D0=B7=D0=BD?= =?UTF-8?q?=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20env=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D1=85.=20=D0=A1=D0=BA?= =?UTF-8?q?=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/.dockerignore | 2 ++ app/Dockerfile | 6 +++-- app/package-lock.json | 38 +++++++++++++++++++++++++--- app/package.json | 10 +++++--- app/src/handlers/edge5-modbus-v2.ts | 13 +--------- app/src/handlers/edge5-modbus.ts | 36 +++----------------------- app/src/helpers/parse.ts | 8 ------ app/src/helpers/validate.ts | 39 ----------------------------- app/src/mapping.ts | 2 -- app/tsconfig.json | 3 +-- 10 files changed, 52 insertions(+), 105 deletions(-) delete mode 100644 app/src/helpers/validate.ts diff --git a/app/.dockerignore b/app/.dockerignore index f06235c..69e11c9 100644 --- a/app/.dockerignore +++ b/app/.dockerignore @@ -1,2 +1,4 @@ node_modules dist +.env +.env.* diff --git a/app/Dockerfile b/app/Dockerfile index 723aacb..a789219 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,13 +1,15 @@ FROM node:22-alpine WORKDIR /app +ENV NODE_ENV=production COPY package.json package-lock.json ./ RUN npm ci COPY tsconfig.json ./ COPY public ./public -# Runtime mappers read JSON files from src/mappings, so they must be copied into the image. COPY src ./src -CMD ["npm", "run", "start"] +RUN npm run build && npm prune --omit=dev + +CMD ["node", "dist/index.js"] diff --git a/app/package-lock.json b/app/package-lock.json index 1d81405..b1b654b 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -6,11 +6,13 @@ "": { "name": "mqtt-worker", "dependencies": { - "@types/node": "^25.9.1", "mqtt": "^5.15.1", - "tsx": "^4.22.4", - "typescript": "^6.0.3", "ws": "^8.21.0" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.22.4", + "typescript": "^6.0.3" } }, "node_modules/@babel/runtime": { @@ -29,6 +31,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -45,6 +48,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -61,6 +65,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -77,6 +82,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -93,6 +99,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -109,6 +116,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -125,6 +133,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -141,6 +150,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -157,6 +167,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -173,6 +184,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -189,6 +201,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -205,6 +218,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -221,6 +235,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -237,6 +252,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -253,6 +269,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -269,6 +286,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -285,6 +303,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -301,6 +320,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -317,6 +337,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -333,6 +354,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -349,6 +371,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -365,6 +388,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -381,6 +405,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -397,6 +422,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -413,6 +439,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -429,6 +456,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -607,6 +635,7 @@ "version": "0.28.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -679,6 +708,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -923,6 +953,7 @@ "version": "4.22.4", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.28.0" @@ -947,6 +978,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/app/package.json b/app/package.json index 3399ecd..6e595dd 100644 --- a/app/package.json +++ b/app/package.json @@ -5,13 +5,15 @@ "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", - "start": "tsx src/index.ts" + "start": "node dist/index.js" }, "dependencies": { - "@types/node": "^25.9.1", "mqtt": "^5.15.1", - "tsx": "^4.22.4", - "typescript": "^6.0.3", "ws": "^8.21.0" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.22.4", + "typescript": "^6.0.3" } } diff --git a/app/src/handlers/edge5-modbus-v2.ts b/app/src/handlers/edge5-modbus-v2.ts index ee6d67a..e5bfaed 100644 --- a/app/src/handlers/edge5-modbus-v2.ts +++ b/app/src/handlers/edge5-modbus-v2.ts @@ -7,20 +7,9 @@ export const TOPIC = 'data/edge5/modbus/v2' const mapper = createMetricMapper({ filePath: process.env.EDGE5_MODBUS_V2_MAPPING_FILE as string, }) -const REGISTER_COUNT = mapper.tagCount function parseValues(payload: Buffer): number[] { - const value = JSON.parse(payload.toString('utf8')) as unknown - - if ( - Array.isArray(value) && - value.length === REGISTER_COUNT && - value.every((item) => typeof item === 'number' && Number.isFinite(item)) - ) { - return value - } - - throw new Error(`Expected JSON number[${REGISTER_COUNT}]`) + return JSON.parse(payload.toString('utf8')) as number[] } async function postMetrics(metrics: MappedMetric[]): Promise { diff --git a/app/src/handlers/edge5-modbus.ts b/app/src/handlers/edge5-modbus.ts index 5416393..7b6b25f 100644 --- a/app/src/handlers/edge5-modbus.ts +++ b/app/src/handlers/edge5-modbus.ts @@ -4,7 +4,6 @@ import type { TopicHandler } from '../types.js' export const TOPIC = 'data/edge5/modbus/v1' const EDGE = 'edge5' -const REGISTER_COUNT = 125 type Edge5ModbusMetric = { edge: string @@ -14,23 +13,7 @@ type Edge5ModbusMetric = { } function parseValues(payload: Buffer): number[] { - let value: unknown - - try { - value = JSON.parse(payload.toString('utf8')) as unknown - } catch { - throw new Error(`Expected JSON number[${REGISTER_COUNT}]`) - } - - if ( - Array.isArray(value) && - value.length === REGISTER_COUNT && - value.every((item) => typeof item === 'number' && Number.isFinite(item)) - ) { - return value - } - - throw new Error(`Expected JSON number[${REGISTER_COUNT}]`) + return JSON.parse(payload.toString('utf8')) as number[] } function toMetrics(values: number[], timestamp: number): Edge5ModbusMetric[] { @@ -47,12 +30,7 @@ async function postMetrics(metrics: Edge5ModbusMetric[]): Promise { if (!response.ok) { const text = await response.text() - console.error('edge5.modbus.post_error', { - status: response.status, - statusText: response.statusText, - body: text.slice(0, 500), - }) - return + throw new Error(text) } console.log('edge5.modbus.posted', { count: metrics.length }) @@ -61,15 +39,7 @@ async function postMetrics(metrics: Edge5ModbusMetric[]): Promise { export const handleEdge5Modbus: TopicHandler = async (topic, payload) => { console.log('edge5.modbus.received', { topic, bytes: payload.length }) - let values: number[] - - try { - values = parseValues(payload) - } catch (err) { - console.warn('edge5.modbus.invalid_payload', { topic, err }) - return - } - + const values = parseValues(payload) const metrics = toMetrics(values, Date.now()) await postMetrics(metrics) } diff --git a/app/src/helpers/parse.ts b/app/src/helpers/parse.ts index b132076..27b05ee 100644 --- a/app/src/helpers/parse.ts +++ b/app/src/helpers/parse.ts @@ -7,14 +7,6 @@ export function parseJson(payload: Buffer): T { return JSON.parse(text) as T } -export function parseJsonArray(payload: Buffer): unknown[] { - const value = parseJson(payload) - if (!Array.isArray(value)) { - throw new Error('Expected JSON array') - } - return value -} - export function decodeBase64(payload: Buffer): Buffer { return Buffer.from(payload.toString('utf8'), 'base64') } diff --git a/app/src/helpers/validate.ts b/app/src/helpers/validate.ts deleted file mode 100644 index f37fffb..0000000 --- a/app/src/helpers/validate.ts +++ /dev/null @@ -1,39 +0,0 @@ -const EXPECTED_CHUNK_LENGTH = 125 - -export function isNonEmptyString(value: unknown): value is string { - return typeof value === 'string' && value.length > 0 -} - -export function isFiniteNumber(value: unknown): value is number { - return typeof value === 'number' && Number.isFinite(value) -} - -export function isArrayOfLength( - value: unknown, - length: number, -): value is unknown[] { - return Array.isArray(value) && value.length === length -} - -export function isArrayOfLength125(value: unknown): value is unknown[] { - return isArrayOfLength(value, EXPECTED_CHUNK_LENGTH) -} - -export function isArrayOfChunks125( - value: unknown, -): value is unknown[][] { - if (!Array.isArray(value)) { - return false - } - return value.every((chunk) => isArrayOfLength125(chunk)) -} - -export function assertValid( - value: unknown, - guard: (v: unknown) => v is T, - label: string, -): asserts value is T { - if (!guard(value)) { - throw new Error(`Invalid payload: ${label}`) - } -} diff --git a/app/src/mapping.ts b/app/src/mapping.ts index 929f98c..cbbb8ca 100644 --- a/app/src/mapping.ts +++ b/app/src/mapping.ts @@ -14,7 +14,6 @@ type MappingConfig = { export type MetricMapper = { edge: string - tagCount: number mapValues: (values: number[], timestamp: number) => MappedMetric[] } @@ -34,7 +33,6 @@ export function createMetricMapper(options: MetricMapperOptions): MetricMapper { return { edge: config.edge, - tagCount: config.tags.length, mapValues: (values, timestamp) => values.map((value, index) => ({ edge: config.edge, diff --git a/app/tsconfig.json b/app/tsconfig.json index a3f07a9..02b90f0 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -7,8 +7,7 @@ "esModuleInterop": true, "skipLibCheck": true, "outDir": "dist", - "rootDir": "src", - "noEmit": true + "rootDir": "src" }, "include": ["src/**/*.ts"] } From d00f79f4d3c15bb0bae8e43276fb18fea44ea73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Fri, 19 Jun 2026 11:10:51 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?public=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8=20=D0=B8=D0=B7=20docker?= =?UTF-8?q?file.=20=D0=92=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=BE=20?= =?UTF-8?q?=D1=82=D0=B5=D0=B3=D0=BE=D0=B2,=20=D0=BF=D1=80=D0=B8=D1=81?= =?UTF-8?q?=D1=8B=D0=BB=D0=B0=D0=B5=D0=BC=D1=8B=D1=85=20=D1=81=20node-red.?= =?UTF-8?q?=20=D0=92=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20=D1=87=D1=82=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20.env=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D0=BE=D0=B9=20=D0=BF=D1=80=D0=B8=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=BC=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BF=D1=83=D1=81=D0=BA=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Dockerfile | 1 - app/package.json | 4 ++-- app/src/handlers/edge5-modbus-v2.ts | 11 ++++++++++- app/src/handlers/edge5-modbus.ts | 11 ++++++++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/Dockerfile b/app/Dockerfile index a789219..17677cc 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -7,7 +7,6 @@ COPY package.json package-lock.json ./ RUN npm ci COPY tsconfig.json ./ -COPY public ./public COPY src ./src RUN npm run build && npm prune --omit=dev diff --git a/app/package.json b/app/package.json index 6e595dd..b3c51b3 100644 --- a/app/package.json +++ b/app/package.json @@ -3,9 +3,9 @@ "private": true, "type": "module", "scripts": { - "dev": "tsx watch src/index.ts", + "dev": "tsx watch --env-file=.env src/index.ts", "build": "tsc", - "start": "node dist/index.js" + "start": "node --env-file=.env dist/index.js" }, "dependencies": { "mqtt": "^5.15.1", diff --git a/app/src/handlers/edge5-modbus-v2.ts b/app/src/handlers/edge5-modbus-v2.ts index e5bfaed..7c09ab5 100644 --- a/app/src/handlers/edge5-modbus-v2.ts +++ b/app/src/handlers/edge5-modbus-v2.ts @@ -4,12 +4,21 @@ import type { TopicHandler } from '../types.js' export const TOPIC = 'data/edge5/modbus/v2' +const REGISTER_COUNT = 125 const mapper = createMetricMapper({ filePath: process.env.EDGE5_MODBUS_V2_MAPPING_FILE as string, }) function parseValues(payload: Buffer): number[] { - return JSON.parse(payload.toString('utf8')) as number[] + const values = JSON.parse(payload.toString('utf8')) as number[] + assertRegisterCount(values) + return values +} + +function assertRegisterCount(values: number[]): void { + if (values.length !== REGISTER_COUNT) { + throw new Error(`Expected ${REGISTER_COUNT} modbus values, got ${values.length}`) + } } async function postMetrics(metrics: MappedMetric[]): Promise { diff --git a/app/src/handlers/edge5-modbus.ts b/app/src/handlers/edge5-modbus.ts index 7b6b25f..59cfefd 100644 --- a/app/src/handlers/edge5-modbus.ts +++ b/app/src/handlers/edge5-modbus.ts @@ -4,6 +4,7 @@ import type { TopicHandler } from '../types.js' export const TOPIC = 'data/edge5/modbus/v1' const EDGE = 'edge5' +const REGISTER_COUNT = 125 type Edge5ModbusMetric = { edge: string @@ -13,7 +14,15 @@ type Edge5ModbusMetric = { } function parseValues(payload: Buffer): number[] { - return JSON.parse(payload.toString('utf8')) as number[] + const values = JSON.parse(payload.toString('utf8')) as number[] + assertRegisterCount(values) + return values +} + +function assertRegisterCount(values: number[]): void { + if (values.length !== REGISTER_COUNT) { + throw new Error(`Expected ${REGISTER_COUNT} modbus values, got ${values.length}`) + } } function toMetrics(values: number[], timestamp: number): Edge5ModbusMetric[] {