poe-svelte-ui-lib 1.74.1 → 1.74.2

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.
@@ -16,6 +16,7 @@ export declare class CborEncoder {
16
16
  encodeFloat16(value: number): this;
17
17
  encodeString(value: string): this;
18
18
  encodeBytes(value: Uint8Array | number[]): this;
19
+ encodeRaw(value: Uint8Array): this;
19
20
  encodeArrayStart(count: number | null): this;
20
21
  encodeMapStart(count: number | null): this;
21
22
  encodeBreak(): this;
@@ -211,6 +211,11 @@ export class CborEncoder {
211
211
  this.writeBytes(bytes);
212
212
  return this;
213
213
  }
214
+ /* Добавить заранее проверенный CBOR item без повторного кодирования. */
215
+ encodeRaw(value) {
216
+ this.writeBytes(Array.from(value));
217
+ return this;
218
+ }
214
219
  /* Начать кодирование массива - count элементов или null для неопределённой длины */
215
220
  encodeArrayStart(count) {
216
221
  if (count === null)
@@ -14,7 +14,7 @@ export { crc16Kermit, crc8Rohc } from "./crc.js";
14
14
  export { BROADCAST_BUS_ID, makeCmd, splitCmd, EXTERNAL_BUS_ID, packetObjectCrc, encodePacket, decodePacket, objectsFor, createPacketStreamParser, } from "./packet.js";
15
15
  /* Графики отладки: кадр OK! DebPlot (SC_PLOT прошивки) — значения из CBOR-карты, график на группу, серии в порядке устройства */
16
16
  export { createPlotGroups, debPlotValues, DEB_PLOT_ARGUMENT } from "./plotGroups.js";
17
- /* System Console over UART: CBOR [SrcID, HdrArg, Value, FHS, CRC16], plus stream parsing */
17
+ /* System Console over UART: single-record poe_cbor_pkt with debug-only FHS */
18
18
  export { encodeXPoeDebugFrame, createXPoeDebugParser, xPoeDebugCmd, xPoeDebugValueFromText } from "./xPoeDebug.js";
19
19
  /* Отображение Value xPOE-Debug в таблице/модалке и копирование Value/HEX */
20
20
  export { formatXPoeDebugValue, formatXPoeDebugValueForTable, formatXPoeDebugModal, bindXPoeDebugCopyButtons } from "./xPoeDebugView.js";
@@ -19,7 +19,8 @@ export declare const splitCmd: (cmd: number) => {
19
19
  argument: number;
20
20
  reply: boolean;
21
21
  };
22
- export declare const packetObjectCrc: (trgBusID: number, srcBusID: number, cmd: number, payload: Uint8Array) => number;
22
+ export declare const packetObjectCrc: (encodedRecordPrefix: Uint8Array) => number;
23
+ export declare const encodePacketBytesPayload: (bytes: Uint8Array) => Uint8Array;
23
24
  export declare const encodePacket: (objects: PacketObject[]) => Uint8Array;
24
25
  export type PacketParse = {
25
26
  status: "complete";
@@ -1,7 +1,6 @@
1
1
  /* $lib/protocols/packet.ts */
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) и облака */
2
+ /* Общий пакет poe_cbor_pkt: indefinite ARRAY записей [TrgBusID, SrcBusID, Cmd, Payload, CRC16].
3
+ Payload — ровно один CBOR item. CRC-16/KERMIT считается по сырым CBOR-байтам записи от её заголовка до конца Payload. */
5
4
  import { CborDecoder } from "./cbor/decoder.js";
6
5
  import { CborEncoder } from "./cbor/encoder.js";
7
6
  import { CborEndOfDataError } from "./cbor/types.js";
@@ -17,22 +16,31 @@ export const splitCmd = (cmd) => ({
17
16
  argument: (cmd >> 1) & 0x1fff,
18
17
  reply: (cmd & 1) === 1,
19
18
  });
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);
23
- return crc16Kermit(crc16Kermit(0, head), payload);
19
+ export const packetObjectCrc = (encodedRecordPrefix) => crc16Kermit(0, encodedRecordPrefix);
20
+ /* Кодирует сырые байты как один CBOR byte string для Payload. */
21
+ export const encodePacketBytesPayload = (bytes) => {
22
+ const encoder = new CborEncoder();
23
+ encoder.encodeBytes(bytes);
24
+ return encoder.getResult();
24
25
  };
25
26
  /* Пакет из одного или нескольких объектов — те же байты, что CBOR_PACK/CBOR_PktEncFinish прошивки */
26
27
  export const encodePacket = (objects) => {
27
28
  const encoder = new CborEncoder();
28
29
  encoder.encodeArrayStart(null);
29
30
  for (const { trgBusID, srcBusID, cmd, payload } of objects) {
31
+ const recordStart = encoder.getResult().length;
30
32
  encoder.encodeArrayStart(5);
31
33
  encoder.encodeUnsigned(trgBusID);
32
34
  encoder.encodeUnsigned(srcBusID);
33
35
  encoder.encodeUnsigned(cmd);
34
- encoder.encodeBytes(payload);
35
- encoder.encodeUnsigned(packetObjectCrc(trgBusID, srcBusID, cmd, payload));
36
+ const prefixStart = encoder.getResult().length;
37
+ /* Payload is already CBOR-encoded; reject malformed or concatenated values. */
38
+ const payloadDecoder = new CborDecoder(payload);
39
+ if (!payloadDecoder.skipValue() || payloadDecoder.offset !== payload.length)
40
+ throw new Error("Payload должен содержать ровно один CBOR item");
41
+ encoder.encodeRaw(payload);
42
+ const prefix = encoder.getResult().slice(recordStart, prefixStart + payload.length);
43
+ encoder.encodeUnsigned(packetObjectCrc(prefix));
36
44
  }
37
45
  encoder.encodeBreak();
38
46
  return encoder.getResult();
@@ -52,13 +60,27 @@ const readValue = (bytes, offset) => {
52
60
  };
53
61
  const toNumber = (value) => (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xffff ? value : null);
54
62
  /* Объект пакета; не по формату (не 5 элементов, не те типы) — null, как CBOR_PktDecNext прошивки его пропускает */
55
- const toObject = (value) => {
63
+ const toObject = (value, rawRecord) => {
56
64
  if (!Array.isArray(value) || value.length !== 5)
57
65
  return null;
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)
66
+ const [trgBusID, srcBusID, cmd, crc] = [toNumber(value[0]), toNumber(value[1]), toNumber(value[2]), toNumber(value[4])];
67
+ if (trgBusID === null || srcBusID === null || cmd === null || crc === null)
68
+ return null;
69
+ const info = rawRecord[0] & 0x1f;
70
+ const headerLength = info < 24 ? 1 : info === 24 ? 2 : info === 25 ? 3 : info === 26 ? 5 : info === 27 ? 9 : 0;
71
+ if ((rawRecord[0] >> 5) !== 4 || headerLength === 0)
72
+ return null;
73
+ const fields = new CborDecoder(rawRecord.subarray(headerLength));
74
+ for (let i = 0; i < 3; i++)
75
+ if (!fields.skipValue())
76
+ return null;
77
+ const payloadStart = headerLength + fields.offset;
78
+ if (!fields.skipValue())
60
79
  return null;
61
- return { trgBusID, srcBusID, cmd, payload, crc, crcOk: packetObjectCrc(trgBusID, srcBusID, cmd, payload) === crc, ...splitCmd(cmd) };
80
+ const payloadEnd = headerLength + fields.offset;
81
+ const prefix = rawRecord.subarray(0, payloadEnd);
82
+ const payloadBytes = rawRecord.subarray(payloadStart, payloadEnd);
83
+ return { trgBusID, srcBusID, cmd, payload: payloadBytes, crc, crcOk: packetObjectCrc(prefix) === crc, ...splitCmd(cmd) };
62
84
  };
63
85
  /* Разбор пакета с начала буфера — и для целого кадра (CAN), и для потока (RS485: «incomplete» — ждать ещё байт, «garbage» — сдвинуться на байт) */
64
86
  export const decodePacket = (bytes) => {
@@ -93,7 +115,7 @@ export const decodePacket = (bytes) => {
93
115
  if (!value)
94
116
  return { status: "incomplete" };
95
117
  offset += value.length;
96
- const object = toObject(value.value);
118
+ const object = toObject(value.value, bytes.subarray(offset - value.length, offset));
97
119
  if (object)
98
120
  objects.push(object);
99
121
  }
@@ -1,5 +1,6 @@
1
1
  export type XPoeDebugItem = {
2
2
  type: "frame";
3
+ trgId: number;
3
4
  srcId: number;
4
5
  cmd: number;
5
6
  header: string;
@@ -14,7 +15,7 @@ export type XPoeDebugItem = {
14
15
  text: string;
15
16
  };
16
17
  export declare const xPoeDebugCmd: (header: string, argument: string) => number;
17
- export declare const encodeXPoeDebugFrame: (header: string, argument: string, value: Uint8Array, srcId?: number, freeHeap?: number | null) => Uint8Array;
18
+ export declare const encodeXPoeDebugFrame: (header: string, argument: string, value: Uint8Array, srcId?: number, freeHeap?: number | null, trgId?: number) => Uint8Array;
18
19
  export declare const createXPoeDebugParser: () => {
19
20
  push(chunk: Uint8Array): XPoeDebugItem[];
20
21
  reset(): void;
@@ -1,5 +1,4 @@
1
- /* System Console: CBOR array [SrcID, HdrArg, Value, FHS, CRC16]. CRC-16/KERMIT covers the encoded
2
- first four array items, including the array header. UART is a stream; the definite array is self-delimiting. */
1
+ /* Debug is a one-record poe_cbor_pkt. Its record adds FHS to the common bus fields. */
3
2
  import { CborDecoder } from "./cbor/decoder.js";
4
3
  import { CborEndOfDataError } from "./cbor/types.js";
5
4
  import { crc16Kermit } from "./crc.js";
@@ -8,7 +7,8 @@ import { DEFAULT_ARGUMENT_CODES, HEADER_CODES, nameByCode } from "./dictionary.j
8
7
  import { makeCmd, splitCmd } from "./packet.js";
9
8
  const MAX_TEXT_TAIL = 4096;
10
9
  const MAX_FRAME = 8192;
11
- const ARRAY5 = 0x85;
10
+ const PACKET_START = 0x9f;
11
+ const RECORD6 = 0x86;
12
12
  const decoder = new TextDecoder();
13
13
  const encodeUInt = (input) => {
14
14
  const value = BigInt(input);
@@ -30,16 +30,16 @@ export const xPoeDebugCmd = (header, argument) => {
30
30
  return makeCmd(hdr, arg);
31
31
  };
32
32
  /* Команда терминала: CRC считается по тем CBOR-байтам, которые реально отправляются. */
33
- export const encodeXPoeDebugFrame = (header, argument, value, srcId = 0, freeHeap = null) => {
33
+ export const encodeXPoeDebugFrame = (header, argument, value, srcId = 0, freeHeap = null, trgId = 0) => {
34
34
  const cmd = xPoeDebugCmd(header, argument);
35
- if (!Number.isInteger(srcId) || srcId < 0 || srcId > 0xffff || !Number.isInteger(cmd) || cmd < 0 || cmd > 0xffff)
36
- throw new Error("SrcID и HdrArg должны быть uint16");
35
+ if (![srcId, trgId, cmd].every((n) => Number.isInteger(n) && n >= 0 && n <= 0xffff))
36
+ throw new Error("TrgBusID, SrcBusID и Cmd должны быть uint16");
37
37
  const valueParser = new CborDecoder(value);
38
38
  if (!valueParser.skipValue() || valueParser.offset !== value.length)
39
39
  throw new Error("Value должен содержать ровно один CBOR item");
40
- const prefix = new Uint8Array([ARRAY5, ...encodeUInt(srcId), ...encodeUInt(cmd), ...value, ...(freeHeap === null ? [0xf6] : encodeUInt(freeHeap))]);
41
- const crc = crc16Kermit(0, prefix);
42
- return new Uint8Array([...prefix, ...encodeUInt(crc)]);
40
+ const recordPrefix = new Uint8Array([RECORD6, ...encodeUInt(trgId), ...encodeUInt(srcId), ...encodeUInt(cmd), ...value, ...(freeHeap === null ? [0xf6] : encodeUInt(freeHeap))]);
41
+ const crc = crc16Kermit(0, recordPrefix);
42
+ return new Uint8Array([PACKET_START, ...recordPrefix, ...encodeUInt(crc), 0xff]);
43
43
  };
44
44
  const textItems = (bytes) => decoder
45
45
  .decode(bytes)
@@ -48,29 +48,30 @@ const textItems = (bytes) => decoder
48
48
  .filter((line) => line && ![...line].some((char) => char.charCodeAt(0) < 0x20 && char !== "\t"))
49
49
  .map((text) => ({ type: "raw", text }));
50
50
  const readUInt = (value) => (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xffff ? value : null);
51
- /* Декодирует одну definite array из начала буфера; null означает неполный или невалидный кадр. */
51
+ /* Декодирует пакет из одной записи с FHS; null означает неполный или невалидный кадр. */
52
52
  const frameItem = (buffer) => {
53
- if (buffer[0] !== ARRAY5)
53
+ if (buffer[0] !== PACKET_START || buffer[1] !== RECORD6)
54
54
  return { status: "garbage" };
55
55
  try {
56
56
  const cbor = new CborDecoder(buffer);
57
- cbor.decode(); /* Проверка структуры и границы всего массива */
57
+ const packet = cbor.decode(); /* Проверка структуры и границы всего пакета */
58
+ if (!Array.isArray(packet) || packet.length !== 1 || !Array.isArray(packet[0]) || packet[0].length !== 6)
59
+ return { status: "garbage" };
58
60
  const length = cbor.offset;
59
- const fields = new CborDecoder(buffer.subarray(1, length));
61
+ const fields = new CborDecoder(buffer.subarray(2, length));
62
+ const trgId = readUInt(fields.decode());
60
63
  const srcId = readUInt(fields.decode());
61
64
  const cmd = readUInt(fields.decode());
62
- if (srcId === null || cmd === null)
65
+ if (trgId === null || srcId === null || cmd === null)
63
66
  return { status: "garbage" };
64
- const valueStart = 1 + fields.offset;
67
+ const valueStart = 2 + fields.offset;
65
68
  if (!fields.skipValue())
66
69
  return { status: "garbage" };
67
- const valueEnd = 1 + fields.offset;
70
+ const valueEnd = 2 + fields.offset;
68
71
  const freeHeapValue = fields.decode();
69
- const crcStart = 1 + fields.offset;
72
+ const crcStart = 2 + fields.offset;
70
73
  const crc = readUInt(fields.decode());
71
- if (fields.offset !== length - 1 ||
72
- crc === null ||
73
- (freeHeapValue !== null && (typeof freeHeapValue !== "number" || !Number.isInteger(freeHeapValue) || freeHeapValue < 0)))
74
+ if (fields.offset !== length - 3 || crc === null || (freeHeapValue !== null && (typeof freeHeapValue !== "number" || !Number.isInteger(freeHeapValue) || freeHeapValue < 0)))
74
75
  return { status: "garbage" };
75
76
  const valueBytes = buffer.slice(valueStart, valueEnd);
76
77
  const { header, argument } = splitCmd(cmd);
@@ -79,6 +80,7 @@ const frameItem = (buffer) => {
79
80
  length,
80
81
  item: {
81
82
  type: "frame",
83
+ trgId,
82
84
  srcId,
83
85
  cmd,
84
86
  header: Object.entries(HEADER_CODES).find(([, code]) => code === header)?.[0] ?? String(header),
@@ -86,7 +88,7 @@ const frameItem = (buffer) => {
86
88
  value: Array.from(valueBytes, (byte) => byte.toString(16).padStart(2, "0").toUpperCase()).join(""),
87
89
  valueBytes,
88
90
  crc: crc.toString(16).toUpperCase().padStart(4, "0"),
89
- crcOk: crc16Kermit(0, buffer.subarray(0, crcStart)) === crc,
91
+ crcOk: crc16Kermit(0, buffer.subarray(1, crcStart)) === crc,
90
92
  freeHeap: typeof freeHeapValue === "number" ? String(freeHeapValue) : "",
91
93
  },
92
94
  };
@@ -110,7 +112,7 @@ export const createXPoeDebugParser = () => {
110
112
  buffer = concat(buffer, chunk);
111
113
  const items = [];
112
114
  while (buffer.length) {
113
- const start = buffer.indexOf(ARRAY5);
115
+ const start = buffer.indexOf(PACKET_START);
114
116
  if (start < 0) {
115
117
  const cut = buffer.length > MAX_TEXT_TAIL ? buffer.length : buffer.lastIndexOf(0x0a);
116
118
  if (cut >= 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.74.1",
3
+ "version": "1.74.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"