@synapticon/motion-master-client 6.0.0-alpha.62 → 6.0.0-alpha.64

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.
@@ -1,4 +1,4 @@
1
- import { AlStatusCode, Cia402Status, DcSyncStatus, DeviceDiagnostics, DeviceParameter, EscRegister, FoeErrorCode, GameLoopHealth, MailboxErrorCode, Monitoring, ObjectDataTypeInfo, OutputStageResult, ParameterCacheEntry, PdoMapping, PdoMappingRequest, ProcessDataWatchdog, ProcessImageObject, SdoAbortCode, SlaveConfig, SlaveInformationInterface } from "./data-contracts.js";
1
+ import { AlStatusCode, BrakeState, Cia402Status, DcSyncStatus, DeviceDiagnostics, DeviceParameter, EscRegister, EsiParseResult, FoeErrorCode, GameLoopHealth, MailboxErrorCode, Monitoring, ObjectDataTypeInfo, OutputStageResult, ParameterCacheEntry, PdoMapping, PdoMappingRequest, ProcedureListing, ProcedureRequest, ProcedureSnapshot, ProcessDataWatchdog, ProcessImageObject, SdoAbortCode, SlaveConfig, SlaveInformationInterface, UserCacheListing } from "./data-contracts.js";
2
2
  import { HttpClient, RequestParams } from "./http-client.js";
3
3
  export declare class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
4
4
  /**
@@ -662,6 +662,78 @@ export declare class Api<SecurityDataType = unknown> extends HttpClient<Security
662
662
  */
663
663
  value: number;
664
664
  }, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<void, void>>;
665
+ /**
666
+ * @description Returns every procedure available on the device, each with its descriptor and the state of its current or last run. One request is enough to render a whole per-device procedures view: the descriptor carries the title, description and caveats to display, and the snapshot says whether anything is running or how the last run went. The list is per device, not global — a procedure is offered only where it applies, so a third-party slave reports an empty list rather than controls that could only ever fail. A procedure that has never run reports an idle snapshot (`status` `idle`, `runCount` 0, every step `idle`), so every entry has the same shape.
667
+ *
668
+ * @name ListProcedures
669
+ * @summary List the procedures a device supports
670
+ * @request GET:/api/devices/{slavePosition}/procedures
671
+ */
672
+ listProcedures: (slavePosition: number, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<ProcedureListing[], void>>;
673
+ /**
674
+ * @description Starts the named procedure and returns as soon as the run is under way — this does **not** wait for it to finish. Poll `GET` on the same path for its outcome, or `DELETE` to cancel it. The request body carries the procedure's parameters; which fields it accepts (and whether it needs any at all) is per procedure, so consult the descriptor from `GET /api/devices/{slavePosition}/procedures`. A procedure that takes no parameters may be started with no body. Only one procedure may run on a device at a time; a second start is refused with 409 while the first is in flight. Most procedures require an active mailbox, so the device must be in PRE-OP, SAFE-OP or OP. A run the drive answers with an error is not a failure of *this* request — it is reported in the snapshot as a failed run.
675
+ *
676
+ * @name StartProcedure
677
+ * @summary Start a procedure on a device
678
+ * @request POST:/api/devices/{slavePosition}/procedures/{procedureName}
679
+ */
680
+ startProcedure: (slavePosition: number, procedureName: string, data?: ProcedureRequest, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<ProcedureSnapshot, void>>;
681
+ /**
682
+ * @description Returns the current run, or the last one to have finished. This is the whole progress surface — there is no push channel — and polling it is lossless: every finished step keeps its terminal status and value, so a step that both starts and completes between two polls is still reported as succeeded. A polling loop is `while status == "running"`. A procedure that has never run is not an absence: it reports an idle snapshot built from the procedure's step template, so a client renders one shape and needs no empty-state branch. The snapshot is retained after a run ends, so a client that reconnects (or a user returning to a page) sees how the last run went. It is dropped when the device set is rebuilt by a scan or reset, because bus positions may then name different hardware.
683
+ *
684
+ * @name GetProcedure
685
+ * @summary Read the state of a procedure on a device
686
+ * @request GET:/api/devices/{slavePosition}/procedures/{procedureName}
687
+ */
688
+ getProcedure: (slavePosition: number, procedureName: string, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<ProcedureSnapshot, void>>;
689
+ /**
690
+ * @description Asks the running procedure to stop and returns immediately. Where the procedure is waiting on the drive it also tells the drive to abort (for an OS command, 0x1024 = 3), so a run that is waiting stops when asked rather than carrying on to completion. The run then finishes with status `cancelled`. This cancels the *run*, not the record: the snapshot remains and reports how far it got.
691
+ *
692
+ * @name CancelProcedure
693
+ * @summary Cancel a running procedure on a device
694
+ * @request DELETE:/api/devices/{slavePosition}/procedures/{procedureName}
695
+ */
696
+ cancelProcedure: (slavePosition: number, procedureName: string, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<void, void>>;
697
+ /**
698
+ * @description Reports the brake objects (0x2004) that decide what a release or engage will do: its current state, the release strategy, the pull time and the pull/hold voltages. Read this before commanding the brake. `softwareControllable` is false when the release strategy is `manualOutputVoltage`, meaning the firmware does not drive the brake at all and both commands below are no-ops. `releaseMovesShaft` is true for a pin brake, whose release procedure turns the motor to lift the load off the pin.
699
+ *
700
+ * @name GetBrake
701
+ * @summary Read a drive's brake configuration and state
702
+ * @request GET:/api/devices/{slavePosition}/brake
703
+ */
704
+ getBrake: (slavePosition: number, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<BrakeState, void>>;
705
+ /**
706
+ * @description Writes "disengaged" to the brake state object (0x2004:07) and waits the drive's pull time (0x2004:03) plus `settle` before answering, because the firmware blocks motion — and motion-related OS commands — until the pull time expires. Answers with the brake state read back. **The release only actually happens while the drive is in OP ENABLED.** In any other state the write merely energises the brake output (phase D). Note also that entering OP ENABLED releases the brake automatically in normal operation but **not** in diagnostics mode, where the master owns the brake — which is why a diagnostics procedure has to call this. **On a pin brake this moves the shaft**: the controller raises current progressively until the load has lifted off the pin by the minimum displacement (0x2004:08), reversing direction if it reaches the current ceiling (0x2004:09, a percentage of rated current) first. Check `releaseMovesShaft` on `GET .../brake`. Brake release is open-loop — nothing confirms the brake let go — so the wait is the only margin there is. A brake whose release strategy is `manualOutputVoltage` is left alone and the returned state says so; that is not an error. **A released brake stays released**: nothing re-engages it for you, so on a vertical or loaded axis engage it again when you are done.
707
+ *
708
+ * @name ReleaseBrake
709
+ * @summary Release (disengage) a drive's brake
710
+ * @request POST:/api/devices/{slavePosition}/brake/release
711
+ */
712
+ releaseBrake: (slavePosition: number, query?: {
713
+ /**
714
+ * Extra wait in milliseconds on top of the drive's pull time. Size it for the machine — release is open-loop, so this is the only margin between "commanded" and "assumed released".
715
+ * @min 0
716
+ * @default 50
717
+ * @example 50
718
+ */
719
+ settle?: number;
720
+ }, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<BrakeState, void>>;
721
+ /**
722
+ * @description Writes "engaged" to the brake state object (0x2004:07) and waits `settle` before answering with the brake state read back. There is no pull time on the way in — a brake is spring-engaged, so engaging it is the removal of voltage — so the wait is only `settle`. As with release, a brake whose release strategy is `manualOutputVoltage` is left alone and the returned state says so.
723
+ *
724
+ * @name EngageBrake
725
+ * @summary Engage a drive's brake
726
+ * @request POST:/api/devices/{slavePosition}/brake/engage
727
+ */
728
+ engageBrake: (slavePosition: number, query?: {
729
+ /**
730
+ * How long to wait in milliseconds after commanding the brake before answering.
731
+ * @min 0
732
+ * @default 50
733
+ * @example 50
734
+ */
735
+ settle?: number;
736
+ }, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<BrakeState, void>>;
665
737
  /**
666
738
  * @description Commands the device to persist its current parameters (the generic CANopen "store parameters" object 0x1010:01) and waits for confirmation. The server writes the ASCII "save" signature, waits a fixed settle (~1 s) for the drive to begin the flash write, then polls 0x1010:01 until it reads back 1 ("save completed"). Because a store in progress can leave the mailbox briefly unresponsive, a poll that does not yet confirm — a value mismatch or a transient read error alike — is retried up to `retries` more times, `interval` apart. The call blocks until the store is confirmed or the retry budget is exhausted (up to a few seconds); the device's mailbox must be active (PRE-OP/SAFE-OP/OP).
667
739
  *
@@ -827,6 +899,17 @@ export declare class Api<SecurityDataType = unknown> extends HttpClient<Security
827
899
  * @request POST:/api/sii/parse
828
900
  */
829
901
  parseSii: (data: File, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<SlaveInformationInterface, void>>;
902
+ /**
903
+ * @description Decodes a vendor's ESI XML supplied in the request body. No device is involved — the Tools page uses this to inspect an ESI with no hardware present, which is the only way to see object descriptions, enum option labels, engineering units and min/max bounds: the CoE SDO-Information service reports none of them. The response carries the vendor, every module, and **every device with its own assembled `entries` table** — one row per addressable `(index, subindex)`, merged from the device's own dictionary plus the dictionaries of the modules its slots reference, with each row recording in `source` where it came from. Object-level annotation (an object's description and raw properties) is attached to **subindex 0 only** — that row *is* the object — rather than repeated onto every subindex. A RECORD member still carries its own description on its own row; an ARRAY element carries none, because the ESI describes an array once rather than per element. To read an object's text for any subindex, look at subindex 0 of the same index. Where a slot offers mutually exclusive module variants the merge necessarily collides; it is resolved last-wins and reported in that device's `warnings`. Pass `modules` to model one concrete configuration instead.
904
+ *
905
+ * @name ParseEsi
906
+ * @summary Parse an EtherCAT Slave Information (ESI) file
907
+ * @request POST:/api/esi/parse
908
+ */
909
+ parseEsi: (data: string, query?: {
910
+ /** Comma-separated `ModuleIdent` values (hexadecimal or decimal) restricting the merge, applied to every device by intersection with the idents that device's slots actually reference. Omit to merge every module each device references. */
911
+ modules?: string;
912
+ }, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<EsiParseResult, void>>;
830
913
  /**
831
914
  * @description Reads live link-quality and watchdog diagnostics from each slave's EtherCAT Slave Controller (DL Status 0x0110, error-counter block 0x0300–0x0313, watchdog block 0x0440–0x0443) via FPRD. Counters are 8-bit, saturate at 255, and are cleared only by a power cycle or explicit write — poll this endpoint and watch for a rising delta rather than an absolute value. The per-port counters localise a degrading link to a specific cable/connector; the watchdog counters distinguish a slave that stopped receiving process data from a master-side problem. Omit `positions` to query all discovered devices. Returns 500 for transports without an ESC (e.g. SPoE).
832
915
  *
@@ -1120,7 +1203,7 @@ export declare class Api<SecurityDataType = unknown> extends HttpClient<Security
1120
1203
  error: string;
1121
1204
  }>>;
1122
1205
  /**
1123
- * @description Serialises the process-data recorder's current span — every cycle from the oldest still in the ring up to the newest at the instant of the call — to a `.mmpd` file and returns its path. Each cycle's full raw input and output images, sequence, and epoch-nanosecond timestamp are written, with the process image (per-device identity and PDO object layout, including object names and data types where the object dictionary has been enumerated) embedded as a header, so the file decodes fully offline with no running Motion Master and no live bus. Works in any state: while devices are exchanging (SAFE-OP/OP) it captures tail→head at that moment and ignores cycles recorded afterwards; after the bus has left the exchange states it uses the most recent retained image layout. The file is written under the configured `recorder.dumpDir` (default: a `motion-master` subdirectory of the OS temporary directory), created if absent. Motion Master binds 127.0.0.1, so the file is on the caller's own machine — only the path is returned (there is no download, list, or delete endpoint).
1206
+ * @description Serialises the process-data recorder's current span — every cycle from the oldest still in the ring up to the newest at the instant of the call — to a `.mmpd` file and returns its path. Each cycle's full raw input and output images, sequence, and epoch-nanosecond timestamp are written, with the process image (per-device identity and PDO object layout, including object names and data types where the object dictionary has been enumerated) embedded as a header, so the file decodes fully offline with no running Motion Master and no live bus. Works in any state: while devices are exchanging (SAFE-OP/OP) it captures tail→head at that moment and ignores cycles recorded afterwards; after the bus has left the exchange states it uses the most recent retained image layout. The file is written under the configured `recorder.dumpDir`, created if absent; by default that is a `dumps` subdirectory of the user-cache root, so the written file is also reachable through the `/api/user-cache` endpoints — list it, download it, or delete it there. Only the path is returned here. (Setting an explicit `recorder.dumpDir` outside the cache root opts out of that; the file is then only reachable from a shell on the server.)
1124
1207
  *
1125
1208
  * @name DumpProcessData
1126
1209
  * @summary Dump the recorder ring to a binary .mmpd file
@@ -1221,17 +1304,17 @@ export declare class Api<SecurityDataType = unknown> extends HttpClient<Security
1221
1304
  /**
1222
1305
  * @description Returns one entry per cached parameter-definition file on disk, keyed by device identity (vendor, product, revision). The cache stores only the object-dictionary *definitions* (not live values), so a scan of previously-seen hardware can skip the slow SDO enumeration. Independent of the bus — works whether or not a driver is initialised.
1223
1306
  *
1224
- * @name ListParameterCaches
1307
+ * @name ListParameterCacheEntries
1225
1308
  * @summary List on-disk parameter caches
1226
- * @request GET:/api/parameter-caches
1309
+ * @request GET:/api/parameter-cache
1227
1310
  */
1228
- listParameterCaches: (params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<ParameterCacheEntry[], any>>;
1311
+ listParameterCacheEntries: (params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<ParameterCacheEntry[], any>>;
1229
1312
  /**
1230
1313
  * @description Returns the raw JSON cache file for the given id, verbatim, so it can be saved or inspected offline.
1231
1314
  *
1232
1315
  * @name GetParameterCache
1233
1316
  * @summary Download a parameter-cache file
1234
- * @request GET:/api/parameter-caches/{id}
1317
+ * @request GET:/api/parameter-cache/{id}
1235
1318
  */
1236
1319
  getParameterCache: (id: string, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<object, void>>;
1237
1320
  /**
@@ -1239,7 +1322,50 @@ export declare class Api<SecurityDataType = unknown> extends HttpClient<Security
1239
1322
  *
1240
1323
  * @name DeleteParameterCache
1241
1324
  * @summary Delete a parameter-cache file
1242
- * @request DELETE:/api/parameter-caches/{id}
1325
+ * @request DELETE:/api/parameter-cache/{id}
1243
1326
  */
1244
1327
  deleteParameterCache: (id: string, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<void, void>>;
1328
+ /**
1329
+ * @description Returns every file in Motion Master's per-user cache directory, flattened: one entry per file with its full path relative to the cache root, whatever the nesting. Directories have no entries of their own. Independent of the bus — works whether or not a driver is initialised. The cache is a plain file store for data that should outlive a restart. The store itself neither validates nor interprets an upload — `PUT` accepts any bytes at any valid path, and putting a file here does not, on its own, make Motion Master do anything with it; the client chooses the paths. That is separate from whether a *feature* reads a given file, and several do: `parameters/` (the on-disk object-dictionary cache) and `dumps/` (`.mmpd` recorder dumps) are written and consumed by those features, with more to follow. Their files appear in this listing alongside everything else, so a client must not assume every entry was uploaded through this API, nor that deleting one is inconsequential.
1330
+ *
1331
+ * @name ListUserCacheFiles
1332
+ * @summary List the files in the user cache
1333
+ * @request GET:/api/user-cache
1334
+ */
1335
+ listUserCacheFiles: (params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<UserCacheListing, void>>;
1336
+ /**
1337
+ * @description Returns the file verbatim, always as `application/octet-stream` and always with `Content-Disposition: attachment`, `X-Content-Type-Options: nosniff` and a `default-src 'none'; sandbox` CSP. The bytes are whatever a user uploaded, served from the API's own origin, so a browser must never *render* them — a rendered upload would be stored XSS against the origin that controls the drives, which CORS cannot help with. No extension is trusted to name a content type, and the attachment carries no `filename` (the path is user-controlled and must not reach a response header). Clients read the bytes and name the saved file themselves.
1338
+ *
1339
+ * @name GetUserCacheFile
1340
+ * @summary Download a user-cache file
1341
+ * @request GET:/api/user-cache/{path}
1342
+ */
1343
+ getUserCacheFile: (path: string, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<Blob, void>>;
1344
+ /**
1345
+ * @description Writes the request body to the given path, creating parent directories as needed and replacing any existing file. The write is atomic — a concurrent reader sees either the old contents or the new one, never a partial file. The body is taken as raw bytes whatever its content type.
1346
+ *
1347
+ * @name PutUserCacheFile
1348
+ * @summary Upload a user-cache file
1349
+ * @request PUT:/api/user-cache/{path}
1350
+ */
1351
+ putUserCacheFile: (path: string, data: File, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<{
1352
+ /**
1353
+ * The path written, relative to the cache root.
1354
+ * @example "configs/machine-a.json"
1355
+ */
1356
+ path: string;
1357
+ /**
1358
+ * Bytes written.
1359
+ * @example 20480
1360
+ */
1361
+ size: number;
1362
+ }, void>>;
1363
+ /**
1364
+ * @description Removes a file, or a directory and everything under it. Directories left empty between the removed path and the cache root are pruned, so no husks remain.
1365
+ *
1366
+ * @name DeleteUserCacheFile
1367
+ * @summary Delete a user-cache file or directory
1368
+ * @request DELETE:/api/user-cache/{path}
1369
+ */
1370
+ deleteUserCacheFile: (path: string, params?: RequestParams) => Promise<import("./http-client.js").HttpResponse<void, void>>;
1245
1371
  }
@@ -456,6 +456,100 @@ export class Api extends HttpClient {
456
456
  type: ContentType.Json,
457
457
  ...params,
458
458
  });
459
+ /**
460
+ * @description Returns every procedure available on the device, each with its descriptor and the state of its current or last run. One request is enough to render a whole per-device procedures view: the descriptor carries the title, description and caveats to display, and the snapshot says whether anything is running or how the last run went. The list is per device, not global — a procedure is offered only where it applies, so a third-party slave reports an empty list rather than controls that could only ever fail. A procedure that has never run reports an idle snapshot (`status` `idle`, `runCount` 0, every step `idle`), so every entry has the same shape.
461
+ *
462
+ * @name ListProcedures
463
+ * @summary List the procedures a device supports
464
+ * @request GET:/api/devices/{slavePosition}/procedures
465
+ */
466
+ listProcedures = (slavePosition, params = {}) => this.request({
467
+ path: `/api/devices/${slavePosition}/procedures`,
468
+ method: "GET",
469
+ format: "json",
470
+ ...params,
471
+ });
472
+ /**
473
+ * @description Starts the named procedure and returns as soon as the run is under way — this does **not** wait for it to finish. Poll `GET` on the same path for its outcome, or `DELETE` to cancel it. The request body carries the procedure's parameters; which fields it accepts (and whether it needs any at all) is per procedure, so consult the descriptor from `GET /api/devices/{slavePosition}/procedures`. A procedure that takes no parameters may be started with no body. Only one procedure may run on a device at a time; a second start is refused with 409 while the first is in flight. Most procedures require an active mailbox, so the device must be in PRE-OP, SAFE-OP or OP. A run the drive answers with an error is not a failure of *this* request — it is reported in the snapshot as a failed run.
474
+ *
475
+ * @name StartProcedure
476
+ * @summary Start a procedure on a device
477
+ * @request POST:/api/devices/{slavePosition}/procedures/{procedureName}
478
+ */
479
+ startProcedure = (slavePosition, procedureName, data, params = {}) => this.request({
480
+ path: `/api/devices/${slavePosition}/procedures/${procedureName}`,
481
+ method: "POST",
482
+ body: data,
483
+ type: ContentType.Json,
484
+ format: "json",
485
+ ...params,
486
+ });
487
+ /**
488
+ * @description Returns the current run, or the last one to have finished. This is the whole progress surface — there is no push channel — and polling it is lossless: every finished step keeps its terminal status and value, so a step that both starts and completes between two polls is still reported as succeeded. A polling loop is `while status == "running"`. A procedure that has never run is not an absence: it reports an idle snapshot built from the procedure's step template, so a client renders one shape and needs no empty-state branch. The snapshot is retained after a run ends, so a client that reconnects (or a user returning to a page) sees how the last run went. It is dropped when the device set is rebuilt by a scan or reset, because bus positions may then name different hardware.
489
+ *
490
+ * @name GetProcedure
491
+ * @summary Read the state of a procedure on a device
492
+ * @request GET:/api/devices/{slavePosition}/procedures/{procedureName}
493
+ */
494
+ getProcedure = (slavePosition, procedureName, params = {}) => this.request({
495
+ path: `/api/devices/${slavePosition}/procedures/${procedureName}`,
496
+ method: "GET",
497
+ format: "json",
498
+ ...params,
499
+ });
500
+ /**
501
+ * @description Asks the running procedure to stop and returns immediately. Where the procedure is waiting on the drive it also tells the drive to abort (for an OS command, 0x1024 = 3), so a run that is waiting stops when asked rather than carrying on to completion. The run then finishes with status `cancelled`. This cancels the *run*, not the record: the snapshot remains and reports how far it got.
502
+ *
503
+ * @name CancelProcedure
504
+ * @summary Cancel a running procedure on a device
505
+ * @request DELETE:/api/devices/{slavePosition}/procedures/{procedureName}
506
+ */
507
+ cancelProcedure = (slavePosition, procedureName, params = {}) => this.request({
508
+ path: `/api/devices/${slavePosition}/procedures/${procedureName}`,
509
+ method: "DELETE",
510
+ ...params,
511
+ });
512
+ /**
513
+ * @description Reports the brake objects (0x2004) that decide what a release or engage will do: its current state, the release strategy, the pull time and the pull/hold voltages. Read this before commanding the brake. `softwareControllable` is false when the release strategy is `manualOutputVoltage`, meaning the firmware does not drive the brake at all and both commands below are no-ops. `releaseMovesShaft` is true for a pin brake, whose release procedure turns the motor to lift the load off the pin.
514
+ *
515
+ * @name GetBrake
516
+ * @summary Read a drive's brake configuration and state
517
+ * @request GET:/api/devices/{slavePosition}/brake
518
+ */
519
+ getBrake = (slavePosition, params = {}) => this.request({
520
+ path: `/api/devices/${slavePosition}/brake`,
521
+ method: "GET",
522
+ format: "json",
523
+ ...params,
524
+ });
525
+ /**
526
+ * @description Writes "disengaged" to the brake state object (0x2004:07) and waits the drive's pull time (0x2004:03) plus `settle` before answering, because the firmware blocks motion — and motion-related OS commands — until the pull time expires. Answers with the brake state read back. **The release only actually happens while the drive is in OP ENABLED.** In any other state the write merely energises the brake output (phase D). Note also that entering OP ENABLED releases the brake automatically in normal operation but **not** in diagnostics mode, where the master owns the brake — which is why a diagnostics procedure has to call this. **On a pin brake this moves the shaft**: the controller raises current progressively until the load has lifted off the pin by the minimum displacement (0x2004:08), reversing direction if it reaches the current ceiling (0x2004:09, a percentage of rated current) first. Check `releaseMovesShaft` on `GET .../brake`. Brake release is open-loop — nothing confirms the brake let go — so the wait is the only margin there is. A brake whose release strategy is `manualOutputVoltage` is left alone and the returned state says so; that is not an error. **A released brake stays released**: nothing re-engages it for you, so on a vertical or loaded axis engage it again when you are done.
527
+ *
528
+ * @name ReleaseBrake
529
+ * @summary Release (disengage) a drive's brake
530
+ * @request POST:/api/devices/{slavePosition}/brake/release
531
+ */
532
+ releaseBrake = (slavePosition, query, params = {}) => this.request({
533
+ path: `/api/devices/${slavePosition}/brake/release`,
534
+ method: "POST",
535
+ query: query,
536
+ format: "json",
537
+ ...params,
538
+ });
539
+ /**
540
+ * @description Writes "engaged" to the brake state object (0x2004:07) and waits `settle` before answering with the brake state read back. There is no pull time on the way in — a brake is spring-engaged, so engaging it is the removal of voltage — so the wait is only `settle`. As with release, a brake whose release strategy is `manualOutputVoltage` is left alone and the returned state says so.
541
+ *
542
+ * @name EngageBrake
543
+ * @summary Engage a drive's brake
544
+ * @request POST:/api/devices/{slavePosition}/brake/engage
545
+ */
546
+ engageBrake = (slavePosition, query, params = {}) => this.request({
547
+ path: `/api/devices/${slavePosition}/brake/engage`,
548
+ method: "POST",
549
+ query: query,
550
+ format: "json",
551
+ ...params,
552
+ });
459
553
  /**
460
554
  * @description Commands the device to persist its current parameters (the generic CANopen "store parameters" object 0x1010:01) and waits for confirmation. The server writes the ASCII "save" signature, waits a fixed settle (~1 s) for the drive to begin the flash write, then polls 0x1010:01 until it reads back 1 ("save completed"). Because a store in progress can leave the mailbox briefly unresponsive, a poll that does not yet confirm — a value mismatch or a transient read error alike — is retried up to `retries` more times, `interval` apart. The call blocks until the store is confirmed or the retry budget is exhausted (up to a few seconds); the device's mailbox must be active (PRE-OP/SAFE-OP/OP).
461
555
  *
@@ -577,6 +671,21 @@ export class Api extends HttpClient {
577
671
  format: "json",
578
672
  ...params,
579
673
  });
674
+ /**
675
+ * @description Decodes a vendor's ESI XML supplied in the request body. No device is involved — the Tools page uses this to inspect an ESI with no hardware present, which is the only way to see object descriptions, enum option labels, engineering units and min/max bounds: the CoE SDO-Information service reports none of them. The response carries the vendor, every module, and **every device with its own assembled `entries` table** — one row per addressable `(index, subindex)`, merged from the device's own dictionary plus the dictionaries of the modules its slots reference, with each row recording in `source` where it came from. Object-level annotation (an object's description and raw properties) is attached to **subindex 0 only** — that row *is* the object — rather than repeated onto every subindex. A RECORD member still carries its own description on its own row; an ARRAY element carries none, because the ESI describes an array once rather than per element. To read an object's text for any subindex, look at subindex 0 of the same index. Where a slot offers mutually exclusive module variants the merge necessarily collides; it is resolved last-wins and reported in that device's `warnings`. Pass `modules` to model one concrete configuration instead.
676
+ *
677
+ * @name ParseEsi
678
+ * @summary Parse an EtherCAT Slave Information (ESI) file
679
+ * @request POST:/api/esi/parse
680
+ */
681
+ parseEsi = (data, query, params = {}) => this.request({
682
+ path: `/api/esi/parse`,
683
+ method: "POST",
684
+ query: query,
685
+ body: data,
686
+ format: "json",
687
+ ...params,
688
+ });
580
689
  /**
581
690
  * @description Reads live link-quality and watchdog diagnostics from each slave's EtherCAT Slave Controller (DL Status 0x0110, error-counter block 0x0300–0x0313, watchdog block 0x0440–0x0443) via FPRD. Counters are 8-bit, saturate at 255, and are cleared only by a power cycle or explicit write — poll this endpoint and watch for a rising delta rather than an absolute value. The per-port counters localise a degrading link to a specific cable/connector; the watchdog counters distinguish a slave that stopped receiving process data from a master-side problem. Omit `positions` to query all discovered devices. Returns 500 for transports without an ESC (e.g. SPoE).
582
691
  *
@@ -701,7 +810,7 @@ export class Api extends HttpClient {
701
810
  ...params,
702
811
  });
703
812
  /**
704
- * @description Serialises the process-data recorder's current span — every cycle from the oldest still in the ring up to the newest at the instant of the call — to a `.mmpd` file and returns its path. Each cycle's full raw input and output images, sequence, and epoch-nanosecond timestamp are written, with the process image (per-device identity and PDO object layout, including object names and data types where the object dictionary has been enumerated) embedded as a header, so the file decodes fully offline with no running Motion Master and no live bus. Works in any state: while devices are exchanging (SAFE-OP/OP) it captures tail→head at that moment and ignores cycles recorded afterwards; after the bus has left the exchange states it uses the most recent retained image layout. The file is written under the configured `recorder.dumpDir` (default: a `motion-master` subdirectory of the OS temporary directory), created if absent. Motion Master binds 127.0.0.1, so the file is on the caller's own machine — only the path is returned (there is no download, list, or delete endpoint).
813
+ * @description Serialises the process-data recorder's current span — every cycle from the oldest still in the ring up to the newest at the instant of the call — to a `.mmpd` file and returns its path. Each cycle's full raw input and output images, sequence, and epoch-nanosecond timestamp are written, with the process image (per-device identity and PDO object layout, including object names and data types where the object dictionary has been enumerated) embedded as a header, so the file decodes fully offline with no running Motion Master and no live bus. Works in any state: while devices are exchanging (SAFE-OP/OP) it captures tail→head at that moment and ignores cycles recorded afterwards; after the bus has left the exchange states it uses the most recent retained image layout. The file is written under the configured `recorder.dumpDir`, created if absent; by default that is a `dumps` subdirectory of the user-cache root, so the written file is also reachable through the `/api/user-cache` endpoints — list it, download it, or delete it there. Only the path is returned here. (Setting an explicit `recorder.dumpDir` outside the cache root opts out of that; the file is then only reachable from a shell on the server.)
705
814
  *
706
815
  * @name DumpProcessData
707
816
  * @summary Dump the recorder ring to a binary .mmpd file
@@ -797,12 +906,12 @@ export class Api extends HttpClient {
797
906
  /**
798
907
  * @description Returns one entry per cached parameter-definition file on disk, keyed by device identity (vendor, product, revision). The cache stores only the object-dictionary *definitions* (not live values), so a scan of previously-seen hardware can skip the slow SDO enumeration. Independent of the bus — works whether or not a driver is initialised.
799
908
  *
800
- * @name ListParameterCaches
909
+ * @name ListParameterCacheEntries
801
910
  * @summary List on-disk parameter caches
802
- * @request GET:/api/parameter-caches
911
+ * @request GET:/api/parameter-cache
803
912
  */
804
- listParameterCaches = (params = {}) => this.request({
805
- path: `/api/parameter-caches`,
913
+ listParameterCacheEntries = (params = {}) => this.request({
914
+ path: `/api/parameter-cache`,
806
915
  method: "GET",
807
916
  format: "json",
808
917
  ...params,
@@ -812,10 +921,10 @@ export class Api extends HttpClient {
812
921
  *
813
922
  * @name GetParameterCache
814
923
  * @summary Download a parameter-cache file
815
- * @request GET:/api/parameter-caches/{id}
924
+ * @request GET:/api/parameter-cache/{id}
816
925
  */
817
926
  getParameterCache = (id, params = {}) => this.request({
818
- path: `/api/parameter-caches/${id}`,
927
+ path: `/api/parameter-cache/${id}`,
819
928
  method: "GET",
820
929
  format: "json",
821
930
  ...params,
@@ -825,10 +934,61 @@ export class Api extends HttpClient {
825
934
  *
826
935
  * @name DeleteParameterCache
827
936
  * @summary Delete a parameter-cache file
828
- * @request DELETE:/api/parameter-caches/{id}
937
+ * @request DELETE:/api/parameter-cache/{id}
829
938
  */
830
939
  deleteParameterCache = (id, params = {}) => this.request({
831
- path: `/api/parameter-caches/${id}`,
940
+ path: `/api/parameter-cache/${id}`,
941
+ method: "DELETE",
942
+ ...params,
943
+ });
944
+ /**
945
+ * @description Returns every file in Motion Master's per-user cache directory, flattened: one entry per file with its full path relative to the cache root, whatever the nesting. Directories have no entries of their own. Independent of the bus — works whether or not a driver is initialised. The cache is a plain file store for data that should outlive a restart. The store itself neither validates nor interprets an upload — `PUT` accepts any bytes at any valid path, and putting a file here does not, on its own, make Motion Master do anything with it; the client chooses the paths. That is separate from whether a *feature* reads a given file, and several do: `parameters/` (the on-disk object-dictionary cache) and `dumps/` (`.mmpd` recorder dumps) are written and consumed by those features, with more to follow. Their files appear in this listing alongside everything else, so a client must not assume every entry was uploaded through this API, nor that deleting one is inconsequential.
946
+ *
947
+ * @name ListUserCacheFiles
948
+ * @summary List the files in the user cache
949
+ * @request GET:/api/user-cache
950
+ */
951
+ listUserCacheFiles = (params = {}) => this.request({
952
+ path: `/api/user-cache`,
953
+ method: "GET",
954
+ format: "json",
955
+ ...params,
956
+ });
957
+ /**
958
+ * @description Returns the file verbatim, always as `application/octet-stream` and always with `Content-Disposition: attachment`, `X-Content-Type-Options: nosniff` and a `default-src 'none'; sandbox` CSP. The bytes are whatever a user uploaded, served from the API's own origin, so a browser must never *render* them — a rendered upload would be stored XSS against the origin that controls the drives, which CORS cannot help with. No extension is trusted to name a content type, and the attachment carries no `filename` (the path is user-controlled and must not reach a response header). Clients read the bytes and name the saved file themselves.
959
+ *
960
+ * @name GetUserCacheFile
961
+ * @summary Download a user-cache file
962
+ * @request GET:/api/user-cache/{path}
963
+ */
964
+ getUserCacheFile = (path, params = {}) => this.request({
965
+ path: `/api/user-cache/${path}`,
966
+ method: "GET",
967
+ ...params,
968
+ });
969
+ /**
970
+ * @description Writes the request body to the given path, creating parent directories as needed and replacing any existing file. The write is atomic — a concurrent reader sees either the old contents or the new one, never a partial file. The body is taken as raw bytes whatever its content type.
971
+ *
972
+ * @name PutUserCacheFile
973
+ * @summary Upload a user-cache file
974
+ * @request PUT:/api/user-cache/{path}
975
+ */
976
+ putUserCacheFile = (path, data, params = {}) => this.request({
977
+ path: `/api/user-cache/${path}`,
978
+ method: "PUT",
979
+ body: data,
980
+ format: "json",
981
+ ...params,
982
+ });
983
+ /**
984
+ * @description Removes a file, or a directory and everything under it. Directories left empty between the removed path and the cache root are pruned, so no husks remain.
985
+ *
986
+ * @name DeleteUserCacheFile
987
+ * @summary Delete a user-cache file or directory
988
+ * @request DELETE:/api/user-cache/{path}
989
+ */
990
+ deleteUserCacheFile = (path, params = {}) => this.request({
991
+ path: `/api/user-cache/${path}`,
832
992
  method: "DELETE",
833
993
  ...params,
834
994
  });
@@ -106,6 +106,33 @@ export interface GameLoopHealth {
106
106
  */
107
107
  timestampUs: number;
108
108
  }
109
+ /** The user cache's location on disk plus every file it holds. */
110
+ export interface UserCacheListing {
111
+ /**
112
+ * Absolute path of the cache directory on the server's filesystem. Platform-dependent (and overridable via the `userCache.directory` config key), so it is reported rather than assumed — show it to the user so they can find the files themselves.
113
+ * @example "/home/user/.cache/motion-master"
114
+ */
115
+ root: string;
116
+ files: UserCacheFile[];
117
+ }
118
+ /** One file in the user cache. */
119
+ export interface UserCacheFile {
120
+ /**
121
+ * Path relative to the cache root, always with `/` separators. Use it verbatim in the download/upload/delete endpoints.
122
+ * @example "configs/machine-a.json"
123
+ */
124
+ path: string;
125
+ /**
126
+ * File size in bytes.
127
+ * @example 20480
128
+ */
129
+ size: number;
130
+ /**
131
+ * Last-write time, in milliseconds since the Unix epoch.
132
+ * @example 1783968687738
133
+ */
134
+ modifiedMs: number;
135
+ }
109
136
  /** Summary of one on-disk parameter-cache file (its identity, size, and count). */
110
137
  export interface ParameterCacheEntry {
111
138
  /**
@@ -317,6 +344,155 @@ export interface ProcessImageObject {
317
344
  */
318
345
  bitLength: number;
319
346
  }
347
+ /** One step of a procedure. `id` is stable across runs, so a client keys its label and its value formatting off it. A step never reports cancellation: cancelling stops the procedure, leaving the step it was on `running` and the rest `idle`, which records how far it got. */
348
+ export interface ProgressStep {
349
+ /**
350
+ * Stable identifier of the step within its procedure.
351
+ * @example "command"
352
+ */
353
+ id: string;
354
+ /** @example "succeeded" */
355
+ status: "idle" | "running" | "succeeded" | "failed";
356
+ /**
357
+ * What the step produced, if anything — shape is per procedure. Absent when the step produced nothing. For `os-command` it is an object with `status`, `data`, and `errorCode` when the drive sent one.
358
+ * @example {"status":1,"data":[1,2,3,4,5,6]}
359
+ */
360
+ value?: any;
361
+ /** Why the step failed. Absent unless it did. */
362
+ error?: string;
363
+ }
364
+ /** The complete state of a procedure on one device — an accumulating snapshot rather than an event, which is what makes polling lossless. */
365
+ export interface ProcedureSnapshot {
366
+ /**
367
+ * The one field a polling loop checks. `cancelled` is distinct from `failed` on purpose: "I stopped it" and "the drive could not do it" are different outcomes.
368
+ * @example "succeeded"
369
+ */
370
+ status: "idle" | "running" | "succeeded" | "failed" | "cancelled";
371
+ /**
372
+ * How many runs have been accepted on this device since the last scan or reset; a rejected start does not count. Doubles as a generation counter — with no run id, a changed `runCount` is how a poller can tell one run from the next.
373
+ * @example 3
374
+ */
375
+ runCount: number;
376
+ /**
377
+ * Epoch milliseconds the run began. Absent when never run.
378
+ * @format int64
379
+ * @example 1735821000123
380
+ */
381
+ startedAt?: number;
382
+ /**
383
+ * Epoch milliseconds the run ended. Absent while running.
384
+ * @format int64
385
+ * @example 1735821042456
386
+ */
387
+ finishedAt?: number;
388
+ /** The procedure's ordered steps, always the full template. */
389
+ steps: ProgressStep[];
390
+ /** Why the run failed when no step captured it — a failure belonging to no step, such as the device not being the kind the procedure needs. Absent otherwise. */
391
+ error?: string;
392
+ }
393
+ /** A drive's brake configuration and current state — the parts of object 0x2004 that decide what commanding the brake will actually do. Returned by every brake endpoint, so the outcome of a release or engage arrives in the same shape as a plain read. */
394
+ export interface BrakeState {
395
+ /**
396
+ * Brake state (0x2004:07). A brake is spring-engaged, so `engaged` is also its powered-off state.
397
+ * @example "engaged"
398
+ */
399
+ status: "notConfigured" | "engaged" | "disengaged";
400
+ /**
401
+ * How the brake is driven (0x2004:04). `manualOutputVoltage` means the firmware does not drive it — the brake is a raw output voltage (0x2004:10) instead.
402
+ * @example "clutch"
403
+ */
404
+ releaseStrategy: "manualOutputVoltage" | "clutch" | "pin";
405
+ /**
406
+ * How long the pull voltage is applied to release the brake (0x2004:03). The firmware blocks motion, and motion-related OS commands, until this expires.
407
+ * @example 120
408
+ */
409
+ pullTimeMs: number;
410
+ /**
411
+ * Voltage that disengages the brake, in millivolts (0x2004:01).
412
+ * @example 24000
413
+ */
414
+ pullVoltageMv: number;
415
+ /**
416
+ * Lower voltage that keeps it disengaged once released, in millivolts (0x2004:02).
417
+ * @example 12000
418
+ */
419
+ holdVoltageMv: number;
420
+ /**
421
+ * Whether commanding the brake does anything at all — false for `manualOutputVoltage`. Derived from the release strategy and served so a client need not encode that rule.
422
+ * @example true
423
+ */
424
+ softwareControllable: boolean;
425
+ /**
426
+ * Whether releasing this brake turns the motor. True only for a pin brake, whose release raises current progressively to lift the load off the pin.
427
+ * @example false
428
+ */
429
+ releaseMovesShaft: boolean;
430
+ }
431
+ /** What a procedure *is*, independent of any run — everything needed to render a control for it. The text is served rather than held per client, because the house rule that every action carries a description and its caveats means it has to exist somewhere, and duplicating it in each client is how it goes stale. */
432
+ export interface ProcedureDescriptor {
433
+ /**
434
+ * Identifier: the `procedureName` path segment, and the key its snapshot is retained under.
435
+ * @example "os-command"
436
+ */
437
+ name: string;
438
+ /**
439
+ * Short human-readable name.
440
+ * @example "OS command"
441
+ */
442
+ title: string;
443
+ /** What the procedure does. */
444
+ description: string;
445
+ /** What a user must know before running it. May be empty. */
446
+ caveats: string[];
447
+ /**
448
+ * True if running it can move the shaft.
449
+ * @example true
450
+ */
451
+ movesMotor: boolean;
452
+ /**
453
+ * True if the drive must be enabled before it will run.
454
+ * @example false
455
+ */
456
+ requiresEnabled: boolean;
457
+ /**
458
+ * The ordered step ids the procedure reports against — bare ids, because a template's per-step status is always idle and says nothing. Live status for the same ids is in the snapshot's `steps`.
459
+ * @example ["command"]
460
+ */
461
+ steps: string[];
462
+ }
463
+ /** One procedure paired with how its last run on this device went — an entry of `GET /api/devices/{slavePosition}/procedures`. The pairing is what lets a page render in a single request instead of one per procedure. */
464
+ export interface ProcedureListing {
465
+ /** What a procedure *is*, independent of any run — everything needed to render a control for it. The text is served rather than held per client, because the house rule that every action carries a description and its caveats means it has to exist somewhere, and duplicating it in each client is how it goes stale. */
466
+ descriptor: ProcedureDescriptor;
467
+ /** The complete state of a procedure on one device — an accumulating snapshot rather than an event, which is what makes polling lossless. */
468
+ snapshot: ProcedureSnapshot;
469
+ }
470
+ /** A procedure's parameters. Which fields apply is per procedure — consult its descriptor — so this schema is open, and a procedure that takes none may be started with no body at all. The properties below are those of `os-command`, where `command` is required. */
471
+ export interface ProcedureRequest {
472
+ /**
473
+ * `os-command` only, and required for it: the eight request bytes. Byte 0 is the OS command ID; bytes 1-7 are that command's parameters.
474
+ * @maxItems 8
475
+ * @minItems 8
476
+ * @example [8,0,0,0,0,0,0,0]
477
+ */
478
+ command?: number[];
479
+ /**
480
+ * Ceiling on the whole command. Not a liveness check — the drive fails a command no service acknowledges within 5 s on its own — so size it for the command being run. Hitting it aborts the command on the drive.
481
+ * @min 1
482
+ * @max 600000
483
+ * @default 1000
484
+ * @example 30000
485
+ */
486
+ timeoutMs?: number;
487
+ /**
488
+ * How often the server reads the drive's response object while waiting.
489
+ * @min 1
490
+ * @max 1000
491
+ * @default 10
492
+ */
493
+ pollIntervalMs?: number;
494
+ [key: string]: any;
495
+ }
320
496
  export interface OutputStageResult {
321
497
  /**
322
498
  * 1-based bus position of the owning device (echoes the request)
@@ -939,3 +1115,129 @@ export interface SiiPdo {
939
1115
  flags?: number;
940
1116
  }[];
941
1117
  }
1118
+ /** One addressable `(index, subindex)` of a device's object dictionary, assembled from the ESI. `defaultData`/`minData`/`maxData` are **raw little-endian bytes** as uppercase hexBinary, the same spelling the ESI itself uses — the first byte is the least significant, so `92010200` is `0x00020192`. Decode them with `dataType`; a byte-wise comparison of `minData` against `maxData` is wrong when `isSigned`. */
1119
+ export interface EsiEntry {
1120
+ /** Effective CoE index */
1121
+ index: number;
1122
+ subindex: number;
1123
+ /** Index as written in the source dictionary; omitted when unrelocated */
1124
+ rawIndex?: number;
1125
+ /** The parent object's name — identical across all its subindices */
1126
+ objectName: string;
1127
+ /** This subindex's own name */
1128
+ entryName: string;
1129
+ /** The ESI's display-name override, else `entryName` */
1130
+ displayName: string;
1131
+ objectCode: "VAR" | "ARRAY" | "RECORD";
1132
+ /** The CoE object code (ETG.1000.6 §5) — 7, 8 or 9 */
1133
+ objectCodeValue?: number;
1134
+ /** Highest subindex; omitted for a VAR */
1135
+ numberOfEntries?: number;
1136
+ /**
1137
+ * ESI/IEC 61131-3 type name, e.g. `UDINT`, `STRING(50)`
1138
+ * @example "UDINT"
1139
+ */
1140
+ dataTypeName: string;
1141
+ /**
1142
+ * ETG.1020 code, matching `DeviceParameter.dataType`; 0 if unresolved
1143
+ * @example 7
1144
+ */
1145
+ dataType: number;
1146
+ isSigned: boolean;
1147
+ bitSize: number;
1148
+ /** Offset within the object's complete-access image */
1149
+ bitOffset: number;
1150
+ /** Uppercase hexBinary */
1151
+ defaultData?: string;
1152
+ minData?: string;
1153
+ maxData?: string;
1154
+ /** Packed ETG.1004 notation value */
1155
+ unit?: number;
1156
+ /** @example "mV" */
1157
+ unitSymbol?: string;
1158
+ access: {
1159
+ mode: "ro" | "rw" | "wo";
1160
+ readRestrictions?: "PreOP" | "PreOP_SafeOP" | "PreOP_OP" | "SafeOP" | "SafeOP_OP" | "OP";
1161
+ writeRestrictions?: "PreOP" | "PreOP_SafeOP" | "PreOP_OP" | "SafeOP" | "SafeOP_OP" | "OP";
1162
+ };
1163
+ /** Mandatory, optional or conditional */
1164
+ category: "m" | "o" | "c";
1165
+ /** Empty when not mappable; `t` TxPDO, `r` RxPDO */
1166
+ pdoMapping: "" | "t" | "r" | "tr";
1167
+ safetyMapping?: "si" | "so" | "sio" | "sp";
1168
+ sdoAccess: "SubIndexAccess" | "CompleteAccess";
1169
+ backup: boolean;
1170
+ setting: boolean;
1171
+ /** SoE IDN */
1172
+ attribute?: number;
1173
+ /** ETG.1000.6 ObjAccess bitfield synthesised from the flags — bits 0-2 read in PreOP/SafeOP/OP, 3-5 write, 6 RxPDO-mappable, 7 TxPDO-mappable, 8 backup, 9 setting. Matches `DeviceParameter.access`. */
1174
+ objAccess: number;
1175
+ /** This row's description (HTML), decoded from `properties`. On subindex 0 it is the object's; on a RECORD member it is that member's; an ARRAY element has none. */
1176
+ description?: string;
1177
+ /** Enum labels, from `<EnumInfo>` and/or an `options` property */
1178
+ options?: {
1179
+ label: string;
1180
+ value: number;
1181
+ }[];
1182
+ /** Raw `<Property>` annotations as written, for whatever this row describes. This is the ESI's generic extension mechanism; `description` and `options` above are conveniences decoded from the two conventional names, so a vendor using other names still has its data here. **Subindex 0 carries the object's own annotation** (stored once, not repeated onto every subindex); other rows carry only what that entry itself declares. */
1183
+ properties?: EsiProperties;
1184
+ /** Which dictionary this entry came from. The module's *name* is not repeated here — resolve `moduleIdent` against the top-level `modules` list. */
1185
+ source: {
1186
+ kind: "device" | "module";
1187
+ moduleIdent?: number;
1188
+ /** Zero-based slot number */
1189
+ slot?: number;
1190
+ /** Slot offset applied to `rawIndex` */
1191
+ indexOffset?: number;
1192
+ };
1193
+ }
1194
+ /** Raw `<Property>` annotations as written, for whatever this row describes. This is the ESI's generic extension mechanism; `description` and `options` above are conveniences decoded from the two conventional names, so a vendor using other names still has its data here. **Subindex 0 carries the object's own annotation** (stored once, not repeated onto every subindex); other rows carry only what that entry itself declares. */
1195
+ export type EsiProperties = {
1196
+ name: string;
1197
+ value: string;
1198
+ }[];
1199
+ export interface EsiDeviceSummary {
1200
+ /** Zero-based position in the file */
1201
+ ordinal: number;
1202
+ /** @example "SOMANET Node" */
1203
+ type: string;
1204
+ name: string;
1205
+ productCode?: number;
1206
+ revisionNo?: number;
1207
+ /** @example 402 */
1208
+ profileNo?: number;
1209
+ groupType: string;
1210
+ /** Objects in the device's own dictionary, before any module merge */
1211
+ objectCount: number;
1212
+ rxPdoCount?: number;
1213
+ txPdoCount?: number;
1214
+ /** Every `ModuleIdent` the device's slots reference, in slot order */
1215
+ moduleIdents: number[];
1216
+ /** The parsed `<Slots>` block */
1217
+ slots?: object;
1218
+ /** This device's flat, slot-merged object dictionary */
1219
+ entries?: EsiEntry[];
1220
+ /** Problems found while assembling *this device* — a value whose byte length disagrees with its declared width, an unresolvable type, a module collision. Distinct from the top-level `warnings`, which are about the document; a different `modules` selection would produce a different list here. */
1221
+ warnings?: string[];
1222
+ }
1223
+ /** A parsed ESI. Every device carries its own assembled `entries` table. */
1224
+ export interface EsiParseResult {
1225
+ /** The `EtherCATInfo/@Version` attribute */
1226
+ version?: string;
1227
+ vendor: {
1228
+ /** @example 8914 */
1229
+ id: number;
1230
+ name: string;
1231
+ url?: string;
1232
+ };
1233
+ devices: EsiDeviceSummary[];
1234
+ modules: {
1235
+ moduleIdent: number;
1236
+ /** @example "CiA402 Dictionary" */
1237
+ type: string;
1238
+ name: string;
1239
+ objectCount?: number;
1240
+ }[];
1241
+ /** Recoverable problems in the document itself — a skipped malformed object, an unparsable value. A file with warnings still parses. Per-device assembly problems are reported on the device instead. */
1242
+ warnings?: string[];
1243
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synapticon/motion-master-client",
3
- "version": "6.0.0-alpha.62",
3
+ "version": "6.0.0-alpha.64",
4
4
  "description": "Isomorphic TypeScript client for Motion Master: the generated HTTP API client, the WebSocket connection, and helpers.",
5
5
  "license": "MIT",
6
6
  "repository": {