poe-svelte-ui-lib 1.77.0 → 1.79.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
@@ -61,7 +61,7 @@ npm install poe-svelte-ui-lib
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
63
 
64
- **Ядро протоколов** — `poe-svelte-ui-lib/protocols`, описание протокола — [`src/lib/protocols/PROTOCOL.md`](src/lib/protocols/PROTOCOL.md) (без Svelte, работает и под Node без Vite): общий для DevCloud, WebApp и SYP-DevTerminal CBOR-кодек (RFC 8949); `valueToCbor`/`cborToValue` — обычные значения, двоичные данные как `Uint8Array` (WebSocket, хранилище, телеметрия); `jsonToCbor` — JSON-совместимое значение в CBOR, строки base64 под ключами с `BIN` — байты (поля ввода терминала и отладки). Пакет `poe_cbor_pkt` — indefinite-массив объектов `[[TrgBusID, SrcBusID, HAR, Payload, CRC16], …]` (CAN, RS485, UART-консоль), `Payload` — одно CBOR item (сырые данные — `encodePacketBytesPayload`, пустой массив — `null`): `encodePacket`/`decodePacket` (разбор с начала буфера — `complete` с длиной, `incomplete` для потока или `garbage`; объекты одному адресату не склеиваются, порядок сохраняется, неверный CRC — флаг `crcOk`), `BUS_ID_REGEXP`/`parseBusId`/`formatBusId` — поля BusID в HEX и вывод `0x0000`, `makeHar(header, argument, reply = true)`/`splitHar` — `HAR`: заголовок в битах 14-15, аргумент в битах 1-13 (0..8191), флаг ответа в бите 0 (как `HAR`/`HAR_NR` прошивки), `packetObjectCrc`; контрольная сумма `crc16Kermit` — строго побайтная, как в `common.c` прошивок. Байты совпадают с `poe_cbor_pkt.c` прошивки — проверка `npm run TestProtocols` (`tests/protocols.test.mjs`, эталонные пакеты собраны прошивочным кодом). Протокол xPOE-Debug (UART-консоль `system_console`) — тот же пакет `poe_cbor_pkt` вперемешку с текстом загрузчика: `encodeXPoeDebugFrame(header, argument, payload, srcId, trgId, reply)` кодирует запрос, `xPoeDebugHar` — HAR по именам словаря, `createXPoeDebugParser().push(байты)` разбирает поток пакетов (каждая запись — отдельный элемент) и внешний текст (`raw`), логи `OK! DebugLog` — `[уровень, тег, текст, FHS]` (`DEBUG_LOG_ARGUMENT`, FHS — в `freeHeap`). Строка таблицы — `xPoeDebugRow`, колонки — `xPoeDebugTableColumns` (основной вход библиотеки): общие для отладки облака и терминала. 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, пусто — пустой Payload). Графики отладки — пакет `OK! DebugPlot` (`LOG_PLOT` прошивки, `DEBUG_PLOT_ARGUMENT`): `debugPlotValues(value)` — пары `[имя, число]` из HEX CBOR-карты `Payload`, `createPlotGroups().update(пары)` раскладывает по группам (префикс до точки) в порядке карты и возвращает обновлённые группы, `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_har.h`), `parseArgumentDictionary` (строки `- [Строка, Enum, Число]`), `nameByCode`. Своих копий протоколов и словаря в приложениях не держим: терминал и отладка облака читают и пишут порт сырыми байтами, разбор и сборка — здесь.
64
+ **Ядро протоколов** — `poe-svelte-ui-lib/protocols`, описание протокола — [`src/lib/protocols/PROTOCOL.md`](src/lib/protocols/PROTOCOL.md) (без Svelte, работает и под Node без Vite): общий для DevCloud, WebApp и SYP-DevTerminal CBOR-кодек (RFC 8949); `valueToCbor`/`cborToValue` — обычные значения, двоичные данные как `Uint8Array` (WebSocket, хранилище, телеметрия); `jsonToCbor` — JSON-совместимое значение в CBOR, строки base64 под ключами с `BIN` — байты (поля ввода терминала и отладки). Пакет `poe_cbor_pkt` — indefinite-массив объектов `[[TrgBusID, SrcBusID, HAR, Payload, CRC16], …]` (CAN, RS485, UART-консоль), `Payload` — одно CBOR item (сырые данные — `encodePacketBytesPayload`, пустой массив — `null`): `encodePacket`/`decodePacket` (разбор с начала буфера — `complete` с длиной, `incomplete` для потока или `garbage`; объекты одному адресату не склеиваются, порядок сохраняется, неверный CRC — флаг `crcOk`), `BUS_ID_REGEXP`/`parseBusId`/`formatBusId` — поля BusID в HEX и вывод `0x0000`, `makeHar(header, argument, reply = true)`/`splitHar` — `HAR`: заголовок в битах 14-15, аргумент в битах 1-13 (0..8191), флаг ответа в бите 0 (как `HAR`/`HAR_NR` прошивки), `packetObjectCrc`; контрольная сумма `crc16Kermit` — строго побайтная, как в `common.c` прошивок. Байты совпадают с `poe_cbor_pkt.c` прошивки — проверка `npm run TestProtocols` (`tests/protocols.test.mjs`, эталонные пакеты собраны прошивочным кодом). Протокол xPOE-Debug (UART-консоль `system_console`) — тот же пакет `poe_cbor_pkt` вперемешку с текстом загрузчика: `encodeXPoeDebugFrame(header, argument, payload, srcId, trgId, reply)` кодирует запрос, `xPoeDebugHar` — HAR по именам словаря, `createXPoeDebugParser().push(байты)` разбирает поток пакетов (каждая запись — отдельный элемент) и внешний текст (`raw`), логи `OK! DebugLog` — `[уровень, тег, текст, FHS]` (`DEBUG_LOG_ARGUMENT`, FHS — в `freeHeap`). Строка таблицы — `xPoeDebugRow`, колонки — `xPoeDebugTableColumns` (основной вход библиотеки): общие для отладки облака и терминала. Ячейка Value — `formatXPoeDebugValue` (CBOR одной строкой, `DebugLog` — уровень · тег · текст), окно по клику — `formatXPoeDebugModal`: Value в развёрнутом виде (`formatXPoeDebugPretty` — то же чистым текстом; карты и массивы с отступами, `DebugLog` — уровень и тег, ниже текст или дамп и FHS) в цветах палитры темы — ключи `--pink-color`, строки `--purple-color`, числа `--red-color`, логические `--orange-color`, `null` — `--cyan-color`, байты `--green-color`, HEX — байты `Payload` по 16 в строке; числа float32 выводятся кратчайшей записью (`0.12`). 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, пусто — пустой Payload). Графики отладки — пакет `OK! DebugPlot` (`LOG_PLOT` прошивки, `DEBUG_PLOT_ARGUMENT`): `debugPlotValues(value)` — пары `[имя, число]` из HEX CBOR-карты `Payload`, `createPlotGroups().update(пары)` раскладывает по группам (префикс до точки) в порядке карты и возвращает обновлённые группы, `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_har.h`), `parseArgumentDictionary` (строки `- [Строка, Enum, Число]`), `nameByCode`. Своих копий протоколов и словаря в приложениях не держим: терминал и отладка облака читают и пишут порт сырыми байтами, разбор и сборка — здесь.
65
65
 
