poe-svelte-ui-lib 1.71.0 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/protocols/crc.js +1 -1
- package/dist/protocols/dictionary.yaml +2 -0
- package/dist/protocols/dictionaryCodes.js +2 -0
- package/dist/protocols/index.d.ts +2 -2
- package/dist/protocols/index.js +4 -4
- package/dist/protocols/plotGroups.d.ts +4 -4
- package/dist/protocols/plotGroups.js +36 -26
- package/dist/protocols/xPoeDebug.d.ts +4 -10
- package/dist/protocols/xPoeDebug.js +99 -84
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ npm install poe-svelte-ui-lib
|
|
|
61
61
|
|
|
62
62
|
**Битовые поля конфигурации** — `bitFieldGet(word, start, end)`/`bitFieldSet(word, start, end, value)`: поле `start..end` 32-битного слова `{GROUP}.Cfg` (Select/Input с `bitMode`) обычной арифметикой, без знаковых 32-битных операций JS — бит 31 разбирается и собирается корректно, результат всегда 0..2³²−1; тест против эталона на BigInt — `tests/bitField.test.mjs`.
|
|
63
63
|
|
|
64
|
-
**Ядро протоколов** — `poe-svelte-ui-lib/protocols` (без Svelte, работает и под Node без Vite): общий для DevCloud, WebApp и SYP-DevTerminal CBOR-кодек (RFC 8949) — `encodeCbor`/`decodeCbor`, `CborEncoder`/`CborDecoder`; `valueToCbor`/`cborToValue` — обычные значения, двоичные данные как `Uint8Array` (WebSocket, хранилище, телеметрия); `jsonToCbor`/`cborToJson` — JSON-совместимые значения, двоичные данные в base64, строки под ключами с `BIN` — байты (поля ввода терминала и отладки). Пакет `poe_cbor_pkt` — indefinite-массив объектов `[[TrgBusID, SrcBusID, Cmd, Payload, CRC16], …]` (CAN, RS485): `encodePacket`/`decodePacket` (разбор с начала буфера — `complete` с длиной, `incomplete` для потока или `garbage`; объекты одному адресату не склеиваются, порядок сохраняется, неверный CRC — флаг `crcOk`), `objectsFor` (свой адрес и broadcast `BROADCAST_BUS_ID = 0xFFFF`), `EXTERNAL_BUS_ID = 0x0000` — `SrcBusID` терминала и отладки, `makeCmd(header, argument, reply = true)`/`splitCmd` — `Cmd`: заголовок в битах 14-15, аргумент в битах 1-13 (0..8191), флаг ответа в бите 0 (как `CMD`/`CMD_NR` прошивки), `packetObjectCrc`; контрольные суммы `crc16Kermit` и `crc8Rohc` — строго побайтные, как в `common.c` прошивок. Байты совпадают с `poe_cbor_pkt.c` прошивки — проверка `npm run TestProtocols` (`tests/protocols.test.mjs`, эталонные пакеты собраны прошивочным кодом).
|
|
64
|
+
**Ядро протоколов** — `poe-svelte-ui-lib/protocols` (без Svelte, работает и под Node без Vite): общий для DevCloud, WebApp и SYP-DevTerminal CBOR-кодек (RFC 8949) — `encodeCbor`/`decodeCbor`, `CborEncoder`/`CborDecoder`; `valueToCbor`/`cborToValue` — обычные значения, двоичные данные как `Uint8Array` (WebSocket, хранилище, телеметрия); `jsonToCbor`/`cborToJson` — JSON-совместимые значения, двоичные данные в base64, строки под ключами с `BIN` — байты (поля ввода терминала и отладки). Пакет `poe_cbor_pkt` — indefinite-массив объектов `[[TrgBusID, SrcBusID, Cmd, Payload, CRC16], …]` (CAN, RS485): `encodePacket`/`decodePacket` (разбор с начала буфера — `complete` с длиной, `incomplete` для потока или `garbage`; объекты одному адресату не склеиваются, порядок сохраняется, неверный CRC — флаг `crcOk`), `objectsFor` (свой адрес и broadcast `BROADCAST_BUS_ID = 0xFFFF`), `EXTERNAL_BUS_ID = 0x0000` — `SrcBusID` терминала и отладки, `makeCmd(header, argument, reply = true)`/`splitCmd` — `Cmd`: заголовок в битах 14-15, аргумент в битах 1-13 (0..8191), флаг ответа в бите 0 (как `CMD`/`CMD_NR` прошивки), `packetObjectCrc`; контрольные суммы `crc16Kermit` и `crc8Rohc` — строго побайтные, как в `common.c` прошивок. Байты совпадают с `poe_cbor_pkt.c` прошивки — проверка `npm run TestProtocols` (`tests/protocols.test.mjs`, эталонные пакеты собраны прошивочным кодом). Протокол xPOE-Debug (UART-консоль `system_console`): definite CBOR-массив `[SrcID, HdrArg, Value, FHS, CRC16]`; `HdrArg` — общее 16-битное `cmd_t.word`, `Value` — один CBOR item, FHS — свободная куча (или `null` в запросе), CRC-16/KERMIT покрывает сырые CBOR-байты первых четырёх элементов. `encodeXPoeDebugFrame` кодирует запрос, `createXPoeDebugParser().push(байты)` разбирает поток кадров и внешний текст (`raw`). CAN/CAN FD через slcan-адаптер (`can_service` прошивки): `canFragments`/`encodeCanPacket` — нарезка пакета на кадры как `CAN_Send` (бит 10 ID — «последний», биты 0..9 — `packetId`, по 8 / 64 байта), `encodeSlcanFrame`, `createCanParser().push(байты)` — разбор кадров slcan со сборкой фрагментов и обрезкой паддинга CAN FD. RS485 — сырой поток пакетов: `createPacketStreamParser().push(байты)` (пауза обрывает недособранное, мусор пропускается по байту). Открытие slcan-адаптера — `slcanOpenCommands(скорость, скоростьДанных?)`. Поле Value команды xPOE-Debug из ввода — `xPoeDebugValueFromText` (hex как есть, JSON → CBOR). Графики отладки — кадр `OK! DebPlot` (`SC_PLOT` прошивки, `DEB_PLOT_ARGUMENT`): `debPlotValues(value)` — пары `[имя, число]` из hex CBOR-карты `VALUE`, `createPlotGroups().update(пары)` раскладывает по группам (префикс до точки) в порядке карты и возвращает обновлённые группы, `data(группа)` — последние значения. Словарь протокола — `HEADER_CODES`, `DEFAULT_ARGUMENT_CODES` (генерируется из мастер-словаря `src/lib/protocols/dictionary.yaml` — `npm run Dictionary`, перед упаковкой автоматически; сам файл поставляется как `poe-svelte-ui-lib/protocols/dictionary.yaml`, `.framework/Dictionary.yaml` прошивок — его справочные копии, сборка прошивок сверяет с ними `api_cmd.h`), `parseArgumentDictionary` (строки `- [Строка, Enum, Число]`), `nameByCode`. Своих копий протоколов и словаря в приложениях не держим: терминал и отладка облака читают и пишут порт сырыми байтами, разбор и сборка — здесь.
|
|
65
65
|
|
|
66
66
|
**Шапка смарт-виджета** (`WidgetHeader`): значок, заголовок и сворачивание слева, «?» в правом краю; правая часть (режимы, «Обновить» и т. п.) стоит в той же строке, а если не помещается — уходит второй строкой под заголовок и там переносится.
|
|
67
67
|
|
package/dist/protocols/crc.js
CHANGED
|
@@ -9,7 +9,7 @@ export const crc16Kermit = (crc, bytes) => {
|
|
|
9
9
|
}
|
|
10
10
|
return crc & 0xffff;
|
|
11
11
|
};
|
|
12
|
-
/* CRC-8/ROHC
|
|
12
|
+
/* CRC-8/ROHC remains available for legacy consumers. System Console frames use CRC-16/KERMIT over CBOR bytes. */
|
|
13
13
|
export const crc8Rohc = (crc, bytes) => {
|
|
14
14
|
for (const byte of bytes) {
|
|
15
15
|
crc ^= byte & 0xff;
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
- [StackInfo, A_STACK_INFO, 8] # GET: снимок куч и стеков задач
|
|
16
16
|
- [BackUp, A_BACKUP, 9] # Резервная копия конфигурации
|
|
17
17
|
- [BindMKey, A_BIND_MOD_KEY, 10] # SET: привязка ключа модуля (ModBindKey)
|
|
18
|
+
- [DebugLog, A_DEBUG_LOG, 11] # OK!: системное сообщение отладки; VALUE — CBOR-массив [уровень, тег, текст]
|
|
19
|
+
- [DebPlot, A_DEBUG_PLOT, 12] # OK!: значения графиков отладки — CBOR-карта {"Имя": float32} (SC_PLOT)
|
|
18
20
|
- [DataStream, A_DATA_STREAM, 15] # OK!: поток данных групп конфигурации (CS_PushGroupStream)
|
|
19
21
|
|
|
20
22
|
# Локальная сеть
|
|
@@ -6,7 +6,7 @@ export { CborToValue, ValueToCbor, cborToValue, valueToCbor } from "./cbor/value
|
|
|
6
6
|
export { CborToJson, JsonToCbor, cborToJson, jsonToCbor } from "./cbor/json.js";
|
|
7
7
|
export { crc16Kermit, crc8Rohc } from "./crc.js";
|
|
8
8
|
export { BROADCAST_BUS_ID, makeCmd, splitCmd, EXTERNAL_BUS_ID, packetObjectCrc, encodePacket, decodePacket, objectsFor, createPacketStreamParser, type PacketObject, type DecodedPacketObject, type PacketParse, } from "./packet.js";
|
|
9
|
-
export { createPlotGroups, type PlotPoint } from "./plotGroups.js";
|
|
10
|
-
export { encodeXPoeDebugFrame, createXPoeDebugParser, xPoeDebugValueFromText, type XPoeDebugItem } from "./xPoeDebug.js";
|
|
9
|
+
export { createPlotGroups, debPlotValues, DEB_PLOT_ARGUMENT, type PlotPoint } from "./plotGroups.js";
|
|
10
|
+
export { encodeXPoeDebugFrame, createXPoeDebugParser, xPoeDebugCmd, xPoeDebugValueFromText, type XPoeDebugItem } from "./xPoeDebug.js";
|
|
11
11
|
export { CAN_FINAL_BIT, CAN_PACKET_ID_MASK, canFragments, encodeSlcanFrame, encodeCanPacket, createCanParser, slcanOpenCommands, type CanFrame, type CanMessage, } from "./can.js";
|
|
12
12
|
export { HEADER_CODES, DEFAULT_ARGUMENT_CODES, parseArgumentDictionary, nameByCode } from "./dictionary.js";
|
package/dist/protocols/index.js
CHANGED
|
@@ -12,10 +12,10 @@ export { CborToJson, JsonToCbor, cborToJson, jsonToCbor } from "./cbor/json.js";
|
|
|
12
12
|
/* Контрольные суммы и пакет poe_cbor_pkt [[TrgBusID, SrcBusID, Cmd, Payload, CRC16], …] (CAN, RS485) */
|
|
13
13
|
export { crc16Kermit, crc8Rohc } from "./crc.js";
|
|
14
14
|
export { BROADCAST_BUS_ID, makeCmd, splitCmd, EXTERNAL_BUS_ID, packetObjectCrc, encodePacket, decodePacket, objectsFor, createPacketStreamParser, } from "./packet.js";
|
|
15
|
-
/*
|
|
16
|
-
export { createPlotGroups } from "./plotGroups.js";
|
|
17
|
-
/*
|
|
18
|
-
export { encodeXPoeDebugFrame, createXPoeDebugParser, xPoeDebugValueFromText } from "./xPoeDebug.js";
|
|
15
|
+
/* Графики отладки: кадр OK! DebPlot (SC_PLOT прошивки) — значения из CBOR-карты, график на группу, серии в порядке устройства */
|
|
16
|
+
export { createPlotGroups, debPlotValues, DEB_PLOT_ARGUMENT } from "./plotGroups.js";
|
|
17
|
+
/* System Console over UART: CBOR [SrcID, HdrArg, Value, FHS, CRC16], plus stream parsing */
|
|
18
|
+
export { encodeXPoeDebugFrame, createXPoeDebugParser, xPoeDebugCmd, xPoeDebugValueFromText } from "./xPoeDebug.js";
|
|
19
19
|
/* CAN/CAN FD через slcan-адаптер: нарезка пакета на кадры как в can_service прошивки, кадры slcan, потоковый разбор со сборкой фрагментов */
|
|
20
20
|
export { CAN_FINAL_BIT, CAN_PACKET_ID_MASK, canFragments, encodeSlcanFrame, encodeCanPacket, createCanParser, slcanOpenCommands, } from "./can.js";
|
|
21
21
|
/* Словарь заголовков и аргументов Cmd — по умолчанию из мастер-словаря dictionary.yaml библиотеки, разбор файла словаря */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
export declare const DEB_PLOT_ARGUMENT = "DebPlot";
|
|
1
2
|
export interface PlotPoint {
|
|
2
3
|
name: string;
|
|
3
4
|
value: number;
|
|
4
5
|
}
|
|
5
|
-
export declare const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
push(series: string, value: number, now?: number): string | null;
|
|
6
|
+
export declare const debPlotValues: (value: string) => [string, number][];
|
|
7
|
+
export declare const createPlotGroups: () => {
|
|
8
|
+
update(values: [string, number][]): string[];
|
|
9
9
|
data(group: string): PlotPoint[];
|
|
10
10
|
clear(): void;
|
|
11
11
|
};
|
|
@@ -1,34 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
/* $lib/protocols/plotGroups.ts */
|
|
2
|
+
/* Графики отладки: кадр xPOE-Debug OK! DebPlot (SC_PLOT прошивки), VALUE — hex CBOR-карты {"Группа.Имя": float32, …} в порядке вызова.
|
|
3
|
+
График на группу — префикс имени до первой точки, без префикса — общая группа ""; серии в порядке карты, у серий, которых в кадре нет, — после них */
|
|
4
|
+
import { cborToValue } from "./cbor/value.js";
|
|
5
|
+
import { xPoeDebugValueFromText } from "./xPoeDebug.js";
|
|
6
|
+
export const DEB_PLOT_ARGUMENT = "DebPlot";
|
|
7
|
+
/* Значения графиков из VALUE кадра DebPlot (hex CBOR-карты); не карта — пустой список */
|
|
8
|
+
export const debPlotValues = (value) => {
|
|
9
|
+
try {
|
|
10
|
+
const decoded = cborToValue(xPoeDebugValueFromText(value));
|
|
11
|
+
const entries = decoded instanceof Map ? [...decoded] : decoded && typeof decoded === "object" ? Object.entries(decoded) : [];
|
|
12
|
+
return entries.filter((e) => typeof e[0] === "string" && typeof e[1] === "number" && Number.isFinite(e[1]));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export const createPlotGroups = () => {
|
|
2
19
|
const groups = new Map();
|
|
3
20
|
return {
|
|
4
|
-
/*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!ordered.has(n))
|
|
20
|
-
ordered.set(n, v);
|
|
21
|
-
g.values = ordered;
|
|
22
|
-
g.burst = [];
|
|
21
|
+
/* Значения одного кадра; возвращает группы, которые обновились */
|
|
22
|
+
update(values) {
|
|
23
|
+
const fresh = new Map();
|
|
24
|
+
for (const [series, value] of values) {
|
|
25
|
+
const dot = series.indexOf(".");
|
|
26
|
+
const group = dot > 0 ? series.slice(0, dot) : "";
|
|
27
|
+
if (!fresh.has(group))
|
|
28
|
+
fresh.set(group, new Map());
|
|
29
|
+
fresh.get(group).set(dot > 0 ? series.slice(dot + 1) : series, value);
|
|
30
|
+
}
|
|
31
|
+
for (const [group, ordered] of fresh) {
|
|
32
|
+
for (const [name, value] of groups.get(group) ?? [])
|
|
33
|
+
if (!ordered.has(name))
|
|
34
|
+
ordered.set(name, value);
|
|
35
|
+
groups.set(group, ordered);
|
|
23
36
|
}
|
|
24
|
-
|
|
25
|
-
g.burst.push(name);
|
|
26
|
-
g.values.set(name, value);
|
|
27
|
-
return group;
|
|
37
|
+
return [...fresh.keys()];
|
|
28
38
|
},
|
|
29
|
-
/* Последние значения серий группы
|
|
39
|
+
/* Последние значения серий группы */
|
|
30
40
|
data(group) {
|
|
31
|
-
return Array.from(groups.get(group)
|
|
41
|
+
return Array.from(groups.get(group) ?? [], ([name, value]) => ({ name, value }));
|
|
32
42
|
},
|
|
33
43
|
clear() {
|
|
34
44
|
groups.clear();
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
export declare const SOH = 1;
|
|
2
|
-
export declare const STX = 2;
|
|
3
|
-
export declare const ETX = 3;
|
|
4
|
-
export declare const EOT = 4;
|
|
5
|
-
export declare const US = 31;
|
|
6
1
|
export type XPoeDebugItem = {
|
|
7
2
|
type: "frame";
|
|
3
|
+
srcId: number;
|
|
4
|
+
cmd: number;
|
|
8
5
|
header: string;
|
|
9
6
|
argument: string;
|
|
10
7
|
value: string;
|
|
@@ -12,15 +9,12 @@ export type XPoeDebugItem = {
|
|
|
12
9
|
crc: string;
|
|
13
10
|
crcOk: boolean;
|
|
14
11
|
freeHeap: string;
|
|
15
|
-
} | {
|
|
16
|
-
type: "plot";
|
|
17
|
-
name: string;
|
|
18
|
-
value: string;
|
|
19
12
|
} | {
|
|
20
13
|
type: "raw";
|
|
21
14
|
text: string;
|
|
22
15
|
};
|
|
23
|
-
export declare const
|
|
16
|
+
export declare const xPoeDebugCmd: (header: string, argument: string) => number;
|
|
17
|
+
export declare const encodeXPoeDebugFrame: (header: string, argument: string, value: Uint8Array, srcId?: number, freeHeap?: number | null) => Uint8Array;
|
|
24
18
|
export declare const createXPoeDebugParser: () => {
|
|
25
19
|
push(chunk: Uint8Array): XPoeDebugItem[];
|
|
26
20
|
reset(): void;
|
|
@@ -1,62 +1,83 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
1
|
+
/* System Console: CBOR array [SrcID, HdrArg, Value, FHS, CRC16]. CRC-16/KERMIT covers the encoded
|
|
2
|
+
first four array items, including the array header. UART is a stream; the definite array is self-delimiting. */
|
|
3
|
+
import { CborDecoder } from "./cbor/decoder.js";
|
|
4
|
+
import { CborEndOfDataError } from "./cbor/types.js";
|
|
5
|
+
import { crc16Kermit } from "./crc.js";
|
|
6
6
|
import { jsonToCbor } from "./cbor/json.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export const ETX = 0x03;
|
|
10
|
-
export const EOT = 0x04;
|
|
11
|
-
export const US = 0x1f;
|
|
12
|
-
const LF = 0x0a;
|
|
13
|
-
/* Предел текста без конца строки и незавершённого кадра — дальше отдаётся/сбрасывается, чтобы буфер не рос бесконечно */
|
|
7
|
+
import { DEFAULT_ARGUMENT_CODES, HEADER_CODES, nameByCode } from "./dictionary.js";
|
|
8
|
+
import { makeCmd, splitCmd } from "./packet.js";
|
|
14
9
|
const MAX_TEXT_TAIL = 4096;
|
|
15
10
|
const MAX_FRAME = 8192;
|
|
16
|
-
const
|
|
11
|
+
const ARRAY5 = 0x85;
|
|
17
12
|
const decoder = new TextDecoder();
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
const encodeUInt = (input) => {
|
|
14
|
+
const value = BigInt(input);
|
|
15
|
+
if (value < 24n)
|
|
16
|
+
return [Number(value)];
|
|
17
|
+
if (value <= 0xffn)
|
|
18
|
+
return [0x18, Number(value)];
|
|
19
|
+
if (value <= 0xffffn)
|
|
20
|
+
return [0x19, Number(value >> 8n), Number(value & 0xffn)];
|
|
21
|
+
if (value <= 0xffffffffn)
|
|
22
|
+
return [0x1a, Number(value >> 24n), Number(value >> 16n & 0xffn), Number(value >> 8n & 0xffn), Number(value & 0xffn)];
|
|
23
|
+
throw new Error("xPOE-Debug integer is out of range");
|
|
25
24
|
};
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
25
|
+
export const xPoeDebugCmd = (header, argument) => {
|
|
26
|
+
const hdr = HEADER_CODES[header];
|
|
27
|
+
const arg = DEFAULT_ARGUMENT_CODES[argument];
|
|
28
|
+
if (hdr === undefined || arg === undefined)
|
|
29
|
+
throw new Error(`Hdr/Arg отсутствует в общем словаре: ${header} ${argument}`);
|
|
30
|
+
return makeCmd(hdr, arg);
|
|
31
|
+
};
|
|
32
|
+
/* Команда терминала: CRC считается по тем CBOR-байтам, которые реально отправляются. */
|
|
33
|
+
export const encodeXPoeDebugFrame = (header, argument, value, srcId = 0, freeHeap = null) => {
|
|
34
|
+
const cmd = xPoeDebugCmd(header, argument);
|
|
35
|
+
if (!Number.isInteger(srcId) || srcId < 0 || srcId > 0xffff || !Number.isInteger(cmd) || cmd < 0 || cmd > 0xffff)
|
|
36
|
+
throw new Error("SrcID и HdrArg должны быть uint16");
|
|
37
|
+
const valueParser = new CborDecoder(value);
|
|
38
|
+
if (!valueParser.skipValue() || valueParser.offset !== value.length)
|
|
39
|
+
throw new Error("Value должен содержать ровно один CBOR item");
|
|
40
|
+
const prefix = new Uint8Array([ARRAY5, ...encodeUInt(srcId), ...encodeUInt(cmd), ...value, ...(freeHeap === null ? [0xf6] : encodeUInt(freeHeap))]);
|
|
41
|
+
const crc = crc16Kermit(0, prefix);
|
|
42
|
+
return new Uint8Array([...prefix, ...encodeUInt(crc)]);
|
|
43
|
+
};
|
|
44
|
+
const textItems = (bytes) => decoder.decode(bytes).split("\n").map((line) => line.trim()).filter((line) => line && ![...line].some((char) => char.charCodeAt(0) < 0x20 && char !== "\t")).map((text) => ({ type: "raw", text }));
|
|
45
|
+
const readUInt = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xffff ? value : null;
|
|
46
|
+
/* Декодирует одну definite array из начала буфера; null означает неполный или невалидный кадр. */
|
|
47
|
+
const frameItem = (buffer) => {
|
|
48
|
+
if (buffer[0] !== ARRAY5)
|
|
49
|
+
return { status: "garbage" };
|
|
50
|
+
try {
|
|
51
|
+
const cbor = new CborDecoder(buffer);
|
|
52
|
+
cbor.decode(); /* Проверка структуры и границы всего массива */
|
|
53
|
+
const length = cbor.offset;
|
|
54
|
+
const fields = new CborDecoder(buffer.subarray(1, length));
|
|
55
|
+
const srcId = readUInt(fields.decode());
|
|
56
|
+
const cmd = readUInt(fields.decode());
|
|
57
|
+
if (srcId === null || cmd === null)
|
|
58
|
+
return { status: "garbage" };
|
|
59
|
+
const valueStart = 1 + fields.offset;
|
|
60
|
+
if (!fields.skipValue())
|
|
61
|
+
return { status: "garbage" };
|
|
62
|
+
const valueEnd = 1 + fields.offset;
|
|
63
|
+
const freeHeapValue = fields.decode();
|
|
64
|
+
const crcStart = 1 + fields.offset;
|
|
65
|
+
const crc = readUInt(fields.decode());
|
|
66
|
+
if (fields.offset !== length - 1 || crc === null || (freeHeapValue !== null && (typeof freeHeapValue !== "number" || !Number.isInteger(freeHeapValue) || freeHeapValue < 0)))
|
|
67
|
+
return { status: "garbage" };
|
|
68
|
+
const valueBytes = buffer.slice(valueStart, valueEnd);
|
|
69
|
+
const { header, argument } = splitCmd(cmd);
|
|
70
|
+
return {
|
|
71
|
+
status: "complete",
|
|
72
|
+
length,
|
|
73
|
+
item: { type: "frame", srcId, cmd, header: Object.entries(HEADER_CODES).find(([, code]) => code === header)?.[0] ?? String(header), argument: nameByCode(DEFAULT_ARGUMENT_CODES, argument), value: Array.from(valueBytes, (byte) => byte.toString(16).padStart(2, "0").toUpperCase()).join(""), valueBytes, crc: crc.toString(16).toUpperCase().padStart(4, "0"), crcOk: crc16Kermit(0, buffer.subarray(0, crcStart)) === crc, freeHeap: typeof freeHeapValue === "number" ? String(freeHeapValue) : "" },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof CborEndOfDataError)
|
|
78
|
+
return { status: "incomplete" };
|
|
79
|
+
return { status: "garbage" };
|
|
80
|
+
}
|
|
60
81
|
};
|
|
61
82
|
const concat = (a, b) => {
|
|
62
83
|
const out = new Uint8Array(a.length + b.length);
|
|
@@ -64,59 +85,53 @@ const concat = (a, b) => {
|
|
|
64
85
|
out.set(b, a.length);
|
|
65
86
|
return out;
|
|
66
87
|
};
|
|
67
|
-
/* Потоковый разбор: push(байты из порта) → готовые кадры и строки; незавершённое ждёт следующего чанка */
|
|
68
88
|
export const createXPoeDebugParser = () => {
|
|
69
89
|
let buffer = new Uint8Array();
|
|
70
90
|
return {
|
|
71
91
|
push(chunk) {
|
|
72
92
|
buffer = concat(buffer, chunk);
|
|
73
93
|
const items = [];
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
94
|
+
while (buffer.length) {
|
|
95
|
+
const start = buffer.indexOf(ARRAY5);
|
|
96
|
+
if (start < 0) {
|
|
97
|
+
const cut = buffer.length > MAX_TEXT_TAIL ? buffer.length : buffer.lastIndexOf(0x0a);
|
|
98
|
+
if (cut >= 0) {
|
|
99
|
+
items.push(...textItems(buffer.subarray(0, cut)));
|
|
100
|
+
buffer = buffer.slice(Math.min(cut + 1, buffer.length));
|
|
101
|
+
}
|
|
102
|
+
else if (buffer.length > MAX_TEXT_TAIL)
|
|
103
|
+
buffer = new Uint8Array();
|
|
77
104
|
break;
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
buffer = buffer.slice(soh);
|
|
105
|
+
}
|
|
106
|
+
if (start > 0) {
|
|
107
|
+
items.push(...textItems(buffer.subarray(0, start)));
|
|
108
|
+
buffer = buffer.slice(start);
|
|
83
109
|
continue;
|
|
84
110
|
}
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
/* Кадр без конца слишком длинный — потерян EOT: пропускаем его SOH и ищем следующий кадр, иначе ждём ещё байт */
|
|
111
|
+
const parsed = frameItem(buffer);
|
|
112
|
+
if (parsed.status === "incomplete") {
|
|
88
113
|
if (buffer.length <= MAX_FRAME)
|
|
89
114
|
break;
|
|
90
115
|
buffer = buffer.slice(1);
|
|
91
116
|
continue;
|
|
92
117
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
buffer = buffer.slice(eot + 1);
|
|
97
|
-
}
|
|
98
|
-
/* Хвост без начала кадра: целые строки — наружу, незавершённая строка ждёт следующего чанка */
|
|
99
|
-
if (buffer.indexOf(SOH) < 0) {
|
|
100
|
-
const cut = buffer.length > MAX_TEXT_TAIL ? buffer.length : buffer.lastIndexOf(LF);
|
|
101
|
-
if (cut >= 0) {
|
|
102
|
-
items.push(...textItems(buffer.subarray(0, cut)));
|
|
103
|
-
buffer = buffer.slice(Math.min(cut + 1, buffer.length));
|
|
118
|
+
if (parsed.status === "garbage") {
|
|
119
|
+
buffer = buffer.slice(1);
|
|
120
|
+
continue;
|
|
104
121
|
}
|
|
122
|
+
items.push(parsed.item);
|
|
123
|
+
buffer = buffer.slice(parsed.length);
|
|
105
124
|
}
|
|
106
125
|
return items;
|
|
107
126
|
},
|
|
108
|
-
|
|
109
|
-
reset() {
|
|
110
|
-
buffer = new Uint8Array();
|
|
111
|
-
},
|
|
127
|
+
reset() { buffer = new Uint8Array(); },
|
|
112
128
|
};
|
|
113
129
|
};
|
|
114
|
-
/*
|
|
115
|
-
(двоичные данные — base64, ключи с BIN — байты, см. jsonToCbor); не hex и не JSON — ошибка */
|
|
130
|
+
/* Value form input: ready CBOR hex passes through; JSON is converted to CBOR. */
|
|
116
131
|
export const xPoeDebugValueFromText = (text) => {
|
|
117
132
|
const trimmed = (text ?? "").trim();
|
|
118
133
|
if (!trimmed)
|
|
119
|
-
return new Uint8Array();
|
|
134
|
+
return new Uint8Array([0xf6]);
|
|
120
135
|
if (/^([0-9A-Fa-f]{2}\s?)+$/.test(trimmed))
|
|
121
136
|
return Uint8Array.from(trimmed.replace(/\s/g, "").match(/../g) ?? [], (pair) => parseInt(pair, 16));
|
|
122
137
|
let parsed;
|