poe-svelte-ui-lib 1.62.0 → 1.63.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` с CRC и разбор потоков 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/CAN/RS485 — своих копий протоколов в приложениях не держим.
63
63
 
64
64
  **Шапка смарт-виджета** (`WidgetHeader`): значок, заголовок и сворачивание слева, «?» в правом краю; правая часть (режимы, «Обновить» и т. п.) стоит в той же строке, а если не помещается — уходит второй строкой под заголовок и там переносится.
65
65
 
@@ -2,6 +2,7 @@ import { type CborValue } from "./types.js";
2
2
  export declare class CborDecoder {
3
3
  private state;
4
4
  constructor(buffer: ArrayBuffer | Uint8Array);
5
+ get offset(): number;
5
6
  decode(): CborValue;
6
7
  skipValue(): boolean;
7
8
  decodeBytes(): {
@@ -12,6 +12,10 @@ export class CborDecoder {
12
12
  depth: 0,
13
13
  };
14
14
  }
15
+ /* Сколько байт уже прочитано — граница значения при разборе потока по одному значению */
16
+ get offset() {
17
+ return this.state.offset;
18
+ }
15
19
  /* Основной метод декодирования - декодирует одно значение CBOR */
16
20
  decode() {
17
21
  this.state.depth++;
@@ -180,14 +184,15 @@ export class CborDecoder {
180
184
  return -1 - Number(header.value);
181
185
  case CborMajorType.ByteString: {
182
186
  const bytesValue = header.value === BigInt(-1) ? this.readIndefiniteBytes() : this.decodeBytesWithLength(Number(header.value));
187
+ /* Не хватило байт — конец данных (в потоке это «ждать ещё»), а не испорченный CBOR */
183
188
  if (!bytesValue)
184
- throw new CborError("Failed to decode byte string");
189
+ throw new CborEndOfDataError();
185
190
  return bytesValue.value;
186
191
  }
187
192
  case CborMajorType.Utf8String: {
188
193
  const stringValue = header.value === BigInt(-1) ? this.readIndefiniteString() : this.decodeStringWithLength(Number(header.value));
189
194
  if (!stringValue)
190
- throw new CborError("Failed to decode string");
195
+ throw new CborEndOfDataError();
191
196
  return stringValue.value;
192
197
  }
193
198
  case CborMajorType.Array:
@@ -0,0 +1,2 @@
1
+ export declare const crc16Kermit: (crc: number, bytes: Uint8Array | number[]) => number;
2
+ export declare const crc8Rohc: (crc: number, bytes: Uint8Array | number[]) => number;
@@ -0,0 +1,20 @@
1
+ /* $lib/protocols/crc.ts */
2
+ /* Контрольные суммы протоколов устройств — побайтные, как в прошивках (ProdFactory-ESP/STM) */
3
+ /* CRC-16/KERMIT: poly 0x1021 reflected (0x8408), init 0x0000, xorout 0x0000, check("123456789") = 0x2189 — объекты пакета poe_cbor_pkt */
4
+ export const crc16Kermit = (crc, bytes) => {
5
+ for (const byte of bytes) {
6
+ crc ^= byte & 0xff;
7
+ for (let bit = 0; bit < 8; bit++)
8
+ crc = crc & 1 ? (crc >>> 1) ^ 0x8408 : crc >>> 1;
9
+ }
10
+ return crc & 0xffff;
11
+ };
12
+ /* CRC-8/ROHC: poly 0x07 reflected (0xE0), init 0xFF, xorout 0x00, check("123456789") = 0xD0 — рамка POESerial (system_console) */
13
+ export const crc8Rohc = (crc, bytes) => {
14
+ for (const byte of bytes) {
15
+ crc ^= byte & 0xff;
16
+ for (let bit = 0; bit < 8; bit++)
17
+ crc = crc & 1 ? (crc >>> 1) ^ 0xe0 : crc >>> 1;
18
+ }
19
+ return crc & 0xff;
20
+ };
@@ -4,3 +4,5 @@ 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";
8
+ export { BROADCAST_BUS_ID, makeHaCode, splitHaCode, packetObjectCrc, encodePacket, decodePacket, objectsFor, type PacketObject, type DecodedPacketObject, type PacketParse, } from "./packet.js";
@@ -9,3 +9,6 @@ export { bytesToBase64, base64ToBytes } from "./cbor/base64.js";
9
9
  export { CborToValue, ValueToCbor, cborToValue, valueToCbor } from "./cbor/value.js";
10
10
  /* JSON-текст ↔ CBOR: двоичные данные — base64, ключи с "BIN" кодируются байтами (поля ввода терминала и отладки) */
11
11
  export { CborToJson, JsonToCbor, cborToJson, jsonToCbor } from "./cbor/json.js";
12
+ /* Контрольные суммы и пакет poe_cbor_pkt { TrgBusID : [RetBusID, HA_Code, Payload, CRC16] } (CAN, RS485) */
13
+ export { crc16Kermit, crc8Rohc } from "./crc.js";
14
+ export { BROADCAST_BUS_ID, makeHaCode, splitHaCode, packetObjectCrc, encodePacket, decodePacket, objectsFor, } from "./packet.js";
@@ -0,0 +1,31 @@
1
+ export declare const BROADCAST_BUS_ID = 65535;
2
+ export interface PacketObject {
3
+ trgBusID: number;
4
+ retBusID: number;
5
+ haCode: number;
6
+ payload: Uint8Array;
7
+ }
8
+ export interface DecodedPacketObject extends PacketObject {
9
+ header: number;
10
+ argument: number;
11
+ crc: number;
12
+ crcOk: boolean;
13
+ }
14
+ export declare const makeHaCode: (header: number, argument: number) => number;
15
+ export declare const splitHaCode: (haCode: number) => {
16
+ header: number;
17
+ argument: number;
18
+ };
19
+ export declare const packetObjectCrc: (trgBusID: number, retBusID: number, haCode: number, payload: Uint8Array) => number;
20
+ export declare const encodePacket: (objects: PacketObject[]) => Uint8Array;
21
+ export type PacketParse = {
22
+ status: "complete";
23
+ length: number;
24
+ objects: DecodedPacketObject[];
25
+ } | {
26
+ status: "incomplete";
27
+ } | {
28
+ status: "garbage";
29
+ };
30
+ export declare const decodePacket: (bytes: Uint8Array) => PacketParse;
31
+ export declare const objectsFor: (objects: DecodedPacketObject[], busID: number) => DecodedPacketObject[];
@@ -0,0 +1,105 @@
1
+ /* $lib/protocols/packet.ts */
2
+ /* Пакет poe_cbor_pkt (README poe_cbor прошивки, ProdFactory-ESP/STM): indefinite MAP { TrgBusID : [RetBusID, HA_Code, Payload, CRC16] },
3
+ объектов в пакете сколько угодно (в том числе с одинаковым TrgBusID), у каждого свой CRC-16/KERMIT по TrgBusID|RetBusID|HA_Code (по 2 байта
4
+ big-endian) и Payload — не зависит от того, как числа записаны в CBOR. Общая точка сборки и разбора для терминала (CAN, RS485) и облака */
5
+ import { CborDecoder } from "./cbor/decoder.js";
6
+ import { CborEncoder } from "./cbor/encoder.js";
7
+ import { CborEndOfDataError } from "./cbor/types.js";
8
+ import { crc16Kermit } from "./crc.js";
9
+ /* Адрес «всем устройствам шины» */
10
+ export const BROADCAST_BUS_ID = 0xffff;
11
+ /* HA_Code = заголовок (0..3) в старших 2 битах и аргумент (0..0x3FFF) */
12
+ export const makeHaCode = (header, argument) => ((header & 0x03) << 14) | (argument & 0x3fff);
13
+ export const splitHaCode = (haCode) => ({ header: (haCode >> 14) & 0x03, argument: haCode & 0x3fff });
14
+ /* CRC объекта: TrgBusID | RetBusID | HA_Code по 2 байта big-endian, затем Payload */
15
+ export const packetObjectCrc = (trgBusID, retBusID, haCode, payload) => {
16
+ const head = [trgBusID >> 8, trgBusID, retBusID >> 8, retBusID, haCode >> 8, haCode].map((byte) => byte & 0xff);
17
+ return crc16Kermit(crc16Kermit(0, head), payload);
18
+ };
19
+ /* Пакет из одного или нескольких объектов — те же байты, что CBOR_PACK/CBOR_PktEncFinish прошивки */
20
+ export const encodePacket = (objects) => {
21
+ const encoder = new CborEncoder();
22
+ encoder.encodeMapStart(null);
23
+ for (const { trgBusID, retBusID, haCode, payload } of objects) {
24
+ encoder.encodeUnsigned(trgBusID);
25
+ encoder.encodeArrayStart(4);
26
+ encoder.encodeUnsigned(retBusID);
27
+ encoder.encodeUnsigned(haCode);
28
+ encoder.encodeBytes(payload);
29
+ encoder.encodeUnsigned(packetObjectCrc(trgBusID, retBusID, haCode, payload));
30
+ }
31
+ encoder.encodeBreak();
32
+ return encoder.getResult();
33
+ };
34
+ /* Одно CBOR-значение с позиции offset: значение и сколько байт заняло; null — данных не хватает */
35
+ const readValue = (bytes, offset) => {
36
+ const decoder = new CborDecoder(bytes.subarray(offset));
37
+ try {
38
+ const value = decoder.decode();
39
+ return { value, length: decoder.offset };
40
+ }
41
+ catch (error) {
42
+ if (error instanceof CborEndOfDataError)
43
+ return null;
44
+ throw error;
45
+ }
46
+ };
47
+ const toNumber = (value) => (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xffff ? value : null);
48
+ /* Объект пакета из пары ключ/значение; не по формату (не 4 элемента, не те типы, старый формат без CRC) — null, как CBOR_PktDecGet его пропускает */
49
+ const toObject = (key, value) => {
50
+ const trgBusID = toNumber(key);
51
+ if (trgBusID === null || !Array.isArray(value) || value.length !== 4)
52
+ return null;
53
+ const [retBusID, haCode, payload, crc] = [toNumber(value[0]), toNumber(value[1]), value[2], toNumber(value[3])];
54
+ if (retBusID === null || haCode === null || !(payload instanceof Uint8Array) || crc === null)
55
+ return null;
56
+ return { trgBusID, retBusID, haCode, payload, crc, crcOk: packetObjectCrc(trgBusID, retBusID, haCode, payload) === crc, ...splitHaCode(haCode) };
57
+ };
58
+ /* Разбор пакета с начала буфера — и для целого кадра (CAN), и для потока (RS485: «incomplete» — ждать ещё байт, «garbage» — сдвинуться на байт) */
59
+ export const decodePacket = (bytes) => {
60
+ if (bytes.length === 0)
61
+ return { status: "incomplete" };
62
+ const first = bytes[0];
63
+ if (first >> 5 !== 5)
64
+ return { status: "garbage" };
65
+ /* Число записей: indefinite (0xBF, до BREAK) или definite — прямо в байте либо в следующих 1/2 байтах */
66
+ let offset = 1;
67
+ let count = null;
68
+ const info = first & 0x1f;
69
+ if (info < 24)
70
+ count = info;
71
+ else if (info === 24 || info === 25) {
72
+ const size = info === 24 ? 1 : 2;
73
+ if (bytes.length < 1 + size)
74
+ return { status: "incomplete" };
75
+ count = size === 1 ? bytes[1] : (bytes[1] << 8) | bytes[2];
76
+ offset += size;
77
+ }
78
+ else if (info !== 31)
79
+ return { status: "garbage" };
80
+ const objects = [];
81
+ try {
82
+ for (let index = 0; count === null || index < count; index++) {
83
+ if (offset >= bytes.length)
84
+ return { status: "incomplete" };
85
+ if (count === null && bytes[offset] === 0xff)
86
+ return { status: "complete", length: offset + 1, objects };
87
+ const key = readValue(bytes, offset);
88
+ if (!key)
89
+ return { status: "incomplete" };
90
+ const value = readValue(bytes, offset + key.length);
91
+ if (!value)
92
+ return { status: "incomplete" };
93
+ offset += key.length + value.length;
94
+ const object = toObject(key.value, value.value);
95
+ if (object)
96
+ objects.push(object);
97
+ }
98
+ }
99
+ catch {
100
+ return { status: "garbage" };
101
+ }
102
+ return { status: "complete", length: offset, objects };
103
+ };
104
+ /* Объекты пакета для своего адреса (и broadcast) с верным CRC — как CBOR_PktDecGet прошивки, только все, а не первый */
105
+ export const objectsFor = (objects, busID) => objects.filter((object) => object.crcOk && (object.trgBusID === busID || object.trgBusID === BROADCAST_BUS_ID));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.62.0",
3
+ "version": "1.63.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"
@@ -15,7 +15,8 @@
15
15
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
16
16
  "CheckUpdate": "npx npm-check-updates -u && npm install",
17
17
  "UpdateIconsLib": "tsx src/lib/IconsCatalog/iconsLib.ts",
18
- "Formatting": "prettier --write ."
18
+ "Formatting": "prettier --write .",
19
+ "TestProtocols": "svelte-package && node --test tests/*.test.mjs"
19
20
  },
20
21
  "svelte": "./dist/index.js",
21
22
  "types": "./dist/index.d.ts",