poe-svelte-ui-lib 1.62.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` с 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 (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
 
@@ -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,3 @@
1
+ export declare const crc16Kermit: (crc: number, bytes: Uint8Array | number[]) => number;
2
+ export declare const crc8Rohc: (crc: number, bytes: Uint8Array | number[]) => number;
3
+ export declare const crc8RohcUtf8: (crc: number, bytes: Uint8Array) => number;
@@ -0,0 +1,44 @@
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
+ };
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,3 +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, crc8RohcUtf8 } from "./crc.js";
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";
@@ -9,3 +9,8 @@ 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, crc8RohcUtf8 } from "./crc.js";
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,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));
@@ -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.62.0",
3
+ "version": "1.64.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",