@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.d.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { CanFrameTx, ITransport, Unsubscribe } from "@fibril/can-transport";
|
|
2
2
|
|
|
3
3
|
//#region src/ack-tracker.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* In-flight ACK/NAK ledger keyed by `seq`. Mirrors the per-node `AckEntry`
|
|
6
|
-
* map in `fibril_can_bridge/include/fibril_can_bridge/node_agent.hpp` but
|
|
7
|
-
* pared down for the single-threaded TS master — no deadlines list, the
|
|
8
|
-
* caller polls via {@link sweep}.
|
|
9
|
-
*/
|
|
4
|
+
/** In-flight ACK/NAK ledger keyed by `seq`. Caller polls via {@link sweep}. */
|
|
10
5
|
type AckOutcome = {
|
|
11
6
|
kind: 'acked';
|
|
12
7
|
} | {
|
|
@@ -49,7 +44,7 @@ declare class AckTracker {
|
|
|
49
44
|
* Standard ID (11 bit, §3.1) is dynamically allocated by the master and
|
|
50
45
|
* has no fixed bitfield meaning — kept here only as the wire mask.
|
|
51
46
|
*/
|
|
52
|
-
declare const PROTOCOL_VERSION =
|
|
47
|
+
declare const PROTOCOL_VERSION = 2;
|
|
53
48
|
declare const MAX_FRAME_PAYLOAD = 64;
|
|
54
49
|
declare const CHAN_CONFIG = 0;
|
|
55
50
|
declare const CHAN_SVC_REQUEST = 1;
|
|
@@ -84,6 +79,9 @@ declare const STATE_UNPROVISIONED = 0;
|
|
|
84
79
|
declare const STATE_PROVISIONED = 1;
|
|
85
80
|
declare const STATE_RUNNING = 2;
|
|
86
81
|
declare const STATE_FAULT = 3;
|
|
82
|
+
declare const STATE_SUSPENDED = 4;
|
|
83
|
+
/** Human-readable name for a node_state byte. Unknown values → `unknown(N)`. */
|
|
84
|
+
declare function stateName(state: number): string;
|
|
87
85
|
declare const FAULT_NONE = 0;
|
|
88
86
|
declare const FAULT_NOMEM = 1;
|
|
89
87
|
declare const FAULT_INDEX_OVERFLOW = 2;
|
|
@@ -117,13 +115,8 @@ declare function extSeg(id: number): number;
|
|
|
117
115
|
//#endregion
|
|
118
116
|
//#region src/config-plane.d.ts
|
|
119
117
|
/**
|
|
120
|
-
* Config-plane wire codec.
|
|
121
|
-
*
|
|
122
|
-
* and `fibril_can_bridge/src/config_plane_codec.cpp`.
|
|
123
|
-
*
|
|
124
|
-
* All integers are little-endian. Decoders return `null` on malformed input
|
|
125
|
-
* (wrong length etc.); encoders produce CAN frames padded to the next DLC
|
|
126
|
-
* step, matching what the slave sees on the bus.
|
|
118
|
+
* Config-plane wire codec. All integers little-endian; decoders return `null`
|
|
119
|
+
* on malformed input; encoders produce CAN-FD frames padded to the next DLC step.
|
|
127
120
|
*/
|
|
128
121
|
/** ANNOUNCE payload (S→M, §5.4). */
|
|
129
122
|
interface AnnounceMsg {
|
|
@@ -170,13 +163,9 @@ declare function encodeFrameDefine(nodeId: number, seq: number, stdId: number, p
|
|
|
170
163
|
declare function encodeStart(nodeId: number, seq: number): CanFrameTx;
|
|
171
164
|
declare function encodeStop(nodeId: number, seq: number): CanFrameTx;
|
|
172
165
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* length to drop those padding bytes (parity with
|
|
177
|
-
* `append_schema_chunk` in `fibril_can_bridge/src/config_plane_codec.cpp`).
|
|
178
|
-
*
|
|
179
|
-
* Out-of-order chunks are rejected (returns 0 from {@link append}).
|
|
166
|
+
* SCHEMA_DATA reassembly. Clips each chunk against `expectedChunkLen` and
|
|
167
|
+
* the remaining blob length so DLC padding is discarded. Out-of-order
|
|
168
|
+
* chunks are rejected ({@link append} returns 0).
|
|
180
169
|
*/
|
|
181
170
|
declare class SchemaAccumulator {
|
|
182
171
|
#private;
|
|
@@ -194,12 +183,8 @@ declare class SchemaAccumulator {
|
|
|
194
183
|
//#endregion
|
|
195
184
|
//#region src/scalar-codec.d.ts
|
|
196
185
|
/**
|
|
197
|
-
* Scalar type
|
|
198
|
-
*
|
|
199
|
-
* `fibril_can_core/src/scalar_type.cpp`.
|
|
200
|
-
*
|
|
201
|
-
* Type ordinal values are part of the protobuf schema blob's enum, so they
|
|
202
|
-
* must not be renumbered.
|
|
186
|
+
* Scalar type enum and little-endian wire codecs. The ordinal values are
|
|
187
|
+
* part of the protobuf schema blob and must not be renumbered.
|
|
203
188
|
*/
|
|
204
189
|
declare enum ScalarType {
|
|
205
190
|
Bool = 0,
|
|
@@ -220,14 +205,9 @@ declare function scalarSize(type: ScalarType): number;
|
|
|
220
205
|
declare function scalarName(type: ScalarType): string | undefined;
|
|
221
206
|
declare function scalarFromName(name: string): ScalarType | undefined;
|
|
222
207
|
/**
|
|
223
|
-
* Pack an
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* Pass `bigint` for 64-bit types (U64/I64). `number` is accepted for
|
|
227
|
-
* everything (it is internally widened to bigint so signed callers get the
|
|
228
|
-
* same sign-extension semantics as the C++ `pack_scalar_int_le(uint64_t)`).
|
|
229
|
-
* Signed callers pass the negative number directly; the function performs the
|
|
230
|
-
* 64-bit two's-complement cast.
|
|
208
|
+
* Pack an int/bool as little-endian into `out[offset..offset+scalarSize(type))`.
|
|
209
|
+
* `number` is widened to bigint so signed negatives sign-extend to 64-bit
|
|
210
|
+
* two's complement before truncation, matching C++ `pack_scalar_int_le`.
|
|
231
211
|
*/
|
|
232
212
|
declare function packScalarIntLe(type: ScalarType, value: number | bigint, out: Uint8Array, offset?: number): void;
|
|
233
213
|
/** Bit-cast a float32 to its IEEE-754 little-endian representation. */
|
|
@@ -311,74 +291,51 @@ interface NodeSchema {
|
|
|
311
291
|
instances: SchemaBlockArray[];
|
|
312
292
|
limits: WireLimits;
|
|
313
293
|
}
|
|
294
|
+
interface DecodeSchemaResult {
|
|
295
|
+
schema: NodeSchema;
|
|
296
|
+
/** True when the blob's protocolVersion exceeds this build's {@link PROTOCOL_VERSION}. */
|
|
297
|
+
versionAheadOfMaster: boolean;
|
|
298
|
+
}
|
|
314
299
|
/**
|
|
315
|
-
* Parse a schema blob
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* `protocolVersion !== 1` (the only version this codec understands —
|
|
319
|
-
* mirrors C++ side's UnsupportedProtocolVersion behaviour).
|
|
300
|
+
* Parse a schema blob into the master-side IR. Throws {@link ProtoError}
|
|
301
|
+
* on malformed input or `protocolVersion === 0` (proto3 leaves an unset
|
|
302
|
+
* uint32 at 0, so zero means it was never populated).
|
|
320
303
|
*/
|
|
304
|
+
declare function decodeSchemaWithMeta(blob: Uint8Array): DecodeSchemaResult;
|
|
305
|
+
/** Shim for callers that don't need the metadata — prefer {@link decodeSchemaWithMeta}. */
|
|
321
306
|
declare function decodeSchema(blob: Uint8Array): NodeSchema;
|
|
322
307
|
/** Wire size in bytes for a single field (scalar or fixed array). */
|
|
323
308
|
declare function fieldWireSize(field: SchemaField): number;
|
|
324
309
|
//#endregion
|
|
325
310
|
//#region src/dynamic-codec.d.ts
|
|
326
311
|
/**
|
|
327
|
-
* Field-driven dict ⇄ Uint8Array codec.
|
|
328
|
-
*
|
|
329
|
-
* function of the field list ({@link SchemaField}[]) and the JS dict.
|
|
330
|
-
*
|
|
331
|
-
* Conventions (matches CLAUDE.md "値の表現規約"):
|
|
332
|
-
* - dict keys are the schema field name verbatim
|
|
333
|
-
* - scalar ↔ JS `number`, except U64/I64 ↔ `bigint`
|
|
334
|
-
* - bool ↔ JS `boolean`
|
|
335
|
-
* - fixed-length array (`arrayLen > 0`) ↔ regular JS array
|
|
336
|
-
* (`number[]` / `bigint[]` / `boolean[]`) of exactly `arrayLen` elements
|
|
337
|
-
*
|
|
338
|
-
* Throws on missing / wrong-shape inputs. Decode tolerates extra trailing
|
|
339
|
-
* bytes (mirrors C++ side: the master ignores trailing slop so that future
|
|
340
|
-
* schema additions don't break older decoders).
|
|
312
|
+
* Field-driven dict ⇄ Uint8Array codec. Throws on shape mismatch; decode
|
|
313
|
+
* tolerates trailing bytes so future schema additions don't break decoders.
|
|
341
314
|
*/
|
|
342
315
|
declare class DynamicCodecError extends Error {
|
|
343
316
|
constructor(message: string);
|
|
344
317
|
}
|
|
345
|
-
/** Tag union of accepted JS value shapes for one field. */
|
|
346
318
|
type FieldValue = number | bigint | boolean | number[] | bigint[] | boolean[];
|
|
347
|
-
/** Dict shape — string-keyed values, one entry per field. */
|
|
348
319
|
type Dict = Record<string, FieldValue>;
|
|
349
320
|
/**
|
|
350
321
|
* Sum the wire size of a field list. The result is the byte count that
|
|
351
322
|
* {@link encodeFields} will produce.
|
|
352
323
|
*/
|
|
353
324
|
declare function fieldsWireSize(fields: readonly SchemaField[]): number;
|
|
354
|
-
/**
|
|
355
|
-
* Encode a dict into the canonical little-endian wire bytes for `fields`.
|
|
356
|
-
* Returns a fresh `Uint8Array`. Throws {@link DynamicCodecError} on a
|
|
357
|
-
* missing or wrong-shape field.
|
|
358
|
-
*/
|
|
325
|
+
/** Encode a dict into little-endian wire bytes. Throws on missing / wrong-shape fields. */
|
|
359
326
|
declare function encodeFields(fields: readonly SchemaField[], value: Dict): Uint8Array;
|
|
360
|
-
/**
|
|
361
|
-
* Decode wire bytes into a dict keyed by field name. Extra trailing bytes
|
|
362
|
-
* are ignored. Throws {@link DynamicCodecError} only if the payload is
|
|
363
|
-
* shorter than the field list demands.
|
|
364
|
-
*/
|
|
327
|
+
/** Decode wire bytes into a dict. Trailing bytes are ignored; throws only if too short. */
|
|
365
328
|
declare function decodeFields(fields: readonly SchemaField[], bytes: Uint8Array): Dict;
|
|
366
329
|
//#endregion
|
|
367
330
|
//#region src/numbering.d.ts
|
|
368
331
|
/**
|
|
369
|
-
* Flat-numbering tables for topics, services, and parameters
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* of (schema, instance-count-vector). The same tables are derived
|
|
373
|
-
* independently by codegen (with max_count for static checks), the slave
|
|
374
|
-
* runtime (with actual counts at boot) and the master (with counts from the
|
|
375
|
-
* ANNOUNCE frame) — keeping this in one place is what guarantees they agree.
|
|
332
|
+
* Flat-numbering tables for topics, services, and parameters — a pure
|
|
333
|
+
* function of (schema, instance-count-vector). Codegen, slave runtime, and
|
|
334
|
+
* master all derive these tables through the same function.
|
|
376
335
|
*/
|
|
377
336
|
declare const SERVICE_INDEX_CANCEL = 255;
|
|
378
337
|
declare const SERVICE_INDEX_PARAM_SET = 253;
|
|
379
|
-
/** Maximum first-class service_index produced by numbering (inclusive). */
|
|
380
338
|
declare const SERVICE_INDEX_MAX = 252;
|
|
381
|
-
/** Number of available first-class service slots. */
|
|
382
339
|
declare const SERVICE_INDEX_CAPACITY: number;
|
|
383
340
|
declare enum NumberingError {
|
|
384
341
|
Ok = "ok",
|
|
@@ -388,23 +345,15 @@ declare enum NumberingError {
|
|
|
388
345
|
InstanceCountMismatch = "actual_counts length != #instances",
|
|
389
346
|
BlockTypeOutOfRange = "BlockArray::blockTypeIndex out of range"
|
|
390
347
|
}
|
|
391
|
-
/** Concrete location of one (instance, endpoint) within a NodeSchema. */
|
|
392
348
|
interface EndpointKey {
|
|
393
|
-
/** Index into NodeSchema.instances. */
|
|
394
349
|
blockArray: number;
|
|
395
|
-
/** 0..count-1 */
|
|
396
350
|
instance: number;
|
|
397
|
-
/** 0..nEndpoints(type)-1 */
|
|
398
351
|
endpoint: number;
|
|
399
352
|
}
|
|
400
|
-
/** Forward + inverse maps for a single endpoint kind. */
|
|
401
353
|
interface NumberingTable {
|
|
402
|
-
/**
|
|
403
|
-
* size = Σ_b(actualCounts[b] × nEndpoints(types[instances[b].blockTypeIndex]));
|
|
404
|
-
* the array index IS the flat numbering.
|
|
405
|
-
*/
|
|
354
|
+
/** Array index is the flat numbering. */
|
|
406
355
|
byIndex: EndpointKey[];
|
|
407
|
-
/** Per-block-array base offsets
|
|
356
|
+
/** Per-block-array base offsets. Length = #blockArrays + 1; last is total. */
|
|
408
357
|
bases: number[];
|
|
409
358
|
}
|
|
410
359
|
interface NumberingResult {
|
|
@@ -414,12 +363,8 @@ interface NumberingResult {
|
|
|
414
363
|
status: NumberingError;
|
|
415
364
|
}
|
|
416
365
|
/**
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
* malformed or service_index would overflow into the reserved range
|
|
420
|
-
* (§4.2 §9.4).
|
|
421
|
-
*
|
|
422
|
-
* On error the returned tables are empty but still safe to inspect.
|
|
366
|
+
* `actualCounts.length` must equal `schema.instances.length`. On any error
|
|
367
|
+
* the returned tables are empty and `status !== Ok`.
|
|
423
368
|
*/
|
|
424
369
|
declare function computeNumbering(schema: NodeSchema, actualCounts: readonly number[]): NumberingResult;
|
|
425
370
|
//#endregion
|
|
@@ -458,10 +403,6 @@ interface ParamDescriptor {
|
|
|
458
403
|
readonly maxValue: Uint8Array;
|
|
459
404
|
}
|
|
460
405
|
type EndpointDescriptor = TopicDescriptor | ServiceDescriptor | ParamDescriptor;
|
|
461
|
-
/**
|
|
462
|
-
* Path → descriptor index for one slave. Built once from
|
|
463
|
-
* (schema, numbering) and reused for the lifetime of a snapshot.
|
|
464
|
-
*/
|
|
465
406
|
declare class SchemaIndex {
|
|
466
407
|
#private;
|
|
467
408
|
constructor(schema: NodeSchema, numbering: NumberingResult, instanceCounts: readonly number[]);
|
|
@@ -509,13 +450,8 @@ declare class NoCallSlotError extends ServiceCallError {
|
|
|
509
450
|
constructor(serviceIndex: number);
|
|
510
451
|
}
|
|
511
452
|
/**
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
* Promise-based and AbortSignal-aware.
|
|
515
|
-
*
|
|
516
|
-
* One {@link ServiceClient} attaches to the transport and demuxes incoming
|
|
517
|
-
* SVC_RESPONSE frames for a single `nodeId`. NodeAgent owns one per slave
|
|
518
|
-
* and exposes it on the running snapshot.
|
|
453
|
+
* Promise-based, AbortSignal-aware service-call client for one slave.
|
|
454
|
+
* NodeAgent owns one per slave and exposes it on the running snapshot.
|
|
519
455
|
*/
|
|
520
456
|
declare class ServiceClient {
|
|
521
457
|
#private;
|
|
@@ -542,24 +478,8 @@ declare class ServiceClient {
|
|
|
542
478
|
//#endregion
|
|
543
479
|
//#region src/typed-param.d.ts
|
|
544
480
|
/**
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* Per SPEC §8.1 PARAM is M→S one-way: the master holds the source of truth
|
|
549
|
-
* and re-injects defaults at provisioning time, so this wrapper exposes
|
|
550
|
-
* `.set()` only — there's no `.get()` that queries the slave. The
|
|
551
|
-
* descriptor's decoded default is available via `defaultValue()` for
|
|
552
|
-
* reference / hydration of master-side state.
|
|
553
|
-
*
|
|
554
|
-
* The wire shape for one PARAM_SET request is:
|
|
555
|
-
* [0] count = 1
|
|
556
|
-
* [1..2] u16 param_index (LE)
|
|
557
|
-
* [3..] value (scalar little-endian or fixed-array contiguous)
|
|
558
|
-
*
|
|
559
|
-
* Response is `[0] status [1..2] failed_param_index (when status != OK)`.
|
|
560
|
-
* `set()` resolves with the status code; non-OK includes `failedIndex` so
|
|
561
|
-
* the caller can correlate with a multi-param batch (single-param sets
|
|
562
|
-
* always blame this param).
|
|
481
|
+
* Typed wrapper around PARAM_SET (service_index 0xFD). PARAM is M→S
|
|
482
|
+
* one-way, so this exposes `.set()` and `.defaultValue()` only.
|
|
563
483
|
*/
|
|
564
484
|
declare class ParamCodecError extends Error {
|
|
565
485
|
constructor(message: string);
|
|
@@ -569,13 +489,9 @@ interface TypedParamSetOptions {
|
|
|
569
489
|
signal?: AbortSignal;
|
|
570
490
|
}
|
|
571
491
|
interface TypedParamSetResult {
|
|
572
|
-
/**
|
|
492
|
+
/** SVC_OK = 0; non-zero = slave error code. */
|
|
573
493
|
readonly status: number;
|
|
574
|
-
/**
|
|
575
|
-
* On `status != SVC_OK`, the slave-reported param_index that failed.
|
|
576
|
-
* For a single-param call this is always `descriptor.paramIndex` if the
|
|
577
|
-
* slave fills the field, else `null` (slave omitted the field).
|
|
578
|
-
*/
|
|
494
|
+
/** Slave-reported failing param_index (non-OK only); null if omitted. */
|
|
579
495
|
readonly failedIndex: number | null;
|
|
580
496
|
}
|
|
581
497
|
/** Build a PARAM_SET request payload for a single (descriptor, value) pair. */
|
|
@@ -585,11 +501,7 @@ declare class TypedParam<T> {
|
|
|
585
501
|
readonly path: string;
|
|
586
502
|
readonly descriptor: ParamDescriptor;
|
|
587
503
|
constructor(client: ServiceClient, descriptor: ParamDescriptor);
|
|
588
|
-
/**
|
|
589
|
-
* Decode the schema-embedded default value (SPEC §9.1 `default`). Useful
|
|
590
|
-
* to seed the master-side persistence store. Returns `null` if the
|
|
591
|
-
* descriptor carries no default payload.
|
|
592
|
-
*/
|
|
504
|
+
/** Decoded schema default; `null` if the descriptor carries no default. */
|
|
593
505
|
defaultValue(): T | null;
|
|
594
506
|
/** Send a PARAM_SET with a single entry for this param. */
|
|
595
507
|
set(value: T, opts?: TypedParamSetOptions): Promise<TypedParamSetResult>;
|
|
@@ -597,20 +509,9 @@ declare class TypedParam<T> {
|
|
|
597
509
|
//#endregion
|
|
598
510
|
//#region src/typed-service.d.ts
|
|
599
511
|
/**
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
* driven entirely by the {@link ServiceDescriptor}'s field list. Callers
|
|
604
|
-
* supply `Req` / `Resp` interfaces matching the schema fields by name —
|
|
605
|
-
* the runtime trusts the dict layout and packs by field iteration order.
|
|
606
|
-
*
|
|
607
|
-
* Returned on success: `{ status, data }` where `status` is the SPEC §7.2
|
|
608
|
-
* service status (0 = OK) and `data` is the decoded response dict typed as
|
|
609
|
-
* `Resp`. Non-OK responses still return a `data` object — the slave is
|
|
610
|
-
* required to fill the response payload even on failure (§7.2), and the
|
|
611
|
-
* decoder will best-effort attempt it; if the body is shorter than the
|
|
612
|
-
* schema demands, `data` is `null` instead of throwing (so callers can
|
|
613
|
-
* surface the status to the user without losing the whole response).
|
|
512
|
+
* Result of a {@link TypedService.call}. `status` follows SPEC §7.2 (0 = OK);
|
|
513
|
+
* `data` is best-effort even on non-OK, and `null` if the response body is
|
|
514
|
+
* shorter than the schema demands.
|
|
614
515
|
*/
|
|
615
516
|
interface TypedServiceResult<Resp extends Dict> {
|
|
616
517
|
readonly status: number;
|
|
@@ -630,35 +531,20 @@ declare class TypedService<Req extends Dict, Resp extends Dict> {
|
|
|
630
531
|
}
|
|
631
532
|
//#endregion
|
|
632
533
|
//#region src/node-agent.d.ts
|
|
633
|
-
/**
|
|
634
|
-
* One planned data-plane CAN frame, supplied by the caller (typically a
|
|
635
|
-
* frame planner — P6) and consumed by {@link NodeAgent} when it issues
|
|
636
|
-
* FRAME_DEFINE during bring-up. Mirrors `FrameSpec` in
|
|
637
|
-
* `fibril_can_bridge/include/fibril_can_bridge/frame_planner.hpp`.
|
|
638
|
-
*/
|
|
534
|
+
/** One planned data-plane CAN frame consumed by FRAME_DEFINE during bring-up. */
|
|
639
535
|
interface FramePlan {
|
|
640
536
|
stdId: number;
|
|
641
537
|
periodUs: number;
|
|
642
538
|
dir: Direction;
|
|
643
539
|
entries: readonly FrameDefineEntry[];
|
|
644
540
|
}
|
|
645
|
-
/**
|
|
646
|
-
* Per-bring-up context handed to a {@link FramePlanFactory} callback so the
|
|
647
|
-
* caller can branch on the specific slave being provisioned (e.g. read an
|
|
648
|
-
* external per-nodeId field-enable override map). The same factory is
|
|
649
|
-
* shared across every {@link NodeAgent} the controller spawns, so this is
|
|
650
|
-
* the only way for the factory to discriminate slaves.
|
|
651
|
-
*/
|
|
541
|
+
/** Context handed to {@link FramePlanFactory} so it can branch on the slave. */
|
|
652
542
|
interface FramePlanContext {
|
|
653
|
-
/** The slave being provisioned (1..0x7E). */
|
|
654
543
|
readonly nodeId: number;
|
|
655
544
|
}
|
|
656
545
|
/**
|
|
657
|
-
* Builds the FRAME_DEFINE plan
|
|
658
|
-
*
|
|
659
|
-
* keep per-slave overrides (e.g. UI-controlled S→M field toggles) can fork
|
|
660
|
-
* on it without threading state through {@link NodeAgentOptions}. The
|
|
661
|
-
* 2-arg form is still accepted — extra parameters are simply ignored.
|
|
546
|
+
* Builds the FRAME_DEFINE plan once schema and numbering are known.
|
|
547
|
+
* `ctx.nodeId` lets a shared factory fork on the specific slave.
|
|
662
548
|
*/
|
|
663
549
|
type FramePlanFactory = ((schema: NodeSchema, numbering: NumberingResult, ctx: FramePlanContext) => readonly FramePlan[]) | readonly FramePlan[];
|
|
664
550
|
/** Phase machine — public for observers. */
|
|
@@ -675,17 +561,12 @@ type NodeProvisionState = {
|
|
|
675
561
|
} | {
|
|
676
562
|
kind: 'provisioning';
|
|
677
563
|
pendingDefines: number;
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* FRAME_DEFINE completed successfully but START has been withheld by
|
|
681
|
-
* the {@link NodeAgentOptions.autoStart} gate. `startNow()` moves the
|
|
682
|
-
* agent forward. `reprovision()` / a fresh ANNOUNCE re-enter the flow
|
|
683
|
-
* from bring-up.
|
|
684
|
-
*/
|
|
685
|
-
| {
|
|
564
|
+
} /** FRAME_DEFINE done, autoStart blocked START. `startNow()` moves forward. */ | {
|
|
686
565
|
kind: 'awaiting-start';
|
|
687
566
|
} | {
|
|
688
567
|
kind: 'running';
|
|
568
|
+
} /** Slave reported SUSPENDED. Reverts to running on the next non-SUSPENDED HEARTBEAT. */ | {
|
|
569
|
+
kind: 'suspended';
|
|
689
570
|
} | {
|
|
690
571
|
kind: 'lost';
|
|
691
572
|
reason: string;
|
|
@@ -707,22 +588,11 @@ interface NodeRunningSnapshot {
|
|
|
707
588
|
stdIdToTopicIndex: Map<number, number>;
|
|
708
589
|
/** §7 master-side RPC client. Stays valid for the lifetime of the snapshot. */
|
|
709
590
|
services: ServiceClient;
|
|
710
|
-
/**
|
|
711
|
-
* Path → IR descriptor lookup. Drives the typed accessors below.
|
|
712
|
-
* Exposed for sniffer-style introspection (`index.topicPaths()` etc.).
|
|
713
|
-
*/
|
|
591
|
+
/** Path → IR descriptor lookup; drives `service()` / `param()` below. */
|
|
714
592
|
index: SchemaIndex;
|
|
715
|
-
/**
|
|
716
|
-
* Schema-driven typed RPC wrapper. Throws if `path` is unknown. Generic
|
|
717
|
-
* `Req` / `Resp` are compile-time only — runtime encoding is driven by
|
|
718
|
-
* the schema's field list.
|
|
719
|
-
*/
|
|
593
|
+
/** Typed RPC wrapper. Throws if `path` is unknown. */
|
|
720
594
|
service<Req extends Dict, Resp extends Dict>(path: string): TypedService<Req, Resp>;
|
|
721
|
-
/**
|
|
722
|
-
* Schema-driven PARAM_SET wrapper. Throws if `path` is unknown.
|
|
723
|
-
* `T` is `number` for scalar params (except U64/I64 → `bigint`),
|
|
724
|
-
* `boolean` for bool, or the array form for fixed-length array params.
|
|
725
|
-
*/
|
|
595
|
+
/** Typed PARAM_SET wrapper. Throws if `path` is unknown. */
|
|
726
596
|
param<T = number | bigint | boolean | number[] | bigint[] | boolean[]>(path: string): TypedParam<T>;
|
|
727
597
|
}
|
|
728
598
|
interface NodeAgentOptions {
|
|
@@ -732,59 +602,34 @@ interface NodeAgentOptions {
|
|
|
732
602
|
transport: ITransport;
|
|
733
603
|
/** Strategy for building the FRAME_DEFINE plan. */
|
|
734
604
|
framePlan: FramePlanFactory;
|
|
735
|
-
/**
|
|
736
|
-
* Per-config-command ACK timeout in ms. Slaves must ACK within this
|
|
737
|
-
* window or the agent abandons the bring-up attempt and waits for the
|
|
738
|
-
* next ANNOUNCE. Default 250 ms (matches bridge's
|
|
739
|
-
* `bring_up_ack_timeout_`).
|
|
740
|
-
*/
|
|
605
|
+
/** Per-config-command ACK timeout in ms. Default 250. */
|
|
741
606
|
ackTimeoutMs?: number;
|
|
742
|
-
/**
|
|
743
|
-
* SCHEMA_READ chunk byte limit. SPEC §4.3 caps a single response at the
|
|
744
|
-
* SINGLE-frame payload (≤59 data bytes after the 4-byte offset header).
|
|
745
|
-
* Default 59.
|
|
746
|
-
*/
|
|
607
|
+
/** SCHEMA_READ chunk byte limit — caps at the SINGLE-frame payload. Default 59. */
|
|
747
608
|
schemaChunkSize?: number;
|
|
748
|
-
/**
|
|
749
|
-
* Multiplier on `ackTimeoutMs` used as the schema-read retry budget.
|
|
750
|
-
* Default 3 (one initial + two retries).
|
|
751
|
-
*/
|
|
609
|
+
/** SCHEMA_READ retry budget. Default 3. */
|
|
752
610
|
schemaReadRetries?: number;
|
|
753
|
-
/**
|
|
754
|
-
* Heartbeat-loss budget. The agent expects a HEARTBEAT every 100 ms
|
|
755
|
-
* (§14); after this much silence it transitions to `lost`. Default 350.
|
|
756
|
-
*/
|
|
611
|
+
/** HEARTBEAT-loss budget in ms. Default 350. */
|
|
757
612
|
heartbeatTimeoutMs?: number;
|
|
758
613
|
/** Per-call default timeout for `services.call()`. Default `ackTimeoutMs`. */
|
|
759
614
|
serviceTimeoutMs?: number;
|
|
760
615
|
/**
|
|
761
|
-
* Gate consulted
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
* Read fresh at each decision point so callers can flip the setting at
|
|
765
|
-
* runtime without reconstructing the agent. Default `() => true`.
|
|
616
|
+
* Gate consulted before each START. Returning `false` parks in
|
|
617
|
+
* `awaiting-start`; caller commits with `startNow()`. Read fresh each
|
|
618
|
+
* time so the flag can flip at runtime. Default `() => true`.
|
|
766
619
|
*/
|
|
767
620
|
autoStart?: () => boolean;
|
|
768
621
|
/** Optional state-change observer. */
|
|
769
622
|
onState?: (state: NodeProvisionState) => void;
|
|
770
|
-
/** Optional non-fatal error observer
|
|
623
|
+
/** Optional non-fatal error observer. */
|
|
771
624
|
onError?: (err: Error) => void;
|
|
772
|
-
/**
|
|
773
|
-
* Override for the wall clock. Tests pass a deterministic counter.
|
|
774
|
-
* Default `() => Date.now()`.
|
|
775
|
-
*/
|
|
625
|
+
/** Wall-clock override for tests. Default `() => Date.now()`. */
|
|
776
626
|
now?: () => number;
|
|
777
627
|
}
|
|
778
628
|
/**
|
|
779
|
-
* Per-slave provisioning state machine.
|
|
780
|
-
* `
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
* Lifecycle: `idle` → `awaiting-announce` → `schema-reading` (skipped if
|
|
785
|
-
* blob already cached on this agent instance) → `numbering` →
|
|
786
|
-
* `provisioning` → `running`. HEARTBEAT loss drops to `lost`; the next
|
|
787
|
-
* ANNOUNCE restarts the flow.
|
|
629
|
+
* Per-slave provisioning state machine.
|
|
630
|
+
* `idle` → `awaiting-announce` → `schema-reading` (skipped on cache hit) →
|
|
631
|
+
* `numbering` → `provisioning` → `running`. HEARTBEAT loss drops to `lost`;
|
|
632
|
+
* the next ANNOUNCE restarts the flow.
|
|
788
633
|
*/
|
|
789
634
|
declare class NodeAgent {
|
|
790
635
|
#private;
|
|
@@ -795,59 +640,32 @@ declare class NodeAgent {
|
|
|
795
640
|
get state(): NodeProvisionState;
|
|
796
641
|
get snapshot(): NodeRunningSnapshot | null;
|
|
797
642
|
/**
|
|
798
|
-
* Attach
|
|
799
|
-
*
|
|
800
|
-
* `stop()` to detach.
|
|
643
|
+
* Attach and wait for ANNOUNCE. Resolves immediately; progress flows via
|
|
644
|
+
* {@link state} / {@link snapshot} / `onState`.
|
|
801
645
|
*/
|
|
802
646
|
start(): Promise<void>;
|
|
803
647
|
/**
|
|
804
|
-
* Re-run
|
|
805
|
-
*
|
|
806
|
-
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
* The agent must have observed an ANNOUNCE already; `reprovision()`
|
|
810
|
-
* before that throws. RUNNING is required so that the slave is known to
|
|
811
|
-
* be reachable — calling during `lost` would race the heartbeat
|
|
812
|
-
* watchdog. Pending service calls are failed and the current snapshot is
|
|
813
|
-
* torn down before the new bring-up begins, mirroring what happens on a
|
|
814
|
-
* boot_id change.
|
|
648
|
+
* Re-run FRAME_DEFINE / START against the live slave with the most recent
|
|
649
|
+
* ANNOUNCE + cached schema. Invokes {@link FramePlanFactory} again so
|
|
650
|
+
* per-slave overrides pick up their latest values. Requires a prior
|
|
651
|
+
* ANNOUNCE. Tears down the current snapshot and fails pending service
|
|
652
|
+
* calls, mirroring a boot_id change.
|
|
815
653
|
*/
|
|
816
654
|
reprovision(): Promise<void>;
|
|
817
655
|
/** Detach, send STOP if currently RUNNING, clear all in-flight state. */
|
|
818
656
|
stop(): Promise<void>;
|
|
819
657
|
/**
|
|
820
|
-
* Commit the deferred START
|
|
821
|
-
*
|
|
822
|
-
* other state throws so callers spot logic bugs instead of racing
|
|
823
|
-
* with a fresh ANNOUNCE.
|
|
658
|
+
* Commit the deferred START. Only legal in `awaiting-start`; throws
|
|
659
|
+
* elsewhere so callers spot logic bugs instead of racing a fresh ANNOUNCE.
|
|
824
660
|
*/
|
|
825
661
|
startNow(): Promise<void>;
|
|
826
662
|
}
|
|
827
663
|
//#endregion
|
|
828
664
|
//#region src/data-plane.d.ts
|
|
829
665
|
/**
|
|
830
|
-
* Master-side decoder for S→M data-plane frames.
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
* the std_ids configured in any attached slave's {@link FramePlan}. For
|
|
834
|
-
* each match, unpacks the topic entries packed into the frame's payload
|
|
835
|
-
* (in FRAME_DEFINE order) using the schema field bitmask and publishes
|
|
836
|
-
* a decoded {@link Dict} per topic to subscribers.
|
|
837
|
-
*
|
|
838
|
-
* Lifecycle:
|
|
839
|
-
* - `new DataPlaneReceiver({ transport })` registers an `onFrame` tap.
|
|
840
|
-
* - `attachSlave(nodeId, framePlan, schemaIndex)` claims a slave's std_ids.
|
|
841
|
-
* - `detachSlave(nodeId)` releases them (call on RUNNING → not-RUNNING).
|
|
842
|
-
* - `dispose()` unsubscribes and clears all state.
|
|
843
|
-
*
|
|
844
|
-
* Two slaves cannot publish on the same std_id — attaching the second
|
|
845
|
-
* throws. This mirrors the bus-level reality: identical std_ids would
|
|
846
|
-
* collide on the wire.
|
|
847
|
-
*
|
|
848
|
-
* M→S frames (master output, dir `Direction.M2S`) are skipped during
|
|
849
|
-
* attach so the receiver doesn't try to decode echo or self-loopback
|
|
850
|
-
* frames as if they were slave-published.
|
|
666
|
+
* Master-side decoder for S→M data-plane frames. `attachSlave` claims a
|
|
667
|
+
* slave's std_ids; two slaves cannot share a std_id (throws). M→S frames
|
|
668
|
+
* are skipped so echo / self-loopback isn't decoded as slave-published.
|
|
851
669
|
*/
|
|
852
670
|
interface DataPlaneReceiverOptions {
|
|
853
671
|
transport: ITransport;
|
|
@@ -862,23 +680,16 @@ interface TopicSample {
|
|
|
862
680
|
readonly tsMs: number;
|
|
863
681
|
}
|
|
864
682
|
interface TopicStats {
|
|
865
|
-
/** Frames ever decoded into this topic since the slave attached. */
|
|
866
683
|
readonly count: number;
|
|
867
684
|
/** `now()` of the most recent frame. */
|
|
868
685
|
readonly lastRxMs: number;
|
|
869
|
-
/**
|
|
870
|
-
* Rolling rate over the last few samples (Hz). 0 until at least
|
|
871
|
-
* two frames have been seen.
|
|
872
|
-
*/
|
|
686
|
+
/** Rolling rate in Hz over the last {@link RATE_WINDOW} samples; 0 with <2 samples. */
|
|
873
687
|
readonly ratePerSec: number;
|
|
874
688
|
}
|
|
875
689
|
declare class DataPlaneReceiver {
|
|
876
690
|
#private;
|
|
877
691
|
constructor(opts: DataPlaneReceiverOptions);
|
|
878
|
-
/**
|
|
879
|
-
* Claim a slave's S→M std_ids. Re-attaching the same nodeId atomically
|
|
880
|
-
* detaches the prior plan first (re-provisioning case).
|
|
881
|
-
*/
|
|
692
|
+
/** Claim a slave's S→M std_ids. Re-attach atomically detaches the prior plan. */
|
|
882
693
|
attachSlave(nodeId: number, framePlan: readonly FramePlan[], index: SchemaIndex): void;
|
|
883
694
|
/** Release a slave's std_ids and drop its cached topic samples. */
|
|
884
695
|
detachSlave(nodeId: number): void;
|
|
@@ -894,19 +705,10 @@ declare class DataPlaneReceiver {
|
|
|
894
705
|
//#endregion
|
|
895
706
|
//#region src/frame-planner.d.ts
|
|
896
707
|
/**
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
* - Group requests by (direction, period_us); never combine across either.
|
|
902
|
-
* - Inside a bucket, bin-pack whole topics into ≤64-byte frames.
|
|
903
|
-
* - A single topic whose declared fields exceed 64 bytes is the sole
|
|
904
|
-
* field-split exception (`emitOversizedTopic`).
|
|
905
|
-
*
|
|
906
|
-
* Determinism is load-bearing: codegen, bridge, and this TS master must
|
|
907
|
-
* produce identical std_id assignments for the same input. Both the
|
|
908
|
-
* bucket map and the per-bucket topic map iterate in ascending key order
|
|
909
|
-
* (matching `std::map` in the C++ reference).
|
|
708
|
+
* Field-requests → FRAME_DEFINE bodies. Groups by (dir, period_us) and
|
|
709
|
+
* bin-packs whole topics into ≤64-byte frames; a topic that exceeds 64 B
|
|
710
|
+
* is the sole field-split exception. Iteration order is ascending on
|
|
711
|
+
* every level so codegen / bridge / master produce identical std_ids.
|
|
910
712
|
*/
|
|
911
713
|
interface FieldRequest {
|
|
912
714
|
topicIndex: number;
|
|
@@ -926,9 +728,8 @@ interface FrameSpec {
|
|
|
926
728
|
entries: FrameDefineEntry[];
|
|
927
729
|
}
|
|
928
730
|
/**
|
|
929
|
-
* 11-bit
|
|
930
|
-
*
|
|
931
|
-
* than wrapping — wrapping past 0x7FF would alias on the wire.
|
|
731
|
+
* 11-bit std_id allocator with a delayed free pool. `next()` returns `null`
|
|
732
|
+
* on exhaustion — wrapping past 0x7FF would alias on the wire.
|
|
932
733
|
*/
|
|
933
734
|
declare class StdIdAllocator {
|
|
934
735
|
#private;
|
|
@@ -937,26 +738,16 @@ declare class StdIdAllocator {
|
|
|
937
738
|
reset(start?: number): void;
|
|
938
739
|
}
|
|
939
740
|
/**
|
|
940
|
-
* Plan a slave's frame layout. Returns `null`
|
|
941
|
-
*
|
|
942
|
-
* plan would silently drop fields.
|
|
741
|
+
* Plan a slave's frame layout. Returns `null` on std_id exhaustion — a
|
|
742
|
+
* partial plan would silently drop fields.
|
|
943
743
|
*/
|
|
944
744
|
declare function planFrames(requests: readonly FieldRequest[], alloc: StdIdAllocator): FrameSpec[] | null;
|
|
945
745
|
//#endregion
|
|
946
746
|
//#region src/default-plan.d.ts
|
|
947
747
|
/**
|
|
948
|
-
*
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
952
|
-
* Contract:
|
|
953
|
-
* - Returning `false` skips the field entirely; the slave will not pack
|
|
954
|
-
* it into any S→M frame. Returning `true` keeps it.
|
|
955
|
-
* - Called only for S→M fields whose schema declares `txEnabled !==
|
|
956
|
-
* false` (schema-disabled fields are filtered out unconditionally).
|
|
957
|
-
* - `topicPath` is the same `'<expanded_ns>/<topic_name>'` string the
|
|
958
|
-
* master uses elsewhere (mirrors {@link SchemaIndex.topicPaths}), so
|
|
959
|
-
* UI override maps can key on the same identifier.
|
|
748
|
+
* Predicate for masking S→M fields on top of the schema-declared `txEnabled`.
|
|
749
|
+
* Only called for fields with `txEnabled !== false`. Return `false` to drop
|
|
750
|
+
* the field. `topicPath` mirrors {@link SchemaIndex.topicPaths}.
|
|
960
751
|
*/
|
|
961
752
|
type S2MFieldFilter = (info: {
|
|
962
753
|
topicPath: string;
|
|
@@ -966,84 +757,37 @@ type S2MFieldFilter = (info: {
|
|
|
966
757
|
field: SchemaField;
|
|
967
758
|
}) => boolean;
|
|
968
759
|
/**
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
* Pass `filter` to layer a runtime per-field mask on top (UI toggles,
|
|
975
|
-
* per-slave overrides). Schema-disabled fields are unconditionally
|
|
976
|
-
* dropped before `filter` is consulted, so callers don't have to repeat
|
|
977
|
-
* the `txEnabled` check.
|
|
978
|
-
*
|
|
979
|
-
* This is the natural input to {@link planFrames} when the master just
|
|
980
|
-
* wants to "observe everything the slave publishes" — i.e. the
|
|
981
|
-
* sniffer-style auto-provision path.
|
|
760
|
+
* Every schema-enabled S→M field at its declared period
|
|
761
|
+
* (`field.periodUsOverride` ?? topic's `defaultPeriodUs`), optionally masked
|
|
762
|
+
* further by `filter`. Feeds {@link planFrames} for the observe-everything
|
|
763
|
+
* auto-provision path.
|
|
982
764
|
*/
|
|
983
765
|
declare function buildDefaultS2MRequests(schema: NodeSchema, numbering: NumberingResult, filter?: S2MFieldFilter): FieldRequest[];
|
|
984
766
|
/**
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
* field-tx-enable map). With no filter, every schema-enabled S→M field is
|
|
990
|
-
* subscribed.
|
|
991
|
-
*
|
|
992
|
-
* Returns `[]` when planning fails (e.g. std_id exhaustion); the caller's
|
|
993
|
-
* NodeAgent will then leave the slave in `provisioning` → `awaiting-announce`
|
|
994
|
-
* with no frames defined, which is the right outcome — partial plans would
|
|
995
|
-
* silently drop fields.
|
|
767
|
+
* FramePlanFactory subscribing to every schema-enabled S→M field (optionally
|
|
768
|
+
* masked by `filter`) with a fresh {@link StdIdAllocator}. Returns `[]` on
|
|
769
|
+
* planning failure so NodeAgent stalls in `awaiting-announce` — a partial
|
|
770
|
+
* plan would silently drop fields.
|
|
996
771
|
*/
|
|
997
772
|
declare function defaultS2MFramePlan(schema: NodeSchema, numbering: NumberingResult, filter?: S2MFieldFilter): FramePlan[];
|
|
998
773
|
//#endregion
|
|
999
774
|
//#region src/dlc.d.ts
|
|
1000
|
-
/**
|
|
1001
|
-
* CAN FD valid payload sizes in ascending order (§4.3).
|
|
1002
|
-
* Mirrors `fibril_can_core/include/fibril_can_core/packing.hpp::kCanFdDlcSteps`
|
|
1003
|
-
* and `fibril_can_runtime/src/fcan_wire.c::fcan_quantize_dlc`.
|
|
1004
|
-
*/
|
|
775
|
+
/** Valid CAN FD payload sizes, ascending. */
|
|
1005
776
|
declare const FD_DLC_STEPS: readonly number[];
|
|
1006
|
-
/**
|
|
1007
|
-
* Round `bytes` up to the next valid CAN FD payload step. Returns 64 for any
|
|
1008
|
-
* input ≥ 64.
|
|
1009
|
-
*/
|
|
777
|
+
/** Round `bytes` up to the next valid CAN FD payload step (caps at 64). */
|
|
1010
778
|
declare function quantizeDlc(bytes: number): number;
|
|
1011
779
|
/**
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
* a self-inconsistent frame the slave would misparse), the original is
|
|
1015
|
-
* returned unchanged so the backend rejects it loudly (parity with
|
|
1016
|
-
* `pad_to_dlc` in `fibril_can_bridge/src/config_plane_codec.cpp`).
|
|
780
|
+
* Pad `data` to the next DLC step with zeros. Over-length input (>64 B, which
|
|
781
|
+
* the slave would misparse) is returned unchanged so the backend rejects it.
|
|
1017
782
|
*/
|
|
1018
783
|
declare function padToDlc(data: Uint8Array): Uint8Array;
|
|
1019
784
|
//#endregion
|
|
1020
785
|
//#region src/master-controller.d.ts
|
|
1021
786
|
/**
|
|
1022
|
-
* Multi-slave coordinator.
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
1025
|
-
*
|
|
1026
|
-
* - one {@link MasterHeartbeatBroadcaster} (shared across slaves)
|
|
1027
|
-
* - a single broadcast DISCOVER at start() to flush every slave's frame
|
|
1028
|
-
* table and pull fresh ANNOUNCEs onto the bus
|
|
1029
|
-
* - a `Map<nodeId, NodeAgent>` of provisioned slaves — auto-spawned on
|
|
1030
|
-
* the first ANNOUNCE / HEARTBEAT from a previously-unseen nodeId
|
|
1031
|
-
*
|
|
1032
|
-
* Periodic broadcast DISCOVER is intentionally NOT done. Broadcast
|
|
1033
|
-
* DISCOVER is destructive (§5.11): every receiving slave clears its frame
|
|
1034
|
-
* table and drops to UNPROVISIONED. Late-joining slaves are covered by
|
|
1035
|
-
* their own spontaneous ANNOUNCE × 3 at boot (§5.4); the user-initiated
|
|
1036
|
-
* "Discover" button (`discoverNow()`) is the escape hatch for the rare
|
|
1037
|
-
* case where the master came up first and missed the burst.
|
|
1038
|
-
*
|
|
1039
|
-
* Provisioning per slave still goes through {@link NodeAgent}. The
|
|
1040
|
-
* controller is the right place to add cross-slave concerns later (PARAM
|
|
1041
|
-
* persistence, name ownership, lab-mode multiplexers) without bloating
|
|
1042
|
-
* `NodeAgent`.
|
|
1043
|
-
*
|
|
1044
|
-
* The controller does NOT replace `NodeAgent` for single-slave embedded
|
|
1045
|
-
* use cases (e.g. tests, a fixed-topology setup); it sits one layer above
|
|
1046
|
-
* and is the natural entrypoint for the Web UI.
|
|
787
|
+
* Multi-slave coordinator. Owns the transport, one MASTER_HEARTBEAT
|
|
788
|
+
* broadcaster, and a `Map<nodeId, NodeAgent>` populated on first sight of
|
|
789
|
+
* ANNOUNCE / HEARTBEAT. Broadcast DISCOVER is destructive so it is fired
|
|
790
|
+
* only once at start(); use `discoverNow()` on demand.
|
|
1047
791
|
*/
|
|
1048
792
|
/** Default policy hooks applied to every spawned NodeAgent. */
|
|
1049
793
|
interface MasterControllerOptions {
|
|
@@ -1054,20 +798,11 @@ interface MasterControllerOptions {
|
|
|
1054
798
|
/** MASTER_HEARTBEAT broadcast period in ms. Default 100 (§5.11). */
|
|
1055
799
|
heartbeatPeriodMs?: number;
|
|
1056
800
|
/**
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
* frame table and forces re-provisioning, breaking S→M topic flow until
|
|
1060
|
-
* each NodeAgent re-pushes its FRAME_DEFINEs. Useful only for very
|
|
1061
|
-
* niche reconnect-storm scenarios; prefer `discoverNow()` for on-demand
|
|
1062
|
-
* rediscovery and rely on slave spontaneous ANNOUNCE × 3 (§5.4) for
|
|
1063
|
-
* normal late-join.
|
|
801
|
+
* Periodic broadcast DISCOVER period in ms. Default 0 (disabled). Each
|
|
802
|
+
* firing wipes every slave's frame table — prefer `discoverNow()`.
|
|
1064
803
|
*/
|
|
1065
804
|
discoverPeriodMs?: number;
|
|
1066
|
-
/**
|
|
1067
|
-
* Send a single broadcast DISCOVER inside `start()` to flush stale
|
|
1068
|
-
* slave state from a previous master session. Default true. Set false
|
|
1069
|
-
* in tests that drive ANNOUNCE manually and need a quiet bus.
|
|
1070
|
-
*/
|
|
805
|
+
/** One broadcast DISCOVER inside `start()` to flush stale slaves. Default true. */
|
|
1071
806
|
initialDiscover?: boolean;
|
|
1072
807
|
/** Per-NodeAgent ACK timeout in ms. Default 250 (matches NodeAgent). */
|
|
1073
808
|
ackTimeoutMs?: number;
|
|
@@ -1075,12 +810,7 @@ interface MasterControllerOptions {
|
|
|
1075
810
|
heartbeatTimeoutMs?: number;
|
|
1076
811
|
/** Per-service-call default timeout in ms. Default `ackTimeoutMs`. */
|
|
1077
812
|
serviceTimeoutMs?: number;
|
|
1078
|
-
/**
|
|
1079
|
-
* Read-through gate applied to every spawned NodeAgent right before it
|
|
1080
|
-
* issues START. Callers pass a getter (not a boolean) so the flag can
|
|
1081
|
-
* be flipped at runtime — see `NodeAgentOptions.autoStart`. Default
|
|
1082
|
-
* `() => true`.
|
|
1083
|
-
*/
|
|
813
|
+
/** Getter (not a boolean) gating START on every spawned NodeAgent. Default `() => true`. */
|
|
1084
814
|
autoStart?: () => boolean;
|
|
1085
815
|
/** Optional clock override (tests). Default `() => Date.now()`. */
|
|
1086
816
|
now?: () => number;
|
|
@@ -1105,48 +835,32 @@ declare class MasterController {
|
|
|
1105
835
|
#private;
|
|
1106
836
|
constructor(opts: MasterControllerOptions);
|
|
1107
837
|
/**
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
* `dataPlane.onTopic(nodeId, path, listener)`; read latest values with
|
|
1111
|
-
* `dataPlane.latest(nodeId, path)`.
|
|
838
|
+
* S→M topic decoder. Auto-attaches / detaches on each slave's `running`
|
|
839
|
+
* transition; subscribe via `dataPlane.onTopic` / read via `dataPlane.latest`.
|
|
1112
840
|
*/
|
|
1113
841
|
get dataPlane(): DataPlaneReceiver;
|
|
1114
|
-
/**
|
|
1115
|
-
* Attach to the transport, start MASTER_HEARTBEAT, fire one initial
|
|
1116
|
-
* broadcast DISCOVER to flush stale slave state from a previous master
|
|
1117
|
-
* session. New slaves auto-spawn a NodeAgent on first sight.
|
|
1118
|
-
*/
|
|
1119
842
|
start(): void;
|
|
1120
843
|
/** Stop all internals, stop every NodeAgent, detach from the transport. */
|
|
1121
844
|
stop(): Promise<void>;
|
|
1122
845
|
/** Snapshot of all known slaves. Stable identity per call. */
|
|
1123
846
|
snapshots(): ControllerSlaveSnapshot[];
|
|
1124
|
-
/** Subscribe to slave-set / state-change updates. Fires on every change. */
|
|
847
|
+
/** Subscribe to slave-set / state-change updates. Fires immediately with the current snapshot, then on every change. */
|
|
1125
848
|
onChange(cb: ControllerListener): Unsubscribe;
|
|
1126
|
-
/**
|
|
1127
|
-
* Access the NodeAgent for one slave, if it exists. Returns null until
|
|
1128
|
-
* the slave has been observed. Useful for service / param calls from
|
|
1129
|
-
* code that already knows the nodeId.
|
|
1130
|
-
*/
|
|
849
|
+
/** NodeAgent for one slave, or null if not yet observed. */
|
|
1131
850
|
agent(nodeId: number): NodeAgent | null;
|
|
1132
|
-
/**
|
|
851
|
+
/** Ad-hoc broadcast DISCOVER. */
|
|
1133
852
|
discoverNow(): Promise<void>;
|
|
1134
853
|
/**
|
|
1135
|
-
* Commit the deferred START on
|
|
1136
|
-
* `
|
|
1137
|
-
*
|
|
1138
|
-
* for a manual start; otherwise resolves once START has been queued
|
|
1139
|
-
* on the wire (the RUNNING transition arrives later via `onChange`).
|
|
854
|
+
* Commit the deferred START on a slave parked in `awaiting-start`.
|
|
855
|
+
* Returns `false` if the slave is unknown or not waiting; the RUNNING
|
|
856
|
+
* transition arrives later via `onChange`.
|
|
1140
857
|
*/
|
|
1141
858
|
startNode(nodeId: number): Promise<boolean>;
|
|
1142
859
|
/**
|
|
1143
|
-
* Re-run
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1147
|
-
* ANNOUNCEd; otherwise resolves once the new bring-up has been kicked
|
|
1148
|
-
* off (it does not await the next RUNNING transition — listen via
|
|
1149
|
-
* {@link onChange} instead).
|
|
860
|
+
* Re-run FRAME_DEFINE / START so the {@link FramePlanFactory} is consulted
|
|
861
|
+
* again (e.g. after a UI-driven field-tx-enable change). Returns `false`
|
|
862
|
+
* if unknown / never ANNOUNCEd; resolves once bring-up is kicked off, not
|
|
863
|
+
* when RUNNING is reached — listen via {@link onChange}.
|
|
1150
864
|
*/
|
|
1151
865
|
reprovision(nodeId: number): Promise<boolean>;
|
|
1152
866
|
}
|
|
@@ -1178,7 +892,7 @@ declare class ProtoReader {
|
|
|
1178
892
|
constructor(buf: Uint8Array, end?: number);
|
|
1179
893
|
get done(): boolean;
|
|
1180
894
|
get position(): number;
|
|
1181
|
-
/** Read a base-128 varint as a JS number (
|
|
895
|
+
/** Read a base-128 varint as a JS number (schema uses uint32 only). */
|
|
1182
896
|
readVarint(): number;
|
|
1183
897
|
/** Read a length-delimited byte slice (no copy). */
|
|
1184
898
|
readBytes(): Uint8Array;
|
|
@@ -1186,11 +900,7 @@ declare class ProtoReader {
|
|
|
1186
900
|
readString(): string;
|
|
1187
901
|
/** Read a length-delimited sub-message and return a new reader scoped to it. */
|
|
1188
902
|
readMessage(): ProtoReader;
|
|
1189
|
-
/**
|
|
1190
|
-
* Read the next tag and return both the field number and wire type. The
|
|
1191
|
-
* caller is expected to dispatch on field number; unknown fields can be
|
|
1192
|
-
* passed to {@link skipValue}.
|
|
1193
|
-
*/
|
|
903
|
+
/** Read a tag as `{field, wire}`. Unknown fields dispatch to {@link skipValue}. */
|
|
1194
904
|
readTag(): {
|
|
1195
905
|
field: number;
|
|
1196
906
|
wire: number;
|
|
@@ -1203,80 +913,45 @@ declare class ProtoError extends Error {
|
|
|
1203
913
|
}
|
|
1204
914
|
//#endregion
|
|
1205
915
|
//#region src/schema-hash.d.ts
|
|
1206
|
-
/**
|
|
1207
|
-
* SHA-256 helpers for the schema blob (§5.4, §9.3).
|
|
1208
|
-
*
|
|
1209
|
-
* Both codegen (C++) and the bridge (C++) hash the entire schema blob with
|
|
1210
|
-
* SHA-256 and pack the first 8 bytes as little-endian u64 into the ANNOUNCE
|
|
1211
|
-
* frame. This module is the master-side equivalent: hash the blob we
|
|
1212
|
-
* received over SCHEMA_DATA and compare with the schema_hash the slave
|
|
1213
|
-
* advertised in ANNOUNCE.
|
|
1214
|
-
*
|
|
1215
|
-
* Uses Web Crypto (`crypto.subtle`) — available in modern browsers and Node
|
|
1216
|
-
* ≥ 19 / Deno / Bun, no polyfill needed for our target environments.
|
|
1217
|
-
*/
|
|
1218
916
|
declare function sha256(bytes: Uint8Array): Promise<Uint8Array>;
|
|
1219
|
-
/**
|
|
1220
|
-
* Compute the ANNOUNCE-shape schema hash: the first 8 bytes of SHA-256(blob),
|
|
1221
|
-
* interpreted as a little-endian u64. Mirrors
|
|
1222
|
-
* `fibril_can_core/sha256::hash_prefix_u64`.
|
|
1223
|
-
*/
|
|
917
|
+
/** First 8 bytes of SHA-256(blob) as little-endian u64 — the ANNOUNCE shape. */
|
|
1224
918
|
declare function schemaHashPrefix(blob: Uint8Array): Promise<bigint>;
|
|
1225
919
|
//#endregion
|
|
1226
920
|
//#region src/segmented-transfer.d.ts
|
|
1227
921
|
/**
|
|
1228
|
-
*
|
|
1229
|
-
* `
|
|
1230
|
-
* `
|
|
1231
|
-
*
|
|
1232
|
-
* - ≤64 byte payload → one SINGLE frame (seg=0).
|
|
1233
|
-
* - Larger → seq=0 carries `[0]=0 [1..2]=total_len + ≤61B data`, subsequent
|
|
1234
|
-
* seqs carry `[0]=seq + ≤63B data`. Throws on payloads >1024B.
|
|
922
|
+
* SVC_REQUEST frames for a master→slave call. ≤64 B → one SINGLE frame;
|
|
923
|
+
* larger → seq 0 carries `[0]=0 [1..2]=total + ≤61 B data`, later seqs
|
|
924
|
+
* carry `[0]=seq + ≤63 B data`. Throws on payloads >1024 B.
|
|
1235
925
|
*/
|
|
1236
926
|
declare function makeSvcRequestFrames(nodeId: number, svcIdx: number, callId: number, payload: Uint8Array): CanFrameTx[];
|
|
1237
927
|
/**
|
|
1238
|
-
*
|
|
1239
|
-
* `channel` is
|
|
1240
|
-
* is `[status, ...response]` so the caller bakes status into the buffer.
|
|
928
|
+
* Response segments (for mock slaves / TS slave runtime). `payload` must
|
|
929
|
+
* already carry `[status, ...body]`; `channel` is typically CHAN_SVC_RESPONSE.
|
|
1241
930
|
*/
|
|
1242
931
|
declare function makeSvcSegmentedFrames(channel: number, nodeId: number, svcIdx: number, callId: number, payload: Uint8Array): CanFrameTx[];
|
|
1243
|
-
/**
|
|
1244
|
-
* Bitmask of segment seqs required to reassemble a `totalLen`-byte payload.
|
|
1245
|
-
* Identical formula to `expected_mask` in
|
|
1246
|
-
* `fibril_can_bridge/src/node_agent.cpp` and the slave's `seg_expected_mask`.
|
|
1247
|
-
*/
|
|
932
|
+
/** Bitmask of seg-seqs required to reassemble a `totalLen`-byte payload. */
|
|
1248
933
|
declare function segExpectedMask(totalLen: number): number;
|
|
1249
|
-
/**
|
|
1250
|
-
* Per-(svc, call_id) reassembly slot. The caller drives one of these per
|
|
1251
|
-
* outstanding incoming segmented transfer.
|
|
1252
|
-
*/
|
|
934
|
+
/** Per-(svc, call_id) reassembly slot for one segmented transfer. */
|
|
1253
935
|
declare class SegmentReassembler {
|
|
1254
936
|
#private;
|
|
1255
937
|
totalLen: number;
|
|
1256
938
|
rxBitmap: number;
|
|
1257
939
|
lastRxMs: number;
|
|
1258
940
|
/**
|
|
1259
|
-
* Ingest one seg=1 frame
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
* on protocol violations and resets the slot.
|
|
941
|
+
* Ingest one seg=1 frame (payload with seq / total_len header attached).
|
|
942
|
+
* Returns the assembled payload once complete, else null. Resets the slot
|
|
943
|
+
* on protocol violation.
|
|
1263
944
|
*/
|
|
1264
945
|
ingest(data: Uint8Array, nowMs: number): Uint8Array | null;
|
|
1265
946
|
}
|
|
1266
947
|
//#endregion
|
|
1267
948
|
//#region src/seq-allocator.d.ts
|
|
1268
|
-
/**
|
|
1269
|
-
* Sequential u8 allocator used for the config-plane `seq` field shared by
|
|
1270
|
-
* every command this master has issued to a given slave. Wraps at 256,
|
|
1271
|
-
* matching `BridgeNode::alloc_seq` in
|
|
1272
|
-
* `fibril_can_bridge/src/bridge_node.cpp` (the bridge wraps on the same
|
|
1273
|
-
* boundary; the AckTracker indexes by the wrapped value).
|
|
1274
|
-
*/
|
|
949
|
+
/** Wrapping u8 counter for the config-plane `seq` byte. */
|
|
1275
950
|
declare class SeqAllocator {
|
|
1276
951
|
#private;
|
|
1277
952
|
next(): number;
|
|
1278
953
|
reset(): void;
|
|
1279
954
|
}
|
|
1280
955
|
//#endregion
|
|
1281
|
-
export { type AckCallback, type AckOutcome, AckTracker, type AnnounceMsg, 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, type ControllerListener, type ControllerSlaveSnapshot, DIR_RX_M2S, DIR_TX_S2M, DataPlaneReceiver, type DataPlaneReceiverOptions, type Dict, Direction, DynamicCodecError, type EndpointDescriptor, type EndpointKey, FAULT_ID_CONFLICT, FAULT_INDEX_OVERFLOW, FAULT_NOMEM, FAULT_NONE, FD_DLC_STEPS, type FieldRequest, type FieldValue, type FrameDefineEntry, type FramePlan, type FramePlanContext, type FramePlanFactory, type FrameSpec, type HeartbeatMsg, MAX_FRAME_PAYLOAD, MasterController, type MasterControllerOptions, 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, type NakMsg, NoCallSlotError, NodeAgent, type NodeAgentOptions, type NodeProvisionState, type NodeRunningSnapshot, type NodeSchema, NumberingError, type NumberingResult, type NumberingTable, PROTOCOL_VERSION, ParamCodecError, type ParamDescriptor, ProtoError, ProtoReader, type RosMapping, type S2MFieldFilter, 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, type SchemaBlockArray, type SchemaBlockType, type SchemaDataMsg, type SchemaField, SchemaIndex, type SchemaParam, type SchemaService, type SchemaTopic, SegmentReassembler, SeqAllocator, ServiceCallError, type ServiceCallOptions, ServiceCancelledError, ServiceClient, type ServiceDescriptor, type ServiceResult, ServiceTimeoutError, ServiceUnavailableError, StdIdAllocator, type TopicDescriptor, type TopicListener, type TopicSample, type TopicStats, TypedParam, type TypedParamSetOptions, type TypedParamSetResult, TypedService, type TypedServiceCallOptions, type TypedServiceResult, type WireLimits, 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 };
|
|
956
|
+
export { type AckCallback, type AckOutcome, AckTracker, type AnnounceMsg, 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, type ControllerListener, type ControllerSlaveSnapshot, DIR_RX_M2S, DIR_TX_S2M, DataPlaneReceiver, type DataPlaneReceiverOptions, type DecodeSchemaResult, type Dict, Direction, DynamicCodecError, type EndpointDescriptor, type EndpointKey, FAULT_ID_CONFLICT, FAULT_INDEX_OVERFLOW, FAULT_NOMEM, FAULT_NONE, FD_DLC_STEPS, type FieldRequest, type FieldValue, type FrameDefineEntry, type FramePlan, type FramePlanContext, type FramePlanFactory, type FrameSpec, type HeartbeatMsg, MAX_FRAME_PAYLOAD, MasterController, type MasterControllerOptions, 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, type NakMsg, NoCallSlotError, NodeAgent, type NodeAgentOptions, type NodeProvisionState, type NodeRunningSnapshot, type NodeSchema, NumberingError, type NumberingResult, type NumberingTable, PROTOCOL_VERSION, ParamCodecError, type ParamDescriptor, ProtoError, ProtoReader, type RosMapping, type S2MFieldFilter, 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, type SchemaBlockArray, type SchemaBlockType, type SchemaDataMsg, type SchemaField, SchemaIndex, type SchemaParam, type SchemaService, type SchemaTopic, SegmentReassembler, SeqAllocator, ServiceCallError, type ServiceCallOptions, ServiceCancelledError, ServiceClient, type ServiceDescriptor, type ServiceResult, ServiceTimeoutError, ServiceUnavailableError, StdIdAllocator, type TopicDescriptor, type TopicListener, type TopicSample, type TopicStats, TypedParam, type TypedParamSetOptions, type TypedParamSetResult, TypedService, type TypedServiceCallOptions, type TypedServiceResult, type WireLimits, 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 };
|
|
1282
957
|
//# sourceMappingURL=index.d.ts.map
|