66
66
  **Шапка смарт-виджета** (`WidgetHeader`): значок, заголовок и сворачивание слева, «?» в правом краю; правая часть (режимы, «Обновить» и т. п.) стоит в той же строке, а если не помещается — уходит второй строкой под заголовок и там переносится.
67
67
 
@@ -6,6 +6,6 @@ export { crc16Kermit } from "./crc.js";
6
6
  export { BUS_ID_REGEXP, parseBusId, formatBusId, makeHar, splitHar, packetObjectCrc, encodePacketBytesPayload, encodePacket, decodePacket, createPacketStreamParser, type PacketObject, type DecodedPacketObject, type PacketParse, } from "./packet.js";
7
7
  export { createPlotGroups, debugPlotValues, DEBUG_PLOT_ARGUMENT, type PlotPoint } from "./plotGroups.js";
8
8
  export { encodeXPoeDebugFrame, createXPoeDebugParser, xPoeDebugHar, xPoeDebugValueFromText, xPoeDebugRow, DEBUG_LOG_ARGUMENT, type XPoeDebugItem, type XPoeDebugRow, } from "./xPoeDebug.js";
9
- export { formatXPoeDebugValue, formatXPoeDebugValueForTable, formatXPoeDebugModal, bindXPoeDebugCopyButtons } from "./xPoeDebugView.js";
9
+ export { formatXPoeDebugValue, formatXPoeDebugValueForTable, formatXPoeDebugPretty, formatXPoeDebugModal, bindXPoeDebugCopyButtons } from "./xPoeDebugView.js";
10
10
  export { CAN_FINAL_BIT, CAN_PACKET_ID_MASK, canFragments, encodeSlcanFrame, encodeCanPacket, createCanParser, slcanOpenCommands, type CanFrame, type CanMessage, } from "./can.js";
