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