@fibril/can-core 0.1.1 → 0.3.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/dist/index.d.ts +141 -466
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +124 -293
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -67,7 +67,7 @@ var AckTracker = class {
|
|
|
67
67
|
* Standard ID (11 bit, §3.1) is dynamically allocated by the master and
|
|
68
68
|
* has no fixed bitfield meaning — kept here only as the wire mask.
|
|
69
69
|
*/
|
|
70
|
-
const PROTOCOL_VERSION =
|
|
70
|
+
const PROTOCOL_VERSION = 2;
|
|
71
71
|
const MAX_FRAME_PAYLOAD = 64;
|
|
72
72
|
const CHAN_CONFIG = 0;
|
|
73
73
|
const CHAN_SVC_REQUEST = 1;
|
|
@@ -102,6 +102,18 @@ const STATE_UNPROVISIONED = 0;
|
|
|
102
102
|
const STATE_PROVISIONED = 1;
|
|
103
103
|
const STATE_RUNNING = 2;
|
|
104
104
|
const STATE_FAULT = 3;
|
|
105
|
+
const STATE_SUSPENDED = 4;
|
|
106
|
+
/** Human-readable name for a node_state byte. Unknown values → `unknown(N)`. */
|
|
107
|
+
function stateName(state) {
|
|
108
|
+
switch (state) {
|
|
109
|
+
case 0: return "UNPROVISIONED";
|
|
110
|
+
case 1: return "PROVISIONED";
|
|
111
|
+
case 2: return "RUNNING";
|
|
112
|
+
case 3: return "FAULT";
|
|
113
|
+
case 4: return "SUSPENDED";
|
|
114
|
+
default: return `unknown(${state})`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
105
117
|
const FAULT_NONE = 0;
|
|
106
118
|
const FAULT_NOMEM = 1;
|
|
107
119
|
const FAULT_INDEX_OVERFLOW = 2;
|
|
@@ -148,11 +160,7 @@ function extSeg(id) {
|
|
|
148
160
|
}
|
|
149
161
|
//#endregion
|
|
150
162
|
//#region src/dlc.ts
|
|
151
|
-
/**
|
|
152
|
-
* CAN FD valid payload sizes in ascending order (§4.3).
|
|
153
|
-
* Mirrors `fibril_can_core/include/fibril_can_core/packing.hpp::kCanFdDlcSteps`
|
|
154
|
-
* and `fibril_can_runtime/src/fcan_wire.c::fcan_quantize_dlc`.
|
|
155
|
-
*/
|
|
163
|
+
/** Valid CAN FD payload sizes, ascending. */
|
|
156
164
|
const FD_DLC_STEPS = Object.freeze([
|
|
157
165
|
0,
|
|
158
166
|
1,
|
|
@@ -171,21 +179,15 @@ const FD_DLC_STEPS = Object.freeze([
|
|
|
171
179
|
48,
|
|
172
180
|
64
|
|
173
181
|
]);
|
|
174
|
-
/**
|
|
175
|
-
* Round `bytes` up to the next valid CAN FD payload step. Returns 64 for any
|
|
176
|
-
* input ≥ 64.
|
|
177
|
-
*/
|
|
182
|
+
/** Round `bytes` up to the next valid CAN FD payload step (caps at 64). */
|
|
178
183
|
function quantizeDlc(bytes) {
|
|
179
184
|
if (bytes >= 64) return 64;
|
|
180
185
|
for (const step of FD_DLC_STEPS) if (step >= bytes) return step;
|
|
181
186
|
return 64;
|
|
182
187
|
}
|
|
183
188
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* a self-inconsistent frame the slave would misparse), the original is
|
|
187
|
-
* returned unchanged so the backend rejects it loudly (parity with
|
|
188
|
-
* `pad_to_dlc` in `fibril_can_bridge/src/config_plane_codec.cpp`).
|
|
189
|
+
* Pad `data` to the next DLC step with zeros. Over-length input (>64 B, which
|
|
190
|
+
* the slave would misparse) is returned unchanged so the backend rejects it.
|
|
189
191
|
*/
|
|
190
192
|
function padToDlc(data) {
|
|
191
193
|
if (data.length >= 64) return data;
|
|
@@ -288,13 +290,9 @@ function encodeStop(nodeId, seq) {
|
|
|
288
290
|
return configFrame(nodeId, 33, seq, /* @__PURE__ */ new Uint8Array(0));
|
|
289
291
|
}
|
|
290
292
|
/**
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* length to drop those padding bytes (parity with
|
|
295
|
-
* `append_schema_chunk` in `fibril_can_bridge/src/config_plane_codec.cpp`).
|
|
296
|
-
*
|
|
297
|
-
* Out-of-order chunks are rejected (returns 0 from {@link append}).
|
|
293
|
+
* SCHEMA_DATA reassembly. Clips each chunk against `expectedChunkLen` and
|
|
294
|
+
* the remaining blob length so DLC padding is discarded. Out-of-order
|
|
295
|
+
* chunks are rejected ({@link append} returns 0).
|
|
298
296
|
*/
|
|
299
297
|
var SchemaAccumulator = class {
|
|
300
298
|
#buf = [];
|
|
@@ -330,12 +328,8 @@ var SchemaAccumulator = class {
|
|
|
330
328
|
//#endregion
|
|
331
329
|
//#region src/scalar-codec.ts
|
|
332
330
|
/**
|
|
333
|
-
* Scalar type
|
|
334
|
-
*
|
|
335
|
-
* `fibril_can_core/src/scalar_type.cpp`.
|
|
336
|
-
*
|
|
337
|
-
* Type ordinal values are part of the protobuf schema blob's enum, so they
|
|
338
|
-
* must not be renumbered.
|
|
331
|
+
* Scalar type enum and little-endian wire codecs. The ordinal values are
|
|
332
|
+
* part of the protobuf schema blob and must not be renumbered.
|
|
339
333
|
*/
|
|
340
334
|
let ScalarType = /* @__PURE__ */ function(ScalarType) {
|
|
341
335
|
ScalarType[ScalarType["Bool"] = 0] = "Bool";
|
|
@@ -388,14 +382,9 @@ function scalarFromName(name) {
|
|
|
388
382
|
return idx >= 0 ? idx : void 0;
|
|
389
383
|
}
|
|
390
384
|
/**
|
|
391
|
-
* Pack an
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
* Pass `bigint` for 64-bit types (U64/I64). `number` is accepted for
|
|
395
|
-
* everything (it is internally widened to bigint so signed callers get the
|
|
396
|
-
* same sign-extension semantics as the C++ `pack_scalar_int_le(uint64_t)`).
|
|
397
|
-
* Signed callers pass the negative number directly; the function performs the
|
|
398
|
-
* 64-bit two's-complement cast.
|
|
385
|
+
* Pack an int/bool as little-endian into `out[offset..offset+scalarSize(type))`.
|
|
386
|
+
* `number` is widened to bigint so signed negatives sign-extend to 64-bit
|
|
387
|
+
* two's complement before truncation, matching C++ `pack_scalar_int_le`.
|
|
399
388
|
*/
|
|
400
389
|
function packScalarIntLe(type, value, out, offset = 0) {
|
|
401
390
|
const bytes = scalarSize(type);
|
|
@@ -451,7 +440,7 @@ var ProtoReader = class ProtoReader {
|
|
|
451
440
|
get position() {
|
|
452
441
|
return this.pos;
|
|
453
442
|
}
|
|
454
|
-
/** Read a base-128 varint as a JS number (
|
|
443
|
+
/** Read a base-128 varint as a JS number (schema uses uint32 only). */
|
|
455
444
|
readVarint() {
|
|
456
445
|
let result = 0;
|
|
457
446
|
let shift = 0;
|
|
@@ -489,11 +478,7 @@ var ProtoReader = class ProtoReader {
|
|
|
489
478
|
readMessage() {
|
|
490
479
|
return new ProtoReader(this.readBytes());
|
|
491
480
|
}
|
|
492
|
-
/**
|
|
493
|
-
* Read the next tag and return both the field number and wire type. The
|
|
494
|
-
* caller is expected to dispatch on field number; unknown fields can be
|
|
495
|
-
* passed to {@link skipValue}.
|
|
496
|
-
*/
|
|
481
|
+
/** Read a tag as `{field, wire}`. Unknown fields dispatch to {@link skipValue}. */
|
|
497
482
|
readTag() {
|
|
498
483
|
const tag = this.readVarint();
|
|
499
484
|
return {
|
|
@@ -531,23 +516,13 @@ var ProtoError = class extends Error {
|
|
|
531
516
|
//#endregion
|
|
532
517
|
//#region src/schema.ts
|
|
533
518
|
/**
|
|
534
|
-
* NodeSchema IR + protobuf decoder for the schema blob
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
* `
|
|
539
|
-
*
|
|
540
|
-
* Master-side notes:
|
|
541
|
-
* - The proto carries `topic_id` / `service_id` / `param_id` / BlockType
|
|
542
|
-
* `type_id` for codegen stability; the master derives equivalent indices
|
|
543
|
-
* from {@link computeNumbering} instead, so these are accepted but
|
|
544
|
-
* discarded on parse.
|
|
545
|
-
* - The proto wire field is `tx_disabled` while the IR holds `tx_enabled`
|
|
546
|
-
* (inverted, matches the C++ side).
|
|
519
|
+
* NodeSchema IR + protobuf decoder for the schema blob. Master-side notes:
|
|
520
|
+
* - Proto `topic_id` / `service_id` / `param_id` / BlockType `type_id`
|
|
521
|
+
* are accepted but discarded — the master derives indices from
|
|
522
|
+
* {@link computeNumbering}.
|
|
523
|
+
* - Proto wire `tx_disabled` inverts to IR `tx_enabled`.
|
|
547
524
|
* - `BlockArray.type_id` (proto) → `BlockArray.blockTypeIndex` (IR).
|
|
548
|
-
*
|
|
549
|
-
* The reader skips unknown fields silently so future schema additions do
|
|
550
|
-
* not break old masters.
|
|
525
|
+
* Unknown fields are skipped so future schema additions don't break masters.
|
|
551
526
|
*/
|
|
552
527
|
const FIELD = {
|
|
553
528
|
Field: {
|
|
@@ -832,13 +807,11 @@ function decodeLimits(r) {
|
|
|
832
807
|
return l;
|
|
833
808
|
}
|
|
834
809
|
/**
|
|
835
|
-
* Parse a schema blob
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
* `protocolVersion !== 1` (the only version this codec understands —
|
|
839
|
-
* mirrors C++ side's UnsupportedProtocolVersion behaviour).
|
|
810
|
+
* Parse a schema blob into the master-side IR. Throws {@link ProtoError}
|
|
811
|
+
* on malformed input or `protocolVersion === 0` (proto3 leaves an unset
|
|
812
|
+
* uint32 at 0, so zero means it was never populated).
|
|
840
813
|
*/
|
|
841
|
-
function
|
|
814
|
+
function decodeSchemaWithMeta(blob) {
|
|
842
815
|
const schema = {
|
|
843
816
|
protocolVersion: 0,
|
|
844
817
|
nodeName: "",
|
|
@@ -868,9 +841,16 @@ function decodeSchema(blob) {
|
|
|
868
841
|
default: r.skipValue(wire);
|
|
869
842
|
}
|
|
870
843
|
}
|
|
871
|
-
if (schema.protocolVersion
|
|
844
|
+
if (schema.protocolVersion === 0) throw new ProtoError(`schema protocol_version is 0 (unset)`);
|
|
872
845
|
for (const arr of schema.instances) if (arr.blockTypeIndex >= schema.types.length) throw new ProtoError(`BlockArray.type_id ${arr.blockTypeIndex} out of range (#types=${schema.types.length})`);
|
|
873
|
-
return
|
|
846
|
+
return {
|
|
847
|
+
schema,
|
|
848
|
+
versionAheadOfMaster: schema.protocolVersion > 2
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
/** Shim for callers that don't need the metadata — prefer {@link decodeSchemaWithMeta}. */
|
|
852
|
+
function decodeSchema(blob) {
|
|
853
|
+
return decodeSchemaWithMeta(blob).schema;
|
|
874
854
|
}
|
|
875
855
|
/** Wire size in bytes for a single field (scalar or fixed array). */
|
|
876
856
|
function fieldWireSize(field) {
|
|
@@ -879,20 +859,8 @@ function fieldWireSize(field) {
|
|
|
879
859
|
//#endregion
|
|
880
860
|
//#region src/dynamic-codec.ts
|
|
881
861
|
/**
|
|
882
|
-
* Field-driven dict ⇄ Uint8Array codec.
|
|
883
|
-
*
|
|
884
|
-
* function of the field list ({@link SchemaField}[]) and the JS dict.
|
|
885
|
-
*
|
|
886
|
-
* Conventions (matches CLAUDE.md "値の表現規約"):
|
|
887
|
-
* - dict keys are the schema field name verbatim
|
|
888
|
-
* - scalar ↔ JS `number`, except U64/I64 ↔ `bigint`
|
|
889
|
-
* - bool ↔ JS `boolean`
|
|
890
|
-
* - fixed-length array (`arrayLen > 0`) ↔ regular JS array
|
|
891
|
-
* (`number[]` / `bigint[]` / `boolean[]`) of exactly `arrayLen` elements
|
|
892
|
-
*
|
|
893
|
-
* Throws on missing / wrong-shape inputs. Decode tolerates extra trailing
|
|
894
|
-
* bytes (mirrors C++ side: the master ignores trailing slop so that future
|
|
895
|
-
* schema additions don't break older decoders).
|
|
862
|
+
* Field-driven dict ⇄ Uint8Array codec. Throws on shape mismatch; decode
|
|
863
|
+
* tolerates trailing bytes so future schema additions don't break decoders.
|
|
896
864
|
*/
|
|
897
865
|
var DynamicCodecError = class extends Error {
|
|
898
866
|
constructor(message) {
|
|
@@ -952,11 +920,7 @@ function fieldsWireSize(fields) {
|
|
|
952
920
|
for (const f of fields) n += fieldWireSize(f);
|
|
953
921
|
return n;
|
|
954
922
|
}
|
|
955
|
-
/**
|
|
956
|
-
* Encode a dict into the canonical little-endian wire bytes for `fields`.
|
|
957
|
-
* Returns a fresh `Uint8Array`. Throws {@link DynamicCodecError} on a
|
|
958
|
-
* missing or wrong-shape field.
|
|
959
|
-
*/
|
|
923
|
+
/** Encode a dict into little-endian wire bytes. Throws on missing / wrong-shape fields. */
|
|
960
924
|
function encodeFields(fields, value) {
|
|
961
925
|
const totalSize = fieldsWireSize(fields);
|
|
962
926
|
const out = new Uint8Array(totalSize);
|
|
@@ -983,11 +947,7 @@ function encodeFields(fields, value) {
|
|
|
983
947
|
}
|
|
984
948
|
return out;
|
|
985
949
|
}
|
|
986
|
-
/**
|
|
987
|
-
* Decode wire bytes into a dict keyed by field name. Extra trailing bytes
|
|
988
|
-
* are ignored. Throws {@link DynamicCodecError} only if the payload is
|
|
989
|
-
* shorter than the field list demands.
|
|
990
|
-
*/
|
|
950
|
+
/** Decode wire bytes into a dict. Trailing bytes are ignored; throws only if too short. */
|
|
991
951
|
function decodeFields(fields, bytes) {
|
|
992
952
|
const expected = fieldsWireSize(fields);
|
|
993
953
|
if (bytes.length < expected) throw new DynamicCodecError(`decodeFields: payload too short — need ${expected} bytes, got ${bytes.length}`);
|
|
@@ -1080,10 +1040,7 @@ var DataPlaneReceiver = class {
|
|
|
1080
1040
|
this.#now = opts.now ?? (() => Date.now());
|
|
1081
1041
|
this.#unsubFrame = opts.transport.onFrame((f) => this.#onFrame(f));
|
|
1082
1042
|
}
|
|
1083
|
-
/**
|
|
1084
|
-
* Claim a slave's S→M std_ids. Re-attaching the same nodeId atomically
|
|
1085
|
-
* detaches the prior plan first (re-provisioning case).
|
|
1086
|
-
*/
|
|
1043
|
+
/** Claim a slave's S→M std_ids. Re-attach atomically detaches the prior plan. */
|
|
1087
1044
|
attachSlave(nodeId, framePlan, index) {
|
|
1088
1045
|
this.detachSlave(nodeId);
|
|
1089
1046
|
const owned = /* @__PURE__ */ new Set();
|
|
@@ -1218,9 +1175,8 @@ var DataPlaneReceiver = class {
|
|
|
1218
1175
|
//#endregion
|
|
1219
1176
|
//#region src/frame-planner.ts
|
|
1220
1177
|
/**
|
|
1221
|
-
* 11-bit
|
|
1222
|
-
*
|
|
1223
|
-
* than wrapping — wrapping past 0x7FF would alias on the wire.
|
|
1178
|
+
* 11-bit std_id allocator with a delayed free pool. `next()` returns `null`
|
|
1179
|
+
* on exhaustion — wrapping past 0x7FF would alias on the wire.
|
|
1224
1180
|
*/
|
|
1225
1181
|
var StdIdAllocator = class {
|
|
1226
1182
|
#next = 256;
|
|
@@ -1312,9 +1268,8 @@ function emitOversizedTopic(fields, dir, periodUs, alloc, out) {
|
|
|
1312
1268
|
return flush();
|
|
1313
1269
|
}
|
|
1314
1270
|
/**
|
|
1315
|
-
* Plan a slave's frame layout. Returns `null`
|
|
1316
|
-
*
|
|
1317
|
-
* plan would silently drop fields.
|
|
1271
|
+
* Plan a slave's frame layout. Returns `null` on std_id exhaustion — a
|
|
1272
|
+
* partial plan would silently drop fields.
|
|
1318
1273
|
*/
|
|
1319
1274
|
function planFrames(requests, alloc) {
|
|
1320
1275
|
const buckets = /* @__PURE__ */ new Map();
|
|
@@ -1402,19 +1357,10 @@ function joinPath$1(ns, name) {
|
|
|
1402
1357
|
return `${ns}/${name}`;
|
|
1403
1358
|
}
|
|
1404
1359
|
/**
|
|
1405
|
-
*
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1410
|
-
* Pass `filter` to layer a runtime per-field mask on top (UI toggles,
|
|
1411
|
-
* per-slave overrides). Schema-disabled fields are unconditionally
|
|
1412
|
-
* dropped before `filter` is consulted, so callers don't have to repeat
|
|
1413
|
-
* the `txEnabled` check.
|
|
1414
|
-
*
|
|
1415
|
-
* This is the natural input to {@link planFrames} when the master just
|
|
1416
|
-
* wants to "observe everything the slave publishes" — i.e. the
|
|
1417
|
-
* sniffer-style auto-provision path.
|
|
1360
|
+
* Every schema-enabled S→M field at its declared period
|
|
1361
|
+
* (`field.periodUsOverride` ?? topic's `defaultPeriodUs`), optionally masked
|
|
1362
|
+
* further by `filter`. Feeds {@link planFrames} for the observe-everything
|
|
1363
|
+
* auto-provision path.
|
|
1418
1364
|
*/
|
|
1419
1365
|
function buildDefaultS2MRequests(schema, numbering, filter) {
|
|
1420
1366
|
const out = [];
|
|
@@ -1451,17 +1397,10 @@ function buildDefaultS2MRequests(schema, numbering, filter) {
|
|
|
1451
1397
|
return out;
|
|
1452
1398
|
}
|
|
1453
1399
|
/**
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
1458
|
-
* field-tx-enable map). With no filter, every schema-enabled S→M field is
|
|
1459
|
-
* subscribed.
|
|
1460
|
-
*
|
|
1461
|
-
* Returns `[]` when planning fails (e.g. std_id exhaustion); the caller's
|
|
1462
|
-
* NodeAgent will then leave the slave in `provisioning` → `awaiting-announce`
|
|
1463
|
-
* with no frames defined, which is the right outcome — partial plans would
|
|
1464
|
-
* silently drop fields.
|
|
1400
|
+
* FramePlanFactory subscribing to every schema-enabled S→M field (optionally
|
|
1401
|
+
* masked by `filter`) with a fresh {@link StdIdAllocator}. Returns `[]` on
|
|
1402
|
+
* planning failure so NodeAgent stalls in `awaiting-announce` — a partial
|
|
1403
|
+
* plan would silently drop fields.
|
|
1465
1404
|
*/
|
|
1466
1405
|
function defaultS2MFramePlan(schema, numbering, filter) {
|
|
1467
1406
|
const planned = planFrames(buildDefaultS2MRequests(schema, numbering, filter), new StdIdAllocator());
|
|
@@ -1512,19 +1451,13 @@ var MasterHeartbeatBroadcaster = class {
|
|
|
1512
1451
|
//#endregion
|
|
1513
1452
|
//#region src/numbering.ts
|
|
1514
1453
|
/**
|
|
1515
|
-
* Flat-numbering tables for topics, services, and parameters
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
* of (schema, instance-count-vector). The same tables are derived
|
|
1519
|
-
* independently by codegen (with max_count for static checks), the slave
|
|
1520
|
-
* runtime (with actual counts at boot) and the master (with counts from the
|
|
1521
|
-
* ANNOUNCE frame) — keeping this in one place is what guarantees they agree.
|
|
1454
|
+
* Flat-numbering tables for topics, services, and parameters — a pure
|
|
1455
|
+
* function of (schema, instance-count-vector). Codegen, slave runtime, and
|
|
1456
|
+
* master all derive these tables through the same function.
|
|
1522
1457
|
*/
|
|
1523
1458
|
const SERVICE_INDEX_CANCEL = 255;
|
|
1524
1459
|
const SERVICE_INDEX_PARAM_SET = 253;
|
|
1525
|
-
/** Maximum first-class service_index produced by numbering (inclusive). */
|
|
1526
1460
|
const SERVICE_INDEX_MAX = 252;
|
|
1527
|
-
/** Number of available first-class service slots. */
|
|
1528
1461
|
const SERVICE_INDEX_CAPACITY = 253;
|
|
1529
1462
|
let NumberingError = /* @__PURE__ */ function(NumberingError) {
|
|
1530
1463
|
NumberingError["Ok"] = "ok";
|
|
@@ -1564,12 +1497,8 @@ function buildTable(schema, counts, pick) {
|
|
|
1564
1497
|
};
|
|
1565
1498
|
}
|
|
1566
1499
|
/**
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
* malformed or service_index would overflow into the reserved range
|
|
1570
|
-
* (§4.2 §9.4).
|
|
1571
|
-
*
|
|
1572
|
-
* On error the returned tables are empty but still safe to inspect.
|
|
1500
|
+
* `actualCounts.length` must equal `schema.instances.length`. On any error
|
|
1501
|
+
* the returned tables are empty and `status !== Ok`.
|
|
1573
1502
|
*/
|
|
1574
1503
|
function computeNumbering(schema, actualCounts) {
|
|
1575
1504
|
const empty = {
|
|
@@ -1614,29 +1543,13 @@ function computeNumbering(schema, actualCounts) {
|
|
|
1614
1543
|
}
|
|
1615
1544
|
//#endregion
|
|
1616
1545
|
//#region src/schema-hash.ts
|
|
1617
|
-
/**
|
|
1618
|
-
* SHA-256 helpers for the schema blob (§5.4, §9.3).
|
|
1619
|
-
*
|
|
1620
|
-
* Both codegen (C++) and the bridge (C++) hash the entire schema blob with
|
|
1621
|
-
* SHA-256 and pack the first 8 bytes as little-endian u64 into the ANNOUNCE
|
|
1622
|
-
* frame. This module is the master-side equivalent: hash the blob we
|
|
1623
|
-
* received over SCHEMA_DATA and compare with the schema_hash the slave
|
|
1624
|
-
* advertised in ANNOUNCE.
|
|
1625
|
-
*
|
|
1626
|
-
* Uses Web Crypto (`crypto.subtle`) — available in modern browsers and Node
|
|
1627
|
-
* ≥ 19 / Deno / Bun, no polyfill needed for our target environments.
|
|
1628
|
-
*/
|
|
1629
1546
|
async function sha256(bytes) {
|
|
1630
1547
|
if (typeof crypto === "undefined" || !crypto.subtle) throw new Error("crypto.subtle is not available in this environment");
|
|
1631
1548
|
const owned = new Uint8Array(bytes);
|
|
1632
1549
|
const digest = await crypto.subtle.digest("SHA-256", owned);
|
|
1633
1550
|
return new Uint8Array(digest);
|
|
1634
1551
|
}
|
|
1635
|
-
/**
|
|
1636
|
-
* Compute the ANNOUNCE-shape schema hash: the first 8 bytes of SHA-256(blob),
|
|
1637
|
-
* interpreted as a little-endian u64. Mirrors
|
|
1638
|
-
* `fibril_can_core/sha256::hash_prefix_u64`.
|
|
1639
|
-
*/
|
|
1552
|
+
/** First 8 bytes of SHA-256(blob) as little-endian u64 — the ANNOUNCE shape. */
|
|
1640
1553
|
async function schemaHashPrefix(blob) {
|
|
1641
1554
|
const digest = await sha256(blob);
|
|
1642
1555
|
let result = 0n;
|
|
@@ -1646,18 +1559,9 @@ async function schemaHashPrefix(blob) {
|
|
|
1646
1559
|
//#endregion
|
|
1647
1560
|
//#region src/schema-index.ts
|
|
1648
1561
|
/**
|
|
1649
|
-
*
|
|
1650
|
-
*
|
|
1651
|
-
*
|
|
1652
|
-
*
|
|
1653
|
-
* Paths are local to a single slave (one {@link NodeAgent}). The block-array
|
|
1654
|
-
* `ros_namespace` is expanded by substituting `{i}` with the instance index
|
|
1655
|
-
* (mirrors `expand_i_token` in `fibril_can_bridge::node_agent_internal.hpp`).
|
|
1656
|
-
*
|
|
1657
|
-
* Two endpoints of the same kind sharing a path is a schema error — the
|
|
1658
|
-
* index throws on construction. Same path across different kinds (e.g. a
|
|
1659
|
-
* topic and a service named `motor0/foo`) is allowed because the caller
|
|
1660
|
-
* selects the kind explicitly via `topic()` / `service()` / `param()`.
|
|
1562
|
+
* Path (`<expanded_ns>/<endpoint_name>`) → descriptor lookup for one slave.
|
|
1563
|
+
* Duplicate path within the same kind throws; the same path across kinds
|
|
1564
|
+
* is allowed (caller selects via `topic()` / `service()` / `param()`).
|
|
1661
1565
|
*/
|
|
1662
1566
|
const I_TOKEN = "{i}";
|
|
1663
1567
|
function expandIToken(s, value) {
|
|
@@ -1674,10 +1578,6 @@ function sumFieldsWireSize(fields) {
|
|
|
1674
1578
|
for (const f of fields) n += fieldWireSize(f);
|
|
1675
1579
|
return n;
|
|
1676
1580
|
}
|
|
1677
|
-
/**
|
|
1678
|
-
* Path → descriptor index for one slave. Built once from
|
|
1679
|
-
* (schema, numbering) and reused for the lifetime of a snapshot.
|
|
1680
|
-
*/
|
|
1681
1581
|
var SchemaIndex = class {
|
|
1682
1582
|
#topicsByPath = /* @__PURE__ */ new Map();
|
|
1683
1583
|
#servicesByPath = /* @__PURE__ */ new Map();
|
|
@@ -1795,13 +1695,7 @@ var SchemaIndex = class {
|
|
|
1795
1695
|
};
|
|
1796
1696
|
//#endregion
|
|
1797
1697
|
//#region src/seq-allocator.ts
|
|
1798
|
-
/**
|
|
1799
|
-
* Sequential u8 allocator used for the config-plane `seq` field shared by
|
|
1800
|
-
* every command this master has issued to a given slave. Wraps at 256,
|
|
1801
|
-
* matching `BridgeNode::alloc_seq` in
|
|
1802
|
-
* `fibril_can_bridge/src/bridge_node.cpp` (the bridge wraps on the same
|
|
1803
|
-
* boundary; the AckTracker indexes by the wrapped value).
|
|
1804
|
-
*/
|
|
1698
|
+
/** Wrapping u8 counter for the config-plane `seq` byte. */
|
|
1805
1699
|
var SeqAllocator = class {
|
|
1806
1700
|
#next = 0;
|
|
1807
1701
|
next() {
|
|
@@ -1816,13 +1710,9 @@ var SeqAllocator = class {
|
|
|
1816
1710
|
//#endregion
|
|
1817
1711
|
//#region src/segmented-transfer.ts
|
|
1818
1712
|
/**
|
|
1819
|
-
*
|
|
1820
|
-
* `
|
|
1821
|
-
* `
|
|
1822
|
-
*
|
|
1823
|
-
* - ≤64 byte payload → one SINGLE frame (seg=0).
|
|
1824
|
-
* - Larger → seq=0 carries `[0]=0 [1..2]=total_len + ≤61B data`, subsequent
|
|
1825
|
-
* seqs carry `[0]=seq + ≤63B data`. Throws on payloads >1024B.
|
|
1713
|
+
* SVC_REQUEST frames for a master→slave call. ≤64 B → one SINGLE frame;
|
|
1714
|
+
* larger → seq 0 carries `[0]=0 [1..2]=total + ≤61 B data`, later seqs
|
|
1715
|
+
* carry `[0]=seq + ≤63 B data`. Throws on payloads >1024 B.
|
|
1826
1716
|
*/
|
|
1827
1717
|
function makeSvcRequestFrames(nodeId, svcIdx, callId, payload) {
|
|
1828
1718
|
if (payload.length > 1024) throw new RangeError(`service request payload ${payload.length} exceeds SEG_MAX_TOTAL (${SEG_MAX_TOTAL})`);
|
|
@@ -1863,9 +1753,8 @@ function makeSvcRequestFrames(nodeId, svcIdx, callId, payload) {
|
|
|
1863
1753
|
return out;
|
|
1864
1754
|
}
|
|
1865
1755
|
/**
|
|
1866
|
-
*
|
|
1867
|
-
* `channel` is
|
|
1868
|
-
* is `[status, ...response]` so the caller bakes status into the buffer.
|
|
1756
|
+
* Response segments (for mock slaves / TS slave runtime). `payload` must
|
|
1757
|
+
* already carry `[status, ...body]`; `channel` is typically CHAN_SVC_RESPONSE.
|
|
1869
1758
|
*/
|
|
1870
1759
|
function makeSvcSegmentedFrames(channel, nodeId, svcIdx, callId, payload) {
|
|
1871
1760
|
if (payload.length > 1024) throw new RangeError(`service response payload ${payload.length} exceeds SEG_MAX_TOTAL (${SEG_MAX_TOTAL})`);
|
|
@@ -1905,11 +1794,7 @@ function makeSvcSegmentedFrames(channel, nodeId, svcIdx, callId, payload) {
|
|
|
1905
1794
|
}
|
|
1906
1795
|
return out;
|
|
1907
1796
|
}
|
|
1908
|
-
/**
|
|
1909
|
-
* Bitmask of segment seqs required to reassemble a `totalLen`-byte payload.
|
|
1910
|
-
* Identical formula to `expected_mask` in
|
|
1911
|
-
* `fibril_can_bridge/src/node_agent.cpp` and the slave's `seg_expected_mask`.
|
|
1912
|
-
*/
|
|
1797
|
+
/** Bitmask of seg-seqs required to reassemble a `totalLen`-byte payload. */
|
|
1913
1798
|
function segExpectedMask(totalLen) {
|
|
1914
1799
|
if (totalLen <= 0) return 0;
|
|
1915
1800
|
if (totalLen <= 61) return 1;
|
|
@@ -1918,20 +1803,16 @@ function segExpectedMask(totalLen) {
|
|
|
1918
1803
|
if (nSegs >= 32) return 4294967295;
|
|
1919
1804
|
return (1 << nSegs) - 1;
|
|
1920
1805
|
}
|
|
1921
|
-
/**
|
|
1922
|
-
* Per-(svc, call_id) reassembly slot. The caller drives one of these per
|
|
1923
|
-
* outstanding incoming segmented transfer.
|
|
1924
|
-
*/
|
|
1806
|
+
/** Per-(svc, call_id) reassembly slot for one segmented transfer. */
|
|
1925
1807
|
var SegmentReassembler = class {
|
|
1926
1808
|
totalLen = 0;
|
|
1927
1809
|
rxBitmap = 0;
|
|
1928
1810
|
lastRxMs = 0;
|
|
1929
1811
|
#buffer = /* @__PURE__ */ new Uint8Array(0);
|
|
1930
1812
|
/**
|
|
1931
|
-
* Ingest one seg=1 frame
|
|
1932
|
-
*
|
|
1933
|
-
*
|
|
1934
|
-
* on protocol violations and resets the slot.
|
|
1813
|
+
* Ingest one seg=1 frame (payload with seq / total_len header attached).
|
|
1814
|
+
* Returns the assembled payload once complete, else null. Resets the slot
|
|
1815
|
+
* on protocol violation.
|
|
1935
1816
|
*/
|
|
1936
1817
|
ingest(data, nowMs) {
|
|
1937
1818
|
if (data.length === 0) return this.#reset();
|
|
@@ -2018,13 +1899,8 @@ const REASM_MAX_CONCURRENT = 16;
|
|
|
2018
1899
|
/** §7.4 reassembly TTL. */
|
|
2019
1900
|
const SEG_TIMEOUT_MS = 100;
|
|
2020
1901
|
/**
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2023
|
-
* Promise-based and AbortSignal-aware.
|
|
2024
|
-
*
|
|
2025
|
-
* One {@link ServiceClient} attaches to the transport and demuxes incoming
|
|
2026
|
-
* SVC_RESPONSE frames for a single `nodeId`. NodeAgent owns one per slave
|
|
2027
|
-
* and exposes it on the running snapshot.
|
|
1902
|
+
* Promise-based, AbortSignal-aware service-call client for one slave.
|
|
1903
|
+
* NodeAgent owns one per slave and exposes it on the running snapshot.
|
|
2028
1904
|
*/
|
|
2029
1905
|
var ServiceClient = class {
|
|
2030
1906
|
nodeId;
|
|
@@ -2189,24 +2065,8 @@ var ServiceClient = class {
|
|
|
2189
2065
|
//#endregion
|
|
2190
2066
|
//#region src/typed-param.ts
|
|
2191
2067
|
/**
|
|
2192
|
-
*
|
|
2193
|
-
*
|
|
2194
|
-
*
|
|
2195
|
-
* Per SPEC §8.1 PARAM is M→S one-way: the master holds the source of truth
|
|
2196
|
-
* and re-injects defaults at provisioning time, so this wrapper exposes
|
|
2197
|
-
* `.set()` only — there's no `.get()` that queries the slave. The
|
|
2198
|
-
* descriptor's decoded default is available via `defaultValue()` for
|
|
2199
|
-
* reference / hydration of master-side state.
|
|
2200
|
-
*
|
|
2201
|
-
* The wire shape for one PARAM_SET request is:
|
|
2202
|
-
* [0] count = 1
|
|
2203
|
-
* [1..2] u16 param_index (LE)
|
|
2204
|
-
* [3..] value (scalar little-endian or fixed-array contiguous)
|
|
2205
|
-
*
|
|
2206
|
-
* Response is `[0] status [1..2] failed_param_index (when status != OK)`.
|
|
2207
|
-
* `set()` resolves with the status code; non-OK includes `failedIndex` so
|
|
2208
|
-
* the caller can correlate with a multi-param batch (single-param sets
|
|
2209
|
-
* always blame this param).
|
|
2068
|
+
* Typed wrapper around PARAM_SET (service_index 0xFD). PARAM is M→S
|
|
2069
|
+
* one-way, so this exposes `.set()` and `.defaultValue()` only.
|
|
2210
2070
|
*/
|
|
2211
2071
|
var ParamCodecError = class extends Error {
|
|
2212
2072
|
constructor(message) {
|
|
@@ -2289,11 +2149,7 @@ var TypedParam = class {
|
|
|
2289
2149
|
this.descriptor = descriptor;
|
|
2290
2150
|
this.path = descriptor.path;
|
|
2291
2151
|
}
|
|
2292
|
-
/**
|
|
2293
|
-
* Decode the schema-embedded default value (SPEC §9.1 `default`). Useful
|
|
2294
|
-
* to seed the master-side persistence store. Returns `null` if the
|
|
2295
|
-
* descriptor carries no default payload.
|
|
2296
|
-
*/
|
|
2152
|
+
/** Decoded schema default; `null` if the descriptor carries no default. */
|
|
2297
2153
|
defaultValue() {
|
|
2298
2154
|
if (this.descriptor.defaultValue.length === 0) return null;
|
|
2299
2155
|
return unpackValue(this.descriptor, this.descriptor.defaultValue);
|
|
@@ -2351,15 +2207,10 @@ const DEFAULT_OPTS = {
|
|
|
2351
2207
|
heartbeatTimeoutMs: 350
|
|
2352
2208
|
};
|
|
2353
2209
|
/**
|
|
2354
|
-
* Per-slave provisioning state machine.
|
|
2355
|
-
* `
|
|
2356
|
-
*
|
|
2357
|
-
*
|
|
2358
|
-
*
|
|
2359
|
-
* Lifecycle: `idle` → `awaiting-announce` → `schema-reading` (skipped if
|
|
2360
|
-
* blob already cached on this agent instance) → `numbering` →
|
|
2361
|
-
* `provisioning` → `running`. HEARTBEAT loss drops to `lost`; the next
|
|
2362
|
-
* ANNOUNCE restarts the flow.
|
|
2210
|
+
* Per-slave provisioning state machine.
|
|
2211
|
+
* `idle` → `awaiting-announce` → `schema-reading` (skipped on cache hit) →
|
|
2212
|
+
* `numbering` → `provisioning` → `running`. HEARTBEAT loss drops to `lost`;
|
|
2213
|
+
* the next ANNOUNCE restarts the flow.
|
|
2363
2214
|
*/
|
|
2364
2215
|
var NodeAgent = class {
|
|
2365
2216
|
nodeId;
|
|
@@ -2420,9 +2271,8 @@ var NodeAgent = class {
|
|
|
2420
2271
|
return this.#snapshot;
|
|
2421
2272
|
}
|
|
2422
2273
|
/**
|
|
2423
|
-
* Attach
|
|
2424
|
-
*
|
|
2425
|
-
* `stop()` to detach.
|
|
2274
|
+
* Attach and wait for ANNOUNCE. Resolves immediately; progress flows via
|
|
2275
|
+
* {@link state} / {@link snapshot} / `onState`.
|
|
2426
2276
|
*/
|
|
2427
2277
|
async start() {
|
|
2428
2278
|
if (this.#stopped) throw new Error("NodeAgent.start() after stop() is not supported — construct a new agent");
|
|
@@ -2433,17 +2283,11 @@ var NodeAgent = class {
|
|
|
2433
2283
|
this.#setState({ kind: "awaiting-announce" });
|
|
2434
2284
|
}
|
|
2435
2285
|
/**
|
|
2436
|
-
* Re-run
|
|
2437
|
-
*
|
|
2438
|
-
*
|
|
2439
|
-
*
|
|
2440
|
-
*
|
|
2441
|
-
* The agent must have observed an ANNOUNCE already; `reprovision()`
|
|
2442
|
-
* before that throws. RUNNING is required so that the slave is known to
|
|
2443
|
-
* be reachable — calling during `lost` would race the heartbeat
|
|
2444
|
-
* watchdog. Pending service calls are failed and the current snapshot is
|
|
2445
|
-
* torn down before the new bring-up begins, mirroring what happens on a
|
|
2446
|
-
* boot_id change.
|
|
2286
|
+
* Re-run FRAME_DEFINE / START against the live slave with the most recent
|
|
2287
|
+
* ANNOUNCE + cached schema. Invokes {@link FramePlanFactory} again so
|
|
2288
|
+
* per-slave overrides pick up their latest values. Requires a prior
|
|
2289
|
+
* ANNOUNCE. Tears down the current snapshot and fails pending service
|
|
2290
|
+
* calls, mirroring a boot_id change.
|
|
2447
2291
|
*/
|
|
2448
2292
|
async reprovision() {
|
|
2449
2293
|
if (this.#stopped) throw new Error("NodeAgent.reprovision() after stop()");
|
|
@@ -2567,6 +2411,11 @@ var NodeAgent = class {
|
|
|
2567
2411
|
if (this.#snapshot !== null) this.#setState({ kind: "running" });
|
|
2568
2412
|
else if (this.#pendingStart !== null) this.#setState({ kind: "awaiting-start" });
|
|
2569
2413
|
}
|
|
2414
|
+
if (this.#snapshot !== null) {
|
|
2415
|
+
const isSuspended = h.state === 4;
|
|
2416
|
+
if (isSuspended && this.#state.kind === "running") this.#setState({ kind: "suspended" });
|
|
2417
|
+
else if (!isSuspended && this.#state.kind === "suspended") this.#setState({ kind: "running" });
|
|
2418
|
+
}
|
|
2570
2419
|
}
|
|
2571
2420
|
#startSchemaRead(a) {
|
|
2572
2421
|
const progress = {
|
|
@@ -2648,13 +2497,15 @@ var NodeAgent = class {
|
|
|
2648
2497
|
return;
|
|
2649
2498
|
}
|
|
2650
2499
|
let schema;
|
|
2500
|
+
let versionAheadOfMaster;
|
|
2651
2501
|
try {
|
|
2652
|
-
schema =
|
|
2502
|
+
({schema, versionAheadOfMaster} = decodeSchemaWithMeta(blob));
|
|
2653
2503
|
} catch (e) {
|
|
2654
2504
|
this.#error(e);
|
|
2655
2505
|
this.#setState({ kind: "awaiting-announce" });
|
|
2656
2506
|
return;
|
|
2657
2507
|
}
|
|
2508
|
+
if (versionAheadOfMaster) this.#error(/* @__PURE__ */ new Error(`node ${this.nodeId}: schema protocol_version=${schema.protocolVersion} exceeds master-known — proceeding with best-effort`));
|
|
2658
2509
|
this.#blobCache.set(a.schemaHash, {
|
|
2659
2510
|
blob,
|
|
2660
2511
|
schema
|
|
@@ -2738,10 +2589,8 @@ var NodeAgent = class {
|
|
|
2738
2589
|
this.#maybeIssueStart(a, schema, blob, numbering, gen, p);
|
|
2739
2590
|
}
|
|
2740
2591
|
/**
|
|
2741
|
-
*
|
|
2742
|
-
*
|
|
2743
|
-
* remembering enough context that `startNow()` can resume without
|
|
2744
|
-
* re-running FRAME_DEFINE.
|
|
2592
|
+
* Fire START if autoStart allows, else park in `awaiting-start` with
|
|
2593
|
+
* enough context that `startNow()` can resume without re-running FRAME_DEFINE.
|
|
2745
2594
|
*/
|
|
2746
2595
|
#maybeIssueStart(a, schema, blob, numbering, gen, progress) {
|
|
2747
2596
|
if (this.#autoStart()) {
|
|
@@ -2759,10 +2608,8 @@ var NodeAgent = class {
|
|
|
2759
2608
|
this.#setState({ kind: "awaiting-start" });
|
|
2760
2609
|
}
|
|
2761
2610
|
/**
|
|
2762
|
-
* Commit the deferred START
|
|
2763
|
-
*
|
|
2764
|
-
* other state throws so callers spot logic bugs instead of racing
|
|
2765
|
-
* with a fresh ANNOUNCE.
|
|
2611
|
+
* Commit the deferred START. Only legal in `awaiting-start`; throws
|
|
2612
|
+
* elsewhere so callers spot logic bugs instead of racing a fresh ANNOUNCE.
|
|
2766
2613
|
*/
|
|
2767
2614
|
async startNow() {
|
|
2768
2615
|
if (this.#stopped) throw new Error("NodeAgent.startNow() after stop()");
|
|
@@ -2886,19 +2733,12 @@ var MasterController = class {
|
|
|
2886
2733
|
this.#onError = opts.onError;
|
|
2887
2734
|
}
|
|
2888
2735
|
/**
|
|
2889
|
-
*
|
|
2890
|
-
*
|
|
2891
|
-
* `dataPlane.onTopic(nodeId, path, listener)`; read latest values with
|
|
2892
|
-
* `dataPlane.latest(nodeId, path)`.
|
|
2736
|
+
* S→M topic decoder. Auto-attaches / detaches on each slave's `running`
|
|
2737
|
+
* transition; subscribe via `dataPlane.onTopic` / read via `dataPlane.latest`.
|
|
2893
2738
|
*/
|
|
2894
2739
|
get dataPlane() {
|
|
2895
2740
|
return this.#dataPlane;
|
|
2896
2741
|
}
|
|
2897
|
-
/**
|
|
2898
|
-
* Attach to the transport, start MASTER_HEARTBEAT, fire one initial
|
|
2899
|
-
* broadcast DISCOVER to flush stale slave state from a previous master
|
|
2900
|
-
* session. New slaves auto-spawn a NodeAgent on first sight.
|
|
2901
|
-
*/
|
|
2902
2742
|
start() {
|
|
2903
2743
|
if (this.#started) return;
|
|
2904
2744
|
this.#started = true;
|
|
@@ -2939,7 +2779,7 @@ var MasterController = class {
|
|
|
2939
2779
|
schemaHash: r.schemaHash
|
|
2940
2780
|
})).sort((a, b) => a.nodeId - b.nodeId);
|
|
2941
2781
|
}
|
|
2942
|
-
/** Subscribe to slave-set / state-change updates. Fires on every change. */
|
|
2782
|
+
/** Subscribe to slave-set / state-change updates. Fires immediately with the current snapshot, then on every change. */
|
|
2943
2783
|
onChange(cb) {
|
|
2944
2784
|
this.#listeners.add(cb);
|
|
2945
2785
|
cb(this.snapshots());
|
|
@@ -2947,24 +2787,18 @@ var MasterController = class {
|
|
|
2947
2787
|
this.#listeners.delete(cb);
|
|
2948
2788
|
};
|
|
2949
2789
|
}
|
|
2950
|
-
/**
|
|
2951
|
-
* Access the NodeAgent for one slave, if it exists. Returns null until
|
|
2952
|
-
* the slave has been observed. Useful for service / param calls from
|
|
2953
|
-
* code that already knows the nodeId.
|
|
2954
|
-
*/
|
|
2790
|
+
/** NodeAgent for one slave, or null if not yet observed. */
|
|
2955
2791
|
agent(nodeId) {
|
|
2956
2792
|
return this.#slaves.get(nodeId)?.agent ?? null;
|
|
2957
2793
|
}
|
|
2958
|
-
/**
|
|
2794
|
+
/** Ad-hoc broadcast DISCOVER. */
|
|
2959
2795
|
async discoverNow() {
|
|
2960
2796
|
await this.#transport.send(encodeDiscover());
|
|
2961
2797
|
}
|
|
2962
2798
|
/**
|
|
2963
|
-
* Commit the deferred START on
|
|
2964
|
-
* `
|
|
2965
|
-
*
|
|
2966
|
-
* for a manual start; otherwise resolves once START has been queued
|
|
2967
|
-
* on the wire (the RUNNING transition arrives later via `onChange`).
|
|
2799
|
+
* Commit the deferred START on a slave parked in `awaiting-start`.
|
|
2800
|
+
* Returns `false` if the slave is unknown or not waiting; the RUNNING
|
|
2801
|
+
* transition arrives later via `onChange`.
|
|
2968
2802
|
*/
|
|
2969
2803
|
async startNode(nodeId) {
|
|
2970
2804
|
const rec = this.#slaves.get(nodeId);
|
|
@@ -2979,13 +2813,10 @@ var MasterController = class {
|
|
|
2979
2813
|
}
|
|
2980
2814
|
}
|
|
2981
2815
|
/**
|
|
2982
|
-
* Re-run
|
|
2983
|
-
*
|
|
2984
|
-
*
|
|
2985
|
-
*
|
|
2986
|
-
* ANNOUNCEd; otherwise resolves once the new bring-up has been kicked
|
|
2987
|
-
* off (it does not await the next RUNNING transition — listen via
|
|
2988
|
-
* {@link onChange} instead).
|
|
2816
|
+
* Re-run FRAME_DEFINE / START so the {@link FramePlanFactory} is consulted
|
|
2817
|
+
* again (e.g. after a UI-driven field-tx-enable change). Returns `false`
|
|
2818
|
+
* if unknown / never ANNOUNCEd; resolves once bring-up is kicked off, not
|
|
2819
|
+
* when RUNNING is reached — listen via {@link onChange}.
|
|
2989
2820
|
*/
|
|
2990
2821
|
async reprovision(nodeId) {
|
|
2991
2822
|
const rec = this.#slaves.get(nodeId);
|
|
@@ -3072,6 +2903,6 @@ var MasterController = class {
|
|
|
3072
2903
|
}
|
|
3073
2904
|
};
|
|
3074
2905
|
//#endregion
|
|
3075
|
-
export { AckTracker, CHAN_CONFIG, CHAN_SVC_REQUEST, CHAN_SVC_RESPONSE, CMD_ACK, CMD_ANNOUNCE, CMD_DISCOVER, CMD_FRAME_DEFINE, CMD_HB_PERIOD, CMD_HEARTBEAT, CMD_MASTER_HEARTBEAT, CMD_NAK, CMD_SCHEMA_DATA, CMD_SCHEMA_READ, CMD_START, CMD_STOP, DIR_RX_M2S, DIR_TX_S2M, DataPlaneReceiver, Direction, DynamicCodecError, FAULT_ID_CONFLICT, FAULT_INDEX_OVERFLOW, FAULT_NOMEM, FAULT_NONE, FD_DLC_STEPS, MAX_FRAME_PAYLOAD, MasterController, MasterHeartbeatBroadcaster, NAK_BAD_LENGTH, NAK_BAD_PARAM, NAK_BAD_STATE, NAK_DIR_MISMATCH, NAK_ID_CONFLICT, NAK_INDEX_RANGE, NAK_NO_RESOURCE, NAK_SIZE_OVERFLOW, NAK_UNKNOWN_CMD, NODE_ID_BROADCAST, NoCallSlotError, NodeAgent, NumberingError, PROTOCOL_VERSION, ParamCodecError, ProtoError, ProtoReader, SCALAR_NAMES, SEG_FIRST_DATA, SEG_MAX_SEQ, SEG_MAX_TOTAL, SEG_REST_DATA, SERVICE_INDEX_CANCEL, SERVICE_INDEX_CAPACITY, SERVICE_INDEX_MAX, SERVICE_INDEX_PARAM_SET, STATE_FAULT, STATE_PROVISIONED, STATE_RUNNING, STATE_UNPROVISIONED, STD_ID_MASK, SVC_ACCEPTED, SVC_APP_ERROR, SVC_BAD_INDEX, SVC_BAD_LENGTH, SVC_BAD_VALUE, SVC_BUSY, SVC_CANCEL, SVC_MAX_FIRST_CLASS, SVC_OK, SVC_PARAM_SET, SVC_UNAVAIL, ScalarType, SchemaAccumulator, SchemaIndex, SegmentReassembler, SeqAllocator, ServiceCallError, ServiceCancelledError, ServiceClient, ServiceTimeoutError, ServiceUnavailableError, StdIdAllocator, TypedParam, TypedService, buildDefaultS2MRequests, computeNumbering, decodeAnnounce, decodeFields, decodeHeartbeat, decodeNak, decodeSchema, decodeSchemaData, defaultS2MFramePlan, encodeDiscover, encodeDiscoverTo, encodeFields, encodeFrameDefine, encodeMasterHeartbeat, encodeParamSetSingle, encodeSchemaRead, encodeStart, encodeStop, extChannel, extIndex, extNodeId, extSeg, extSeq, fieldWireSize, fieldsWireSize, makeExtId, makeSvcRequestFrames, makeSvcSegmentedFrames, packScalarF32Le, packScalarF64Le, packScalarIntLe, padToDlc, planFrames, quantizeDlc, scalarFromName, scalarName, scalarSize, schemaHashPrefix, segExpectedMask, segOffset, sha256, signExtend, unpackScalarF32Le, unpackScalarF64Le, unpackScalarIntLe };
|
|
2906
|
+
export { AckTracker, CHAN_CONFIG, CHAN_SVC_REQUEST, CHAN_SVC_RESPONSE, CMD_ACK, CMD_ANNOUNCE, CMD_DISCOVER, CMD_FRAME_DEFINE, CMD_HB_PERIOD, CMD_HEARTBEAT, CMD_MASTER_HEARTBEAT, CMD_NAK, CMD_SCHEMA_DATA, CMD_SCHEMA_READ, CMD_START, CMD_STOP, DIR_RX_M2S, DIR_TX_S2M, DataPlaneReceiver, Direction, DynamicCodecError, FAULT_ID_CONFLICT, FAULT_INDEX_OVERFLOW, FAULT_NOMEM, FAULT_NONE, FD_DLC_STEPS, MAX_FRAME_PAYLOAD, MasterController, MasterHeartbeatBroadcaster, NAK_BAD_LENGTH, NAK_BAD_PARAM, NAK_BAD_STATE, NAK_DIR_MISMATCH, NAK_ID_CONFLICT, NAK_INDEX_RANGE, NAK_NO_RESOURCE, NAK_SIZE_OVERFLOW, NAK_UNKNOWN_CMD, NODE_ID_BROADCAST, NoCallSlotError, NodeAgent, NumberingError, PROTOCOL_VERSION, ParamCodecError, ProtoError, ProtoReader, SCALAR_NAMES, SEG_FIRST_DATA, SEG_MAX_SEQ, SEG_MAX_TOTAL, SEG_REST_DATA, SERVICE_INDEX_CANCEL, SERVICE_INDEX_CAPACITY, SERVICE_INDEX_MAX, SERVICE_INDEX_PARAM_SET, STATE_FAULT, STATE_PROVISIONED, STATE_RUNNING, STATE_SUSPENDED, STATE_UNPROVISIONED, STD_ID_MASK, SVC_ACCEPTED, SVC_APP_ERROR, SVC_BAD_INDEX, SVC_BAD_LENGTH, SVC_BAD_VALUE, SVC_BUSY, SVC_CANCEL, SVC_MAX_FIRST_CLASS, SVC_OK, SVC_PARAM_SET, SVC_UNAVAIL, ScalarType, SchemaAccumulator, SchemaIndex, SegmentReassembler, SeqAllocator, ServiceCallError, ServiceCancelledError, ServiceClient, ServiceTimeoutError, ServiceUnavailableError, StdIdAllocator, TypedParam, TypedService, buildDefaultS2MRequests, computeNumbering, decodeAnnounce, decodeFields, decodeHeartbeat, decodeNak, decodeSchema, decodeSchemaData, decodeSchemaWithMeta, defaultS2MFramePlan, encodeDiscover, encodeDiscoverTo, encodeFields, encodeFrameDefine, encodeMasterHeartbeat, encodeParamSetSingle, encodeSchemaRead, encodeStart, encodeStop, extChannel, extIndex, extNodeId, extSeg, extSeq, fieldWireSize, fieldsWireSize, makeExtId, makeSvcRequestFrames, makeSvcSegmentedFrames, packScalarF32Le, packScalarF64Le, packScalarIntLe, padToDlc, planFrames, quantizeDlc, scalarFromName, scalarName, scalarSize, schemaHashPrefix, segExpectedMask, segOffset, sha256, signExtend, stateName, unpackScalarF32Le, unpackScalarF64Le, unpackScalarIntLe };
|
|
3076
2907
|
|
|
3077
2908
|
//# sourceMappingURL=index.js.map
|