poe-svelte-ui-lib 1.69.0 → 1.71.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 +3 -1
- package/dist/Input/README.md +1 -0
- package/dist/Select/README.md +1 -1
- package/dist/bitField.d.ts +2 -0
- package/dist/bitField.js +11 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/protocols/crc.js +1 -1
- package/dist/protocols/dictionary.js +1 -1
- package/dist/protocols/dictionary.yaml +1 -1
- package/dist/protocols/index.d.ts +3 -2
- package/dist/protocols/index.js +7 -5
- package/dist/protocols/packet.d.ts +8 -5
- package/dist/protocols/packet.js +34 -32
- package/dist/protocols/plotGroups.d.ts +11 -0
- package/dist/protocols/plotGroups.js +37 -0
- package/dist/protocols/{poeSerial.d.ts → xPoeDebug.d.ts} +5 -5
- package/dist/protocols/{poeSerial.js → xPoeDebug.js} +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,9 @@ npm install poe-svelte-ui-lib
|
|
|
59
59
|
|
|
60
60
|
**Названия смарт-виджетов** — в словаре библиотеки на всех языках (WiFi, «Об устройстве», «Файловая система», «Стек и задачи», «RGB лента», «Пилотажный дисплей», «Карта»); пустой `label.name` показывает название на языке пользователя, `widgetTitle(type)` отдаёт его сразу на всех языках `{ ru, en, zh }` — так конструктор заполняет заголовок нового виджета.
|
|
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
|
+
|
|
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`. Своих копий протоколов и словаря в приложениях не держим: терминал и отладка облака читают и пишут порт сырыми байтами, разбор и сборка — здесь.
|
|
63
65
|
|
|
64
66
|
**Шапка смарт-виджета** (`WidgetHeader`): значок, заголовок и сворачивание слева, «?» в правом краю; правая часть (режимы, «Обновить» и т. п.) стоит в той же строке, а если не помещается — уходит второй строкой под заголовок и там переносится.
|
|
65
67
|
|
package/dist/Input/README.md
CHANGED
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
- Поддерживает выбор типа поля: текст, пароль, число, многострочный текст, битовая маска
|
|
151
151
|
- Включает библиотеку готовых регулярных выражений для валидации (модальное окно с выбором шаблона)
|
|
152
152
|
- Для типа `bitMode` позволяет задать диапазон битов слайдером; `number.maxNum` рассчитывается автоматически
|
|
153
|
+
- Значение поля в `bitMode` вызывающий код берёт и записывает через `bitFieldGet`/`bitFieldSet` (`poe-svelte-ui-lib`) — корректно для любых бит 0..31, включая старший.
|
|
153
154
|
- Поддерживает валидацию вводимых данных через регулярные выражения
|
|
154
155
|
|
|
155
156
|
### Что настраивается
|
package/dist/Select/README.md
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
| `options` | `IOption<T>[]` | `[]` | Массив доступных опций |
|
|
55
55
|
| `onUpdate` | `(value: IOption<T> \| IOption<T>[]) => void` | `undefined` | Обработчик изменения значения |
|
|
56
56
|
|
|
57
|
-
> **`bitMode`/`range`**: эти поля объявлены в типе `ISelectProps` и настраиваются в конструкторской панели (`SelectProps.svelte`), но сам `Select.svelte` их не читает и не использует — они не оказывают никакого эффекта на голый `<UI.Select>`. Если нужен битовый выбор в реальном рантайме, интерпретируйте маску на стороне вызывающего кода и передавайте уже готовый `value`/`options`.
|
|
57
|
+
> **`bitMode`/`range`**: эти поля объявлены в типе `ISelectProps` и настраиваются в конструкторской панели (`SelectProps.svelte`), но сам `Select.svelte` их не читает и не использует — они не оказывают никакого эффекта на голый `<UI.Select>`. Если нужен битовый выбор в реальном рантайме, интерпретируйте маску на стороне вызывающего кода и передавайте уже готовый `value`/`options`. Разбор и сборка поля `range.start..range.end` 32-битного слова — `bitFieldGet(word, start, end)`/`bitFieldSet(word, start, end, value)` из `poe-svelte-ui-lib` (обычная арифметика вместо знаковых 32-битных операций JS: бит 31 и поля любой ширины без отрицательных чисел, слово на входе может быть и отрицательным — то же 32-битное представление); так делают `GUIPreview` DevCloud и WebApp.
|
|
58
58
|
|
|
59
59
|
### Вложенные типы
|
|
60
60
|
|
package/dist/bitField.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* $lib/bitField.ts */
|
|
2
|
+
/* Битовое поле [start..end] 32-битного слова конфигурации (Select/Input с bitMode): обычная арифметика вместо знаковых 32-битных операций JS —
|
|
3
|
+
бит 31 и поля любой ширины разбираются и собираются без отрицательных значений; слово на входе может прийти и отрицательным (то же 32-битное представление) */
|
|
4
|
+
const span = (start, end) => 2 ** (end - start + 1);
|
|
5
|
+
export const bitFieldGet = (word, start, end) => Math.floor((word >>> 0) / 2 ** start) % span(start, end);
|
|
6
|
+
export const bitFieldSet = (word, start, end, value) => {
|
|
7
|
+
const w = word >>> 0;
|
|
8
|
+
const size = span(start, end);
|
|
9
|
+
const field = ((Math.trunc(value) % size) + size) % size;
|
|
10
|
+
return w - bitFieldGet(w, start, end) * 2 ** start + field * 2 ** start;
|
|
11
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export { default as SwitchProps } from "./Switch/SwitchProps.svelte";
|
|
|
29
29
|
export { default as Table } from "./Table/Table.svelte";
|
|
30
30
|
export { default as TableProps } from "./Table/TableProps.svelte";
|
|
31
31
|
export { tableMonoWidth } from "./Table/columnWidth";
|
|
32
|
+
export { bitFieldGet, bitFieldSet } from "./bitField";
|
|
32
33
|
export { default as Tabs } from "./Tabs/Tabs.svelte";
|
|
33
34
|
export { default as TabsProps } from "./Tabs/TabsProps.svelte";
|
|
34
35
|
export { default as TextField } from "./TextField/TextField.svelte";
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ export { default as SwitchProps } from "./Switch/SwitchProps.svelte";
|
|
|
30
30
|
export { default as Table } from "./Table/Table.svelte";
|
|
31
31
|
export { default as TableProps } from "./Table/TableProps.svelte";
|
|
32
32
|
export { tableMonoWidth } from "./Table/columnWidth";
|
|
33
|
+
export { bitFieldGet, bitFieldSet } from "./bitField";
|
|
33
34
|
export { default as Tabs } from "./Tabs/Tabs.svelte";
|
|
34
35
|
export { default as TabsProps } from "./Tabs/TabsProps.svelte";
|
|
35
36
|
export { default as TextField } from "./TextField/TextField.svelte";
|
package/dist/protocols/crc.js
CHANGED
|
@@ -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 — рамка
|
|
12
|
+
/* CRC-8/ROHC: poly 0x07 reflected (0xE0), init 0xFF, xorout 0x00, check("123456789") = 0xD0 — рамка xPOE-Debug (system_console): HEADER, ARGUMENT, VALUE */
|
|
13
13
|
export const crc8Rohc = (crc, bytes) => {
|
|
14
14
|
for (const byte of bytes) {
|
|
15
15
|
crc ^= byte & 0xff;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* $lib/protocols/dictionary.ts */
|
|
2
|
-
/* Словарь заголовков и аргументов протокола
|
|
2
|
+
/* Словарь заголовков и аргументов протокола — заголовок и аргумент поля Cmd пакета (makeCmd/splitCmd). Аргументы по умолчанию —
|
|
3
3
|
из мастер-словаря dictionary.yaml рядом (единственный источник; .framework/Dictionary.yaml прошивок — его справочные копии), DEFAULT_ARGUMENT_CODES
|
|
4
4
|
генерируется из него (dictionaryCodes.ts, npm run Dictionary); у устройства словарь может быть свой — его загружают из файла
|
|
5
5
|
(parseArgumentDictionary), своих копий словаря в приложениях не держим */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Словарь аргументов протокола
|
|
1
|
+
# Словарь аргументов протокола (поле arg в Cmd, 0..8191) — единый для всех устройств, обоих фреймворков и приложений
|
|
2
2
|
# Строковое представление аргумента | Название в перечислении | Числовой код
|
|
3
3
|
# Источник — poe-svelte-ui-lib (src/lib/protocols/dictionary.yaml); .framework/Dictionary.yaml фреймворков — копии, не править
|
|
4
4
|
|
|
@@ -5,7 +5,8 @@ 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
7
|
export { crc16Kermit, crc8Rohc } from "./crc.js";
|
|
8
|
-
export { BROADCAST_BUS_ID,
|
|
9
|
-
export {
|
|
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";
|
|
10
11
|
export { CAN_FINAL_BIT, CAN_PACKET_ID_MASK, canFragments, encodeSlcanFrame, encodeCanPacket, createCanParser, slcanOpenCommands, type CanFrame, type CanMessage, } from "./can.js";
|
|
11
12
|
export { HEADER_CODES, DEFAULT_ARGUMENT_CODES, parseArgumentDictionary, nameByCode } from "./dictionary.js";
|
package/dist/protocols/index.js
CHANGED
|
@@ -9,12 +9,14 @@ 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
|
|
12
|
+
/* Контрольные суммы и пакет poe_cbor_pkt [[TrgBusID, SrcBusID, Cmd, Payload, CRC16], …] (CAN, RS485) */
|
|
13
13
|
export { crc16Kermit, crc8Rohc } from "./crc.js";
|
|
14
|
-
export { BROADCAST_BUS_ID,
|
|
15
|
-
/*
|
|
16
|
-
export {
|
|
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";
|
|
17
19
|
/* CAN/CAN FD через slcan-адаптер: нарезка пакета на кадры как в can_service прошивки, кадры slcan, потоковый разбор со сборкой фрагментов */
|
|
18
20
|
export { CAN_FINAL_BIT, CAN_PACKET_ID_MASK, canFragments, encodeSlcanFrame, encodeCanPacket, createCanParser, slcanOpenCommands, } from "./can.js";
|
|
19
|
-
/* Словарь заголовков и аргументов
|
|
21
|
+
/* Словарь заголовков и аргументов Cmd — по умолчанию из мастер-словаря dictionary.yaml библиотеки, разбор файла словаря */
|
|
20
22
|
export { HEADER_CODES, DEFAULT_ARGUMENT_CODES, parseArgumentDictionary, nameByCode } from "./dictionary.js";
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
export declare const BROADCAST_BUS_ID = 65535;
|
|
2
|
+
export declare const EXTERNAL_BUS_ID = 0;
|
|
2
3
|
export interface PacketObject {
|
|
3
4
|
trgBusID: number;
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
srcBusID: number;
|
|
6
|
+
cmd: number;
|
|
6
7
|
payload: Uint8Array;
|
|
7
8
|
}
|
|
8
9
|
export interface DecodedPacketObject extends PacketObject {
|
|
9
10
|
header: number;
|
|
10
11
|
argument: number;
|
|
12
|
+
reply: boolean;
|
|
11
13
|
crc: number;
|
|
12
14
|
crcOk: boolean;
|
|
13
15
|
}
|
|
14
|
-
export declare const
|
|
15
|
-
export declare const
|
|
16
|
+
export declare const makeCmd: (header: number, argument: number, reply?: boolean) => number;
|
|
17
|
+
export declare const splitCmd: (cmd: number) => {
|
|
16
18
|
header: number;
|
|
17
19
|
argument: number;
|
|
20
|
+
reply: boolean;
|
|
18
21
|
};
|
|
19
|
-
export declare const packetObjectCrc: (trgBusID: number,
|
|
22
|
+
export declare const packetObjectCrc: (trgBusID: number, srcBusID: number, cmd: number, payload: Uint8Array) => number;
|
|
20
23
|
export declare const encodePacket: (objects: PacketObject[]) => Uint8Array;
|
|
21
24
|
export type PacketParse = {
|
|
22
25
|
status: "complete";
|
package/dist/protocols/packet.js
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
/* $lib/protocols/packet.ts */
|
|
2
|
-
/* Пакет poe_cbor_pkt (README poe_cbor прошивки, ProdFactory-ESP/STM): indefinite
|
|
3
|
-
объектов в пакете сколько угодно (в том числе
|
|
4
|
-
big-endian) и Payload — не зависит от того, как числа записаны в CBOR. Общая точка сборки и разбора для терминала (CAN, RS485) и облака */
|
|
2
|
+
/* Пакет poe_cbor_pkt (README poe_cbor прошивки, ProdFactory-ESP/STM): indefinite ARRAY объектов [[TrgBusID, SrcBusID, Cmd, Payload, CRC16], …],
|
|
3
|
+
объектов в пакете сколько угодно (в том числе одному адресату), порядок сохраняется; у каждого свой CRC-16/KERMIT по TrgBusID|SrcBusID|Cmd
|
|
4
|
+
(по 2 байта big-endian) и Payload — не зависит от того, как числа записаны в CBOR. Общая точка сборки и разбора для терминала (CAN, RS485) и облака */
|
|
5
5
|
import { CborDecoder } from "./cbor/decoder.js";
|
|
6
6
|
import { CborEncoder } from "./cbor/encoder.js";
|
|
7
7
|
import { CborEndOfDataError } from "./cbor/types.js";
|
|
8
8
|
import { crc16Kermit } from "./crc.js";
|
|
9
|
-
/* Адрес «всем устройствам шины» */
|
|
9
|
+
/* Адрес «всем устройствам шины» — только адресат */
|
|
10
10
|
export const BROADCAST_BUS_ID = 0xffff;
|
|
11
|
-
/*
|
|
12
|
-
export const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export const
|
|
16
|
-
|
|
11
|
+
/* Адрес внешнего клиента (терминал, отладка облака) — у модулей такого адреса не бывает */
|
|
12
|
+
export const EXTERNAL_BUS_ID = 0x0000;
|
|
13
|
+
/* Cmd = заголовок (0..3) в битах 14-15, аргумент (0..8191) в битах 1-13, флаг ответа в бите 0 — как CMD/CMD_NR в api_cmd.h прошивки */
|
|
14
|
+
export const makeCmd = (header, argument, reply = true) => ((header & 0x03) << 14) | ((argument & 0x1fff) << 1) | (reply ? 1 : 0);
|
|
15
|
+
export const splitCmd = (cmd) => ({
|
|
16
|
+
header: (cmd >> 14) & 0x03,
|
|
17
|
+
argument: (cmd >> 1) & 0x1fff,
|
|
18
|
+
reply: (cmd & 1) === 1,
|
|
19
|
+
});
|
|
20
|
+
/* CRC объекта: TrgBusID | SrcBusID | Cmd по 2 байта big-endian, затем Payload */
|
|
21
|
+
export const packetObjectCrc = (trgBusID, srcBusID, cmd, payload) => {
|
|
22
|
+
const head = [trgBusID >> 8, trgBusID, srcBusID >> 8, srcBusID, cmd >> 8, cmd].map((byte) => byte & 0xff);
|
|
17
23
|
return crc16Kermit(crc16Kermit(0, head), payload);
|
|
18
24
|
};
|
|
19
25
|
/* Пакет из одного или нескольких объектов — те же байты, что CBOR_PACK/CBOR_PktEncFinish прошивки */
|
|
20
26
|
export const encodePacket = (objects) => {
|
|
21
27
|
const encoder = new CborEncoder();
|
|
22
|
-
encoder.
|
|
23
|
-
for (const { trgBusID,
|
|
28
|
+
encoder.encodeArrayStart(null);
|
|
29
|
+
for (const { trgBusID, srcBusID, cmd, payload } of objects) {
|
|
30
|
+
encoder.encodeArrayStart(5);
|
|
24
31
|
encoder.encodeUnsigned(trgBusID);
|
|
25
|
-
encoder.
|
|
26
|
-
encoder.encodeUnsigned(
|
|
27
|
-
encoder.encodeUnsigned(haCode);
|
|
32
|
+
encoder.encodeUnsigned(srcBusID);
|
|
33
|
+
encoder.encodeUnsigned(cmd);
|
|
28
34
|
encoder.encodeBytes(payload);
|
|
29
|
-
encoder.encodeUnsigned(packetObjectCrc(trgBusID,
|
|
35
|
+
encoder.encodeUnsigned(packetObjectCrc(trgBusID, srcBusID, cmd, payload));
|
|
30
36
|
}
|
|
31
37
|
encoder.encodeBreak();
|
|
32
38
|
return encoder.getResult();
|
|
@@ -45,24 +51,23 @@ const readValue = (bytes, offset) => {
|
|
|
45
51
|
}
|
|
46
52
|
};
|
|
47
53
|
const toNumber = (value) => (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xffff ? value : null);
|
|
48
|
-
/* Объект
|
|
49
|
-
const toObject = (
|
|
50
|
-
|
|
51
|
-
if (trgBusID === null || !Array.isArray(value) || value.length !== 4)
|
|
54
|
+
/* Объект пакета; не по формату (не 5 элементов, не те типы) — null, как CBOR_PktDecNext прошивки его пропускает */
|
|
55
|
+
const toObject = (value) => {
|
|
56
|
+
if (!Array.isArray(value) || value.length !== 5)
|
|
52
57
|
return null;
|
|
53
|
-
const [
|
|
54
|
-
if (
|
|
58
|
+
const [trgBusID, srcBusID, cmd, payload, crc] = [toNumber(value[0]), toNumber(value[1]), toNumber(value[2]), value[3], toNumber(value[4])];
|
|
59
|
+
if (trgBusID === null || srcBusID === null || cmd === null || !(payload instanceof Uint8Array) || crc === null)
|
|
55
60
|
return null;
|
|
56
|
-
return { trgBusID,
|
|
61
|
+
return { trgBusID, srcBusID, cmd, payload, crc, crcOk: packetObjectCrc(trgBusID, srcBusID, cmd, payload) === crc, ...splitCmd(cmd) };
|
|
57
62
|
};
|
|
58
63
|
/* Разбор пакета с начала буфера — и для целого кадра (CAN), и для потока (RS485: «incomplete» — ждать ещё байт, «garbage» — сдвинуться на байт) */
|
|
59
64
|
export const decodePacket = (bytes) => {
|
|
60
65
|
if (bytes.length === 0)
|
|
61
66
|
return { status: "incomplete" };
|
|
62
67
|
const first = bytes[0];
|
|
63
|
-
if (first >> 5 !==
|
|
68
|
+
if (first >> 5 !== 4)
|
|
64
69
|
return { status: "garbage" };
|
|
65
|
-
/* Число
|
|
70
|
+
/* Число объектов: indefinite (0x9F, до BREAK) или definite — прямо в байте либо в следующих 1/2 байтах */
|
|
66
71
|
let offset = 1;
|
|
67
72
|
let count = null;
|
|
68
73
|
const info = first & 0x1f;
|
|
@@ -84,14 +89,11 @@ export const decodePacket = (bytes) => {
|
|
|
84
89
|
return { status: "incomplete" };
|
|
85
90
|
if (count === null && bytes[offset] === 0xff)
|
|
86
91
|
return { status: "complete", length: offset + 1, objects };
|
|
87
|
-
const
|
|
88
|
-
if (!key)
|
|
89
|
-
return { status: "incomplete" };
|
|
90
|
-
const value = readValue(bytes, offset + key.length);
|
|
92
|
+
const value = readValue(bytes, offset);
|
|
91
93
|
if (!value)
|
|
92
94
|
return { status: "incomplete" };
|
|
93
|
-
offset +=
|
|
94
|
-
const object = toObject(
|
|
95
|
+
offset += value.length;
|
|
96
|
+
const object = toObject(value.value);
|
|
95
97
|
if (object)
|
|
96
98
|
objects.push(object);
|
|
97
99
|
}
|
|
@@ -101,7 +103,7 @@ export const decodePacket = (bytes) => {
|
|
|
101
103
|
}
|
|
102
104
|
return { status: "complete", length: offset, objects };
|
|
103
105
|
};
|
|
104
|
-
/* Объекты пакета для своего адреса (и broadcast) с верным CRC — как
|
|
106
|
+
/* Объекты пакета для своего адреса (и broadcast) с верным CRC, по порядку — как CBOR_PktDecNext прошивки */
|
|
105
107
|
export const objectsFor = (objects, busID) => objects.filter((object) => object.crcOk && (object.trgBusID === busID || object.trgBusID === BROADCAST_BUS_ID));
|
|
106
108
|
/* Потоковый разбор пакетов из сырого потока (RS485): пауза дольше gapMs обрывает недособранный пакет, мусор сдвигается по байту,
|
|
107
109
|
без единого целого пакета буфер не растёт дальше maxBytes */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PlotPoint {
|
|
2
|
+
name: string;
|
|
3
|
+
value: number;
|
|
4
|
+
}
|
|
5
|
+
export declare const createPlotGroups: ({ gapMs }?: {
|
|
6
|
+
gapMs?: number | undefined;
|
|
7
|
+
}) => {
|
|
8
|
+
push(series: string, value: number, now?: number): string | null;
|
|
9
|
+
data(group: string): PlotPoint[];
|
|
10
|
+
clear(): void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const createPlotGroups = ({ gapMs = 50 } = {}) => {
|
|
2
|
+
const groups = new Map();
|
|
3
|
+
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 = [];
|
|
23
|
+
}
|
|
24
|
+
g.last = now;
|
|
25
|
+
g.burst.push(name);
|
|
26
|
+
g.values.set(name, value);
|
|
27
|
+
return group;
|
|
28
|
+
},
|
|
29
|
+
/* Последние значения серий группы в порядке устройства */
|
|
30
|
+
data(group) {
|
|
31
|
+
return Array.from(groups.get(group)?.values ?? [], ([name, value]) => ({ name, value }));
|
|
32
|
+
},
|
|
33
|
+
clear() {
|
|
34
|
+
groups.clear();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -3,7 +3,7 @@ export declare const STX = 2;
|
|
|
3
3
|
export declare const ETX = 3;
|
|
4
4
|
export declare const EOT = 4;
|
|
5
5
|
export declare const US = 31;
|
|
6
|
-
export type
|
|
6
|
+
export type XPoeDebugItem = {
|
|
7
7
|
type: "frame";
|
|
8
8
|
header: string;
|
|
9
9
|
argument: string;
|
|
@@ -20,9 +20,9 @@ export type PoeSerialItem = {
|
|
|
20
20
|
type: "raw";
|
|
21
21
|
text: string;
|
|
22
22
|
};
|
|
23
|
-
export declare const
|
|
24
|
-
export declare const
|
|
25
|
-
push(chunk: Uint8Array):
|
|
23
|
+
export declare const encodeXPoeDebugFrame: (header: string, argument: string, value: Uint8Array) => Uint8Array;
|
|
24
|
+
export declare const createXPoeDebugParser: () => {
|
|
25
|
+
push(chunk: Uint8Array): XPoeDebugItem[];
|
|
26
26
|
reset(): void;
|
|
27
27
|
};
|
|
28
|
-
export declare const
|
|
28
|
+
export declare const xPoeDebugValueFromText: (text: string | undefined) => Uint8Array;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* $lib/protocols/
|
|
2
|
-
/* Рамка
|
|
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
3
|
по HEADER+ARGUMENT+VALUE (crc8Rohc), FHS (свободная куча) только у кадров устройства. Устройство шлёт VALUE текстом (лог или hex-дамп CBOR),
|
|
4
4
|
устройству VALUE уходит сырыми байтами CBOR. Между кадрами — текст: строки Teleplot «>Имя:Значение» (PLOT) и прочий вывод (RAW) */
|
|
5
5
|
import { crc8Rohc } from "./crc.js";
|
|
@@ -17,7 +17,7 @@ const encoder = new TextEncoder();
|
|
|
17
17
|
const decoder = new TextDecoder();
|
|
18
18
|
const hex2 = (value) => value.toString(16).toUpperCase().padStart(2, "0");
|
|
19
19
|
/* Кадр команды устройству — те же байты, что разбирает system_console (VALUE — сырой CBOR, FHS не передаётся) */
|
|
20
|
-
export const
|
|
20
|
+
export const encodeXPoeDebugFrame = (header, argument, value) => {
|
|
21
21
|
const headerBytes = encoder.encode(header);
|
|
22
22
|
const argumentBytes = encoder.encode(argument);
|
|
23
23
|
const crc = crc8Rohc(crc8Rohc(crc8Rohc(0xff, headerBytes), argumentBytes), value);
|
|
@@ -65,7 +65,7 @@ const concat = (a, b) => {
|
|
|
65
65
|
return out;
|
|
66
66
|
};
|
|
67
67
|
/* Потоковый разбор: push(байты из порта) → готовые кадры и строки; незавершённое ждёт следующего чанка */
|
|
68
|
-
export const
|
|
68
|
+
export const createXPoeDebugParser = () => {
|
|
69
69
|
let buffer = new Uint8Array();
|
|
70
70
|
return {
|
|
71
71
|
push(chunk) {
|
|
@@ -113,7 +113,7 @@ export const createPoeSerialParser = () => {
|
|
|
113
113
|
};
|
|
114
114
|
/* Поле Value команды из ввода пользователя: пусто — без тела, hex-строка (с пробелами или без) — готовые байты как есть, иначе JSON → CBOR
|
|
115
115
|
(двоичные данные — base64, ключи с BIN — байты, см. jsonToCbor); не hex и не JSON — ошибка */
|
|
116
|
-
export const
|
|
116
|
+
export const xPoeDebugValueFromText = (text) => {
|
|
117
117
|
const trimmed = (text ?? "").trim();
|
|
118
118
|
if (!trimmed)
|
|
119
119
|
return new Uint8Array();
|