11
11
  export { HEADER_CODES, DEFAULT_ARGUMENT_CODES, parseArgumentDictionary, nameByCode } from "./dictionary.js";
@@ -15,7 +15,7 @@ export { createPlotGroups, debugPlotValues, DEBUG_PLOT_ARGUMENT } from "./plotGr
15
15
  /* xPOE-Debug — UART-консоль как шина: пакеты вперемешку с текстом загрузчика, логи OK! DebugLog */
16
16
  export { encodeXPoeDebugFrame, createXPoeDebugParser, xPoeDebugHar, xPoeDebugValueFromText, xPoeDebugRow, DEBUG_LOG_ARGUMENT, } from "./xPoeDebug.js";
17
17
  /* Отображение Value xPOE-Debug в таблице/модалке и копирование Value/HEX */
18
- export { formatXPoeDebugValue, formatXPoeDebugValueForTable, formatXPoeDebugModal, bindXPoeDebugCopyButtons } from "./xPoeDebugView.js";
18
+ export { formatXPoeDebugValue, formatXPoeDebugValueForTable, formatXPoeDebugPretty, formatXPoeDebugModal, bindXPoeDebugCopyButtons } from "./xPoeDebugView.js";
19
19
  /* CAN/CAN FD через slcan-адаптер: нарезка пакета на кадры как в can_service прошивки, кадры slcan, потоковый разбор со сборкой фрагментов */
20
20
  export { CAN_FINAL_BIT, CAN_PACKET_ID_MASK, canFragments, encodeSlcanFrame, encodeCanPacket, createCanParser, slcanOpenCommands, } from "./can.js";
21
21
  /* Словарь заголовков и аргументов HAR — по умолчанию из мастер-словаря dictionary.yaml библиотеки, разбор файла словаря */
@@ -1,3 +1,4 @@
1
+ export declare function formatXPoeDebugPretty(hex: string): string | null;
1
2
  export declare function formatXPoeDebugValue(rawValue: string): string;
2
3
  export declare function formatXPoeDebugValueForTable(rawValue: string, isCbor?: boolean): string;
3
4
  export declare function bindXPoeDebugCopyButtons(): () => void;
@@ -3,7 +3,20 @@ const LOG_LEVELS = { 1: "🔴", 2: "🟡", 3: "🟢", 4: "🔵" };
3
3
  const HTML_ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
4
4
  const escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
5
5
  const bytesToHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0").toUpperCase()).join(" ");
6
+ /* float32 с провода — кратчайшая запись, дающая то же float32 (0.12, а не 0.12000000476837158); double и целые — как есть */
7
+ const shortNumber = (value) => {
8
+ if (Number.isInteger(value) || !Number.isFinite(value) || Math.fround(value) !== value)
9
+ return value;
10
+ for (let digits = 1; digits <= 9; digits++) {
11
+ const short = Number(value.toPrecision(digits));
12
+ if (Math.fround(short) === value)
13
+ return short;
14
+ }
15
+ return value;
16
+ };
6
17
  const normalizeCborValue = (value) => {
18
+ if (typeof value === "number")
19
+ return shortNumber(value);
7
20
  if (value instanceof Uint8Array)
8
21
  return `h'${bytesToHex(value)}'`;
9
22
  if (Array.isArray(value))
@@ -19,6 +32,91 @@ const compactCborValue = (value) => {
19
32
  return `bytes(${value.length}): ${escapeHtml(bytesToHex(value))}`;
20
33
  return escapeHtml(JSON.stringify(normalizeCborValue(value)) ?? String(value));
21
34
  };
35
+ const hexToBytes = (hex) => {
36
+ const clean = hex.replace(/\s/g, "");
37
+ if (!clean || clean.length % 2 !== 0 || !/^[0-9A-Fa-f]+$/.test(clean))
38
+ return null;
39
+ return Uint8Array.from(clean.match(/.{2}/g), (byte) => parseInt(byte, 16));
40
+ };
41
+ const isDebugLog = (value) => Array.isArray(value) &&
42
+ value.length === 4 &&
43
+ typeof value[0] === "number" &&
44
+ !!LOG_LEVELS[value[0]] &&
45
+ typeof value[1] === "string" &&
46
+ (typeof value[2] === "string" || value[2] instanceof Uint8Array);
47
+ /* Байты строками по 16 — для дампов DebugLog и раздела HEX */
48
+ const hexRows = (bytes) => (bytesToHex(bytes).match(/.{1,48}/g) ?? []).map((row) => row.trim()).join("\n");
49
+ const PRETTY_COLORS = {
50
+ key: "--pink-color",
51
+ string: "--purple-color",
52
+ number: "--red-color",
53
+ boolean: "--orange-color",
54
+ null: "--cyan-color",
55
+ bytes: "--green-color",
56
+ };
57
+ const plain = (_kind, text) => text;
58
+ const colored = (kind, text) => `<span style="color:var(${PRETTY_COLORS[kind]})">${escapeHtml(text)}</span>`;
59
+ /* Карты и массивы с отступом 2, по одному элементу на строку; скобки и запятые — без цвета */
60
+ const prettyValue = (value, paint, indent = "") => {
61
+ if (value === null || value === undefined)
62
+ return paint("null", "null");
63
+ if (typeof value === "string")
64
+ return paint("string", JSON.stringify(value));
65
+ if (typeof value === "number")
66
+ return paint("number", String(shortNumber(value)));
67
+ if (typeof value === "bigint")
68
+ return paint("number", String(value));
69
+ if (typeof value === "boolean")
70
+ return paint("boolean", String(value));
71
+ if (value instanceof Uint8Array)
72
+ return paint("bytes", `h'${bytesToHex(value)}'`);
73
+ const inner = `${indent} `;
74
+ if (Array.isArray(value)) {
75
+ if (!value.length)
76
+ return "[]";
77
+ return `[\n${value.map((item) => inner + prettyValue(item, paint, inner)).join(",\n")}\n${indent}]`;
78
+ }
79
+ const entries = value instanceof Map ? [...value.entries()] : Object.entries(value);
80
+ if (!entries.length)
81
+ return "{}";
82
+ return `{\n${entries.map(([key, item]) => `${inner}${paint("key", `${JSON.stringify(String(key))}:`)} ${prettyValue(item, paint, inner)}`).join(",\n")}\n${indent}}`;
83
+ };
84
+ /* DebugLog — уровень и тег, ниже текст или дамп по 16 байт и FHS; остальное — prettyValue */
85
+ const prettyPayload = (value, paint, html) => {
86
+ const raw = (text) => (html ? escapeHtml(text) : text);
87
+ if (isDebugLog(value)) {
88
+ const [level, tag, detail, fhs] = value;
89
+ const body = detail instanceof Uint8Array ? `${raw(`bytes(${detail.length}):`)}\n${paint("bytes", hexRows(detail))}` : raw(detail);
90
+ return [`${LOG_LEVELS[level]} ${paint("key", tag)}`, body, typeof fhs === "number" ? `FHS: ${paint("number", String(fhs))}` : ""].filter(Boolean).join("\n");
91
+ }
92
+ if (typeof value === "string")
93
+ return raw(value);
94
+ return prettyValue(value, paint);
95
+ };
96
+ /* Payload в развёрнутом виде, чистый текст: карты и массивы с отступами, DebugLog — уровень и тег, ниже текст или дамп и FHS; не CBOR — null */
97
+ export function formatXPoeDebugPretty(hex) {
98
+ const bytes = hexToBytes(hex);
99
+ if (!bytes)
100
+ return null;
101
+ try {
102
+ return prettyPayload(cborToValue(bytes), plain, false);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ /* То же в HTML с цветами палитры темы — для модалки */
109
+ const formatXPoeDebugPrettyHtml = (hex) => {
110
+ const bytes = hexToBytes(hex);
111
+ if (!bytes)
112
+ return null;
113
+ try {
114
+ return prettyPayload(cborToValue(bytes), colored, true);
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ };
22
120
  /* Payload в таблицу: CBOR как JSON, лог DebugLog [уровень, тег, текст|байты, FHS] — уровень · тег · текст */
23
121
  export function formatXPoeDebugValue(rawValue) {
24
122
  const hex = rawValue.trim().replace(/\s/g, "");
@@ -26,12 +124,7 @@ export function formatXPoeDebugValue(rawValue) {
26
124
  return escapeHtml(rawValue);
27
125
  try {
28
126
  const value = cborToValue(Uint8Array.from(hex.match(/.{2}/g), (byte) => parseInt(byte, 16)));
29
- if (Array.isArray(value) &&
30
- value.length === 4 &&
31
- typeof value[0] === "number" &&
32
- LOG_LEVELS[value[0]] &&
33
- typeof value[1] === "string" &&
34
- (typeof value[2] === "string" || value[2] instanceof Uint8Array)) {
127
+ if (isDebugLog(value)) {
35
128
  const level = LOG_LEVELS[value[0]];
36
129
  const detail = compactCborValue(value[2]);
37
130
  return `<span class="font-semibold">${level}</span> · ${escapeHtml(value[1])}${detail ? ` · ${detail}` : ""}`;
@@ -108,12 +201,15 @@ export function bindXPoeDebugCopyButtons() {
108
201
  }
109
202
  };
110
203
  }
111
- /* Две прокручиваемые секции модалки; HEX-раздел появляется, если форматтер таблицы добавил исходные байты. */
204
+ /* Две прокручиваемые секции модалки: Value — развёрнутый вид Payload в цветах палитры темы, HEX — байты Payload по 16 в строке; HEX-раздел есть, если форматтер таблицы добавил исходные байты */
112
205
  export function formatXPoeDebugModal(text) {
113
206
  const separator = "\nHEX\n";
114
207
  const hexIndex = text.lastIndexOf(separator);
115
- const decoded = hexIndex < 0 ? text : text.slice(0, hexIndex);
116
208
  const hex = hexIndex < 0 ? "" : text.slice(hexIndex + separator.length);
209
+ const pretty = hex ? formatXPoeDebugPrettyHtml(hex) : null;
210
+ const bytes = pretty === null ? null : hexToBytes(hex);
211
+ const decoded = pretty === null ? (hexIndex < 0 ? text : text.slice(0, hexIndex)) : pretty;
212
+ const hexText = bytes ? hexRows(bytes) : hex;
117
213
  const modalStyle = "display:flex;flex-direction:column;gap:.75rem;max-height:80vh;min-width:0;overflow-y:auto;overflow-x:hidden";
118
214
  const sectionStyle = "min-width:0;padding:.25rem 0 .75rem";
119
215
  const headingStyle = "display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin:0 0 .5rem;font-weight:600";
@@ -121,5 +217,5 @@ export function formatXPoeDebugModal(text) {
121
217
  const buttonClass = "flex size-7 cursor-pointer items-center justify-center rounded-lg border-none bg-transparent text-(--faint-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color)";
122
218
  const textStyle = "display:block;min-width:0;max-width:100%;margin:0;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font:inherit";
123
219
  const pane = (title, target, value) => `<section style="${sectionStyle}"><div style="${headingStyle}"><span>${title}</span><button type="button" class="${buttonClass}" data-xpoe-copy="${target}" aria-label="Копировать ${title}" style="${buttonStyle}">${COPY_ICON}</button></div><pre style="${textStyle}">${value}</pre></section>`;
124
- return `<div data-xpoe-debug-modal style="${modalStyle}">${pane("Value", "value", decoded)}${hexIndex < 0 ? "" : pane("HEX", "hex", hex)}</div>`;
220
+ return `<div data-xpoe-debug-modal style="${modalStyle}">${pane("Value", "value", decoded)}${hexIndex < 0 ? "" : pane("HEX", "hex", hexText)}</div>`;
125
221
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.77.0",
3
+ "version": "1.79.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"