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 +1 -1
- package/dist/protocols/cbor/decoder.d.ts +1 -0
- package/dist/protocols/cbor/decoder.js +7 -2
- package/dist/protocols/crc.d.ts +2 -0
- package/dist/protocols/crc.js +20 -0
- package/dist/protocols/index.d.ts +2 -0
- package/dist/protocols/index.js +3 -0
- package/dist/protocols/packet.d.ts +31 -0
- package/dist/protocols/packet.js +105 -0
- package/package.json +3 -2
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` — байты (поля ввода терминала и отладки).
|
|
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
|
|
|
@@ -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
|
|
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
|
|
195
|
+
throw new CborEndOfDataError();
|
|
191
196
|
return stringValue.value;
|
|
192
197
|
}
|
|
193
198
|
case CborMajorType.Array:
|
|
@@ -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";
|
package/dist/protocols/index.js
CHANGED
|
@@ -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.
|
|
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",
|