poe-svelte-ui-lib 1.63.0 → 1.64.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
@@ -59,7 +59,7 @@ npm install poe-svelte-ui-lib
59
59
 
60
60
  **Названия смарт-виджетов** — в словаре библиотеки на всех языках (WiFi, «Об устройстве», «Файловая система», «Стек и задачи», «RGB лента», «Пилотажный дисплей», «Карта»); пустой `label.name` показывает название на языке пользователя, `widgetTitle(type)` отдаёт его сразу на всех языках `{ ru, en, zh }` — так конструктор заполняет заголовок нового виджета.
61
61
 
62
- **Ядро протоколов** — `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` `{ TrgBusID : [RetBusID, HA_Code, Payload, CRC16] }` (CAN, RS485): `encodePacket`/`decodePacket` (разбор с начала буфера — `complete` с длиной, `incomplete` для потока или `garbage`; объекты с одинаковым `TrgBusID` не склеиваются, неверный CRC — флаг `crcOk`), `objectsFor` (свой адрес и broadcast `0xFFFF`), `makeHaCode`/`splitHaCode`, `packetObjectCrc`; контрольные суммы `crc16Kermit` и `crc8Rohc`. Байты совпадают с `poe_cbor_pkt.c` прошивки — проверка `npm run TestProtocols` (`tests/protocols.test.mjs`, эталонные пакеты собраны прошивочным кодом). Дальше сюда переезжает разбор потоков POESerial/CAN/RS485 — своих копий протоколов в приложениях не держим.
62
+ **Ядро протоколов** — `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` `{ TrgBusID : [RetBusID, HA_Code, Payload, CRC16] }` (CAN, RS485): `encodePacket`/`decodePacket` (разбор с начала буфера — `complete` с длиной, `incomplete` для потока или `garbage`; объекты с одинаковым `TrgBusID` не склеиваются, неверный CRC — флаг `crcOk`), `objectsFor` (свой адрес и broadcast `0xFFFF`), `makeHaCode`/`splitHaCode`, `packetObjectCrc`; контрольные суммы `crc16Kermit` и `crc8Rohc`. Байты совпадают с `poe_cbor_pkt.c` прошивки — проверка `npm run TestProtocols` (`tests/protocols.test.mjs`, эталонные пакеты собраны прошивочным кодом). Рамка POESerial (UART-консоль `system_console`, `[SOH] HDR [US] ARG [STX] VAL [ETX] CRC8 [US] FHS [EOT]`): `encodePoeSerialFrame` — команда устройству, `createPoeSerialParser().push(байты)` — потоковый разбор кадров, строк Teleplot (`plot`) и прочего текста (`raw`), с проверкой CRC `crc8RohcUtf8` по правилу прошивки (`crc8_rohc` в `common.c`). Дальше сюда переезжает разбор CAN/RS485 — своих копий протоколов в приложениях не держим.
63
63
 
64
64
  **Шапка смарт-виджета** (`WidgetHeader`): значок, заголовок и сворачивание слева, «?» в правом краю; правая часть (режимы, «Обновить» и т. п.) стоит в той же строке, а если не помещается — уходит второй строкой под заголовок и там переносится.
65
65
 
@@ -1,2 +1,3 @@
1
1
  export declare const crc16Kermit: (crc: number, bytes: Uint8Array | number[]) => number;
2
2
  export declare const crc8Rohc: (crc: number, bytes: Uint8Array | number[]) => number;
3
+ export declare const crc8RohcUtf8: (crc: number, bytes: Uint8Array) => number;
@@ -18,3 +18,27 @@ export const crc8Rohc = (crc, bytes) => {
18
18
  }
19
19
  return crc & 0xff;
20
20
  };
21
+ /* Байт для CRC от позиции pos по правилу прошивки (common.c, utf8_seq_byte): целая UTF-8-последовательность даёт младший байт
22
+ своего code point, иначе байт как есть — так прошивка считает CRC и по тексту с эмодзи, и по сырому CBOR */
23
+ const utf8SeqByte = (bytes, pos) => {
24
+ const first = bytes[pos];
25
+ const cont = (offset) => (bytes[pos + offset] & 0xc0) === 0x80;
26
+ if ((first & 0x80) === 0)
27
+ return { data: first, length: 1 };
28
+ if ((first & 0xe0) === 0xc0 && pos + 1 < bytes.length && cont(1))
29
+ return { data: (((first & 0x1f) << 6) | (bytes[pos + 1] & 0x3f)) & 0xff, length: 2 };
30
+ if ((first & 0xf0) === 0xe0 && pos + 2 < bytes.length && cont(1) && cont(2))
31
+ return { data: (((first & 0x0f) << 12) | ((bytes[pos + 1] & 0x3f) << 6) | (bytes[pos + 2] & 0x3f)) & 0xff, length: 3 };
32
+ if ((first & 0xf8) === 0xf0 && pos + 3 < bytes.length && cont(1) && cont(2) && cont(3))
33
+ return { data: (((bytes[pos + 1] & 0x3f) << 12) | ((bytes[pos + 2] & 0x3f) << 6) | (bytes[pos + 3] & 0x3f)) & 0xff, length: 4 };
34
+ return { data: first, length: 1 };
35
+ };
36
+ /* CRC-8/ROHC по правилу прошивки для UTF-8 (crc8_rohc в common.c) — рамка POESerial: HEADER, ARGUMENT, VALUE */
37
+ export const crc8RohcUtf8 = (crc, bytes) => {
38
+ for (let pos = 0; pos < bytes.length;) {
39
+ const { data, length } = utf8SeqByte(bytes, pos);
40
+ crc = crc8Rohc(crc, [data]);
41
+ pos += length;
42
+ }
43
+ return crc;
44
+ };
@@ -4,5 +4,6 @@ export { CborEncoder, encodeCbor } from "./cbor/encoder.js";
4
4
  export { bytesToBase64, base64ToBytes } from "./cbor/base64.js";
5
5
  export { CborToValue, ValueToCbor, cborToValue, valueToCbor } from "./cbor/value.js";
6
6
  export { CborToJson, JsonToCbor, cborToJson, jsonToCbor } from "./cbor/json.js";
7
- export { crc16Kermit, crc8Rohc } from "./crc.js";
7
+ export { crc16Kermit, crc8Rohc, crc8RohcUtf8 } from "./crc.js";
8
8
  export { BROADCAST_BUS_ID, makeHaCode, splitHaCode, packetObjectCrc, encodePacket, decodePacket, objectsFor, type PacketObject, type DecodedPacketObject, type PacketParse, } from "./packet.js";
9
+ export { encodePoeSerialFrame, createPoeSerialParser, type PoeSerialItem } from "./poeSerial.js";
@@ -10,5 +10,7 @@ export { CborToValue, ValueToCbor, cborToValue, valueToCbor } from "./cbor/value
10
10
  /* JSON-текст ↔ CBOR: двоичные данные — base64, ключи с "BIN" кодируются байтами (поля ввода терминала и отладки) */
11
11
  export { CborToJson, JsonToCbor, cborToJson, jsonToCbor } from "./cbor/json.js";
12
12
  /* Контрольные суммы и пакет poe_cbor_pkt { TrgBusID : [RetBusID, HA_Code, Payload, CRC16] } (CAN, RS485) */
13
- export { crc16Kermit, crc8Rohc } from "./crc.js";
13
+ export { crc16Kermit, crc8Rohc, crc8RohcUtf8 } from "./crc.js";
14
14
  export { BROADCAST_BUS_ID, makeHaCode, splitHaCode, packetObjectCrc, encodePacket, decodePacket, objectsFor, } from "./packet.js";
15
+ /* Рамка POESerial (UART-консоль system_console): сборка команды и потоковый разбор кадров, строк Teleplot и текста */
16
+ export { encodePoeSerialFrame, createPoeSerialParser } from "./poeSerial.js";
@@ -0,0 +1,27 @@
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
+ export type PoeSerialItem = {
7
+ type: "frame";
8
+ header: string;
9
+ argument: string;
10
+ value: string;
11
+ valueBytes: Uint8Array;
12
+ crc: string;
13
+ crcOk: boolean;
14
+ freeHeap: string;
15
+ } | {
16
+ type: "plot";
17
+ name: string;
18
+ value: string;
19
+ } | {
20
+ type: "raw";
21
+ text: string;
22
+ };
23
+ export declare const encodePoeSerialFrame: (header: string, argument: string, value: Uint8Array) => Uint8Array;
24
+ export declare const createPoeSerialParser: () => {
25
+ push(chunk: Uint8Array): PoeSerialItem[];
26
+ reset(): void;
27
+ };
@@ -0,0 +1,112 @@
1
+ /* $lib/protocols/poeSerial.ts */
2
+ /* Рамка POESerial (system_console прошивки): [SOH] HEADER [US] ARGUMENT [STX] VALUE [ETX] CRC8 [US] FHS [EOT] \n — CRC8 двумя hex-символами
3
+ по HEADER+ARGUMENT+VALUE (crc8RohcUtf8), FHS (свободная куча) только у кадров устройства. Устройство шлёт VALUE текстом (лог или hex-дамп CBOR),
4
+ устройству VALUE уходит сырыми байтами CBOR. Между кадрами — текст: строки Teleplot «>Имя:Значение» (PLOT) и прочий вывод (RAW) */
5
+ import { crc8RohcUtf8 } from "./crc.js";
6
+ export const SOH = 0x01;
7
+ export const STX = 0x02;
8
+ export const ETX = 0x03;
9
+ export const EOT = 0x04;
10
+ export const US = 0x1f;
11
+ const LF = 0x0a;
12
+ /* Предел текста без конца строки и незавершённого кадра — дальше отдаётся/сбрасывается, чтобы буфер не рос бесконечно */
13
+ const MAX_TEXT_TAIL = 4096;
14
+ const MAX_FRAME = 8192;
15
+ const encoder = new TextEncoder();
16
+ const decoder = new TextDecoder();
17
+ const hex2 = (value) => value.toString(16).toUpperCase().padStart(2, "0");
18
+ /* Кадр команды устройству — те же байты, что разбирает system_console (VALUE — сырой CBOR, FHS не передаётся) */
19
+ export const encodePoeSerialFrame = (header, argument, value) => {
20
+ const headerBytes = encoder.encode(header);
21
+ const argumentBytes = encoder.encode(argument);
22
+ const crc = crc8RohcUtf8(crc8RohcUtf8(crc8RohcUtf8(0xff, headerBytes), argumentBytes), value);
23
+ return new Uint8Array([SOH, ...headerBytes, US, ...argumentBytes, STX, ...value, ETX, ...encoder.encode(hex2(crc)), EOT]);
24
+ };
25
+ /* Строки текста вне кадров: Teleplot «>Имя:Значение» → plot, остальное → raw; пустые и с управляющими символами пропускаются */
26
+ const textItems = (bytes) => decoder
27
+ .decode(bytes)
28
+ .split("\n")
29
+ .map((line) => line.trim())
30
+ .filter((line) => line && ![...line].some((char) => char.charCodeAt(0) < 0x20 && char !== "\t"))
31
+ .map((line) => {
32
+ const colon = line.indexOf(":");
33
+ return line.startsWith(">") && colon > 1 ? { type: "plot", name: line.slice(1, colon), value: line.slice(colon + 1) } : { type: "raw", text: line };
34
+ });
35
+ /* Кадр от SOH до EOT включительно; без разделителей — null (пропускается, как в прошивке и Rust-терминале) */
36
+ const frameItem = (frame) => {
37
+ const us = frame.indexOf(US);
38
+ const stx = us < 0 ? -1 : frame.indexOf(STX, us + 1);
39
+ const etx = stx < 0 ? -1 : frame.indexOf(ETX, stx + 1);
40
+ if (etx < 0)
41
+ return null;
42
+ const header = frame.subarray(1, us);
43
+ const argument = frame.subarray(us + 1, stx);
44
+ const valueBytes = frame.subarray(stx + 1, etx);
45
+ const tailUs = frame.indexOf(US, etx + 1);
46
+ const crc = decoder.decode(frame.subarray(etx + 1, tailUs < 0 ? frame.length - 1 : tailUs)).trim();
47
+ const freeHeap = tailUs < 0 ? "" : decoder.decode(frame.subarray(tailUs + 1, frame.length - 1)).trim();
48
+ const crcCalc = crc8RohcUtf8(crc8RohcUtf8(crc8RohcUtf8(0xff, header), argument), valueBytes);
49
+ return {
50
+ type: "frame",
51
+ header: decoder.decode(header),
52
+ argument: decoder.decode(argument),
53
+ value: decoder.decode(valueBytes),
54
+ valueBytes: valueBytes.slice(),
55
+ crc,
56
+ crcOk: /^[0-9A-Fa-f]{2}$/.test(crc) && parseInt(crc, 16) === crcCalc,
57
+ freeHeap,
58
+ };
59
+ };
60
+ const concat = (a, b) => {
61
+ const out = new Uint8Array(a.length + b.length);
62
+ out.set(a);
63
+ out.set(b, a.length);
64
+ return out;
65
+ };
66
+ /* Потоковый разбор: push(байты из порта) → готовые кадры и строки; незавершённое ждёт следующего чанка */
67
+ export const createPoeSerialParser = () => {
68
+ let buffer = new Uint8Array();
69
+ return {
70
+ push(chunk) {
71
+ buffer = concat(buffer, chunk);
72
+ const items = [];
73
+ for (;;) {
74
+ const soh = buffer.indexOf(SOH);
75
+ if (soh < 0)
76
+ break;
77
+ /* Текст перед кадром не теряем; обрывок прошлого кадра (до последнего EOT) отбрасываем */
78
+ if (soh > 0) {
79
+ const prefix = buffer.subarray(0, soh);
80
+ items.push(...textItems(prefix.subarray(prefix.lastIndexOf(EOT) + 1)));
81
+ buffer = buffer.slice(soh);
82
+ continue;
83
+ }
84
+ const eot = buffer.indexOf(EOT);
85
+ if (eot < 0) {
86
+ /* Кадр без конца слишком длинный — потерян EOT: пропускаем его SOH и ищем следующий кадр, иначе ждём ещё байт */
87
+ if (buffer.length <= MAX_FRAME)
88
+ break;
89
+ buffer = buffer.slice(1);
90
+ continue;
91
+ }
92
+ const item = frameItem(buffer.subarray(0, eot + 1));
93
+ if (item)
94
+ items.push(item);
95
+ buffer = buffer.slice(eot + 1);
96
+ }
97
+ /* Хвост без начала кадра: целые строки — наружу, незавершённая строка ждёт следующего чанка */
98
+ if (buffer.indexOf(SOH) < 0) {
99
+ const cut = buffer.length > MAX_TEXT_TAIL ? buffer.length : buffer.lastIndexOf(LF);
100
+ if (cut >= 0) {
101
+ items.push(...textItems(buffer.subarray(0, cut)));
102
+ buffer = buffer.slice(Math.min(cut + 1, buffer.length));
103
+ }
104
+ }
105
+ return items;
106
+ },
107
+ /* Сброс при переподключении порта */
108
+ reset() {
109
+ buffer = new Uint8Array();
110
+ },
111
+ };
112
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.63.0",
3
+ "version": "1.64.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"