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 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`, эталонные пакеты собраны прошивочным кодом). Рамка xPOE-Debug (UART-консоль `system_console`, `[SOH] HDR [US] ARG [STX] VAL [ETX] CRC8 [US] FHS [EOT]`): `encodeXPoeDebugFrame` — команда устройству, `createXPoeDebugParser().push(байты)` — потоковый разбор кадров, строк Teleplot (`plot`) и прочего текста (`raw`), с проверкой CRC `crc8Rohc` строго побайтно, как `crc8_rohc` в `common.c` прошивок. 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). Графики Teleplot (строки PLOT `>Группа.Имя:Значение`) — `createPlotGroups({ gapMs })`: `push(серия, значение, время)` раскладывает по группам (префикс до точки) и держит серии в порядке устройства — пачка значений идёт подряд, новая начинается после паузы дольше `gapMs` (50 мс) или с повтора имени, порядок берётся из последней пачки; `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`. Своих копий протоколов и словаря в приложениях не держим: терминал и отладка облака читают и пишут порт сырыми байтами, разбор и сборка — здесь.
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
 
@@ -9,7 +9,7 @@ export const crc16Kermit = (crc, bytes) => {
9
9
  }
10
10
  return crc & 0xffff;
11
11
  };
12
- /* CRC-8/ROHC: poly 0x07 reflected (0xE0), init 0xFF, xorout 0x00, check("123456789") = 0xD0 — рамка xPOE-Debug (system_console): HEADER, ARGUMENT, VALUE */
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
  # Локальная сеть
@@ -10,6 +10,8 @@ export const DEFAULT_ARGUMENT_CODES = {
10
10
  StackInfo: 8,
11
11
  BackUp: 9,
12
12
  BindMKey: 10,
13
+ DebugLog: 11,
14
+ DebPlot: 12,
13
15
  DataStream: 15,
14
16
  APsList: 21,
15
17
  FS: 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";
@@ -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
- /* Группы графиков Teleplot (строки PLOT): серии в порядке устройства, график на группу */
16
- export { createPlotGroups } from "./plotGroups.js";
17
- /* Рамка xPOE-Debug (UART-консоль system_console): сборка команды и потоковый разбор кадров, строк Teleplot и текста */
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 createPlotGroups: ({ gapMs }?: {
6
- gapMs?: number | undefined;
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
- export const createPlotGroups = ({ gapMs = 50 } = {}) => {
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
- /* Значение серии (now — время прихода, мс); возвращает группу или null, если значение не число */
5
- push(series, value, now = Date.now()) {
6
- if (!series || !Number.isFinite(value))
7
- return null;
8
- const dot = series.indexOf(".");
9
- const group = dot > 0 ? series.slice(0, dot) : "";
10
- const name = dot > 0 ? series.slice(dot + 1) : series;
11
- let g = groups.get(group);
12
- if (!g)
13
- groups.set(group, (g = { values: new Map(), burst: [], last: now }));
14
- if (now - g.last > gapMs || g.burst.includes(name)) {
15
- const ordered = new Map();
16
- for (const n of g.burst)
17
- ordered.set(n, g.values.get(n));
18
- for (const [n, v] of g.values)
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
- g.last = now;
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)?.values ?? [], ([name, value]) => ({ name, value }));
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 encodeXPoeDebugFrame: (header: string, argument: string, value: Uint8Array) => Uint8Array;
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
- /* $lib/protocols/xPoeDebug.ts */
2
- /* Рамка xPOE-Debug (system_console прошивки): [SOH] HEADER [US] ARGUMENT [STX] VALUE [ETX] CRC8 [US] FHS [EOT] \n — CRC8 двумя hex-символами
3
- по HEADER+ARGUMENT+VALUE (crc8Rohc), FHS (свободная куча) только у кадров устройства. Устройство шлёт VALUE текстом (лог или hex-дамп CBOR),
4
- устройству VALUE уходит сырыми байтами CBOR. Между кадрами — текст: строки Teleplot «>Имя:Значение» (PLOT) и прочий вывод (RAW) */
5
- import { crc8Rohc } from "./crc.js";
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
- export const SOH = 0x01;
8
- export const STX = 0x02;
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 encoder = new TextEncoder();
11
+ const ARRAY5 = 0x85;
17
12
  const decoder = new TextDecoder();
18
- const hex2 = (value) => value.toString(16).toUpperCase().padStart(2, "0");
19
- /* Кадр команды устройству — те же байты, что разбирает system_console (VALUE — сырой CBOR, FHS не передаётся) */
20
- export const encodeXPoeDebugFrame = (header, argument, value) => {
21
- const headerBytes = encoder.encode(header);
22
- const argumentBytes = encoder.encode(argument);
23
- const crc = crc8Rohc(crc8Rohc(crc8Rohc(0xff, headerBytes), argumentBytes), value);
24
- return new Uint8Array([SOH, ...headerBytes, US, ...argumentBytes, STX, ...value, ETX, ...encoder.encode(hex2(crc)), EOT]);
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
- /* Строки текста вне кадров: Teleplot «>Имя:Значение» → plot, остальное → raw; пустые и с управляющими символами пропускаются */
27
- const textItems = (bytes) => decoder
28
- .decode(bytes)
29
- .split("\n")
30
- .map((line) => line.trim())
31
- .filter((line) => line && ![...line].some((char) => char.charCodeAt(0) < 0x20 && char !== "\t"))
32
- .map((line) => {
33
- const colon = line.indexOf(":");
34
- return line.startsWith(">") && colon > 1 ? { type: "plot", name: line.slice(1, colon), value: line.slice(colon + 1) } : { type: "raw", text: line };
35
- });
36
- /* Кадр от SOH до EOT включительно; без разделителей — null (пропускается, как в прошивке и Rust-терминале) */
37
- const frameItem = (frame) => {
38
- const us = frame.indexOf(US);
39
- const stx = us < 0 ? -1 : frame.indexOf(STX, us + 1);
40
- const etx = stx < 0 ? -1 : frame.indexOf(ETX, stx + 1);
41
- if (etx < 0)
42
- return null;
43
- const header = frame.subarray(1, us);
44
- const argument = frame.subarray(us + 1, stx);
45
- const valueBytes = frame.subarray(stx + 1, etx);
46
- const tailUs = frame.indexOf(US, etx + 1);
47
- const crc = decoder.decode(frame.subarray(etx + 1, tailUs < 0 ? frame.length - 1 : tailUs)).trim();
48
- const freeHeap = tailUs < 0 ? "" : decoder.decode(frame.subarray(tailUs + 1, frame.length - 1)).trim();
49
- const crcCalc = crc8Rohc(crc8Rohc(crc8Rohc(0xff, header), argument), valueBytes);
50
- return {
51
- type: "frame",
52
- header: decoder.decode(header),
53
- argument: decoder.decode(argument),
54
- value: decoder.decode(valueBytes),
55
- valueBytes: valueBytes.slice(),
56
- crc,
57
- crcOk: /^[0-9A-Fa-f]{2}$/.test(crc) && parseInt(crc, 16) === crcCalc,
58
- freeHeap,
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
- for (;;) {
75
- const soh = buffer.indexOf(SOH);
76
- if (soh < 0)
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
- /* Текст перед кадром не теряем; обрывок прошлого кадра (до последнего EOT) отбрасываем */
79
- if (soh > 0) {
80
- const prefix = buffer.subarray(0, soh);
81
- items.push(...textItems(prefix.subarray(prefix.lastIndexOf(EOT) + 1)));
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 eot = buffer.indexOf(EOT);
86
- if (eot < 0) {
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
- const item = frameItem(buffer.subarray(0, eot + 1));
94
- if (item)
95
- items.push(item);
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
- /* Поле Value команды из ввода пользователя: пусто — без тела, hex-строка (с пробелами или без) — готовые байты как есть, иначе JSON → CBOR
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.71.0",
3
+ "version": "1.73.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"