@ikalogic/at1000 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +21 -0
  2. package/build/at1000-3evjqv7V.d.mts +69 -0
  3. package/build/at1000-3evjqv7V.d.mts.map +1 -0
  4. package/build/at1000-BAZbVgLx.cjs +3 -0
  5. package/build/at1000-BAZbVgLx.cjs.map +1 -0
  6. package/build/at1000-Cxx254i3.mjs +3 -0
  7. package/build/at1000-Cxx254i3.mjs.map +1 -0
  8. package/build/at1000-D_y0MxFy.d.cts +69 -0
  9. package/build/at1000-D_y0MxFy.d.cts.map +1 -0
  10. package/build/browser.cjs +1 -1
  11. package/build/browser.cjs.map +1 -1
  12. package/build/browser.d.cts +2 -2
  13. package/build/browser.d.cts.map +1 -1
  14. package/build/browser.d.mts +2 -2
  15. package/build/browser.d.mts.map +1 -1
  16. package/build/browser.mjs +1 -1
  17. package/build/browser.mjs.map +1 -1
  18. package/build/{com-B2qxT8se.d.cts → com-BzaKOH_B.d.cts} +63 -63
  19. package/build/{com-B2qxT8se.d.cts.map → com-BzaKOH_B.d.cts.map} +1 -1
  20. package/build/{com-B2qxT8se.d.mts → com-BzaKOH_B.d.mts} +63 -63
  21. package/build/{com-B2qxT8se.d.mts.map → com-BzaKOH_B.d.mts.map} +1 -1
  22. package/build/com.cjs +1 -1
  23. package/build/com.cjs.map +1 -1
  24. package/build/com.d.cts +2 -2
  25. package/build/com.d.mts +2 -2
  26. package/build/com.mjs +1 -1
  27. package/build/com.mjs.map +1 -1
  28. package/build/events-PyyK1XbE.d.cts +351 -0
  29. package/build/events-PyyK1XbE.d.cts.map +1 -0
  30. package/build/events-PyyK1XbE.d.mts +351 -0
  31. package/build/events-PyyK1XbE.d.mts.map +1 -0
  32. package/build/events.cjs +1 -0
  33. package/build/events.d.cts +2 -0
  34. package/build/events.d.mts +2 -0
  35. package/build/events.mjs +1 -0
  36. package/build/{gpio-D1QpFMeE.d.cts → gpio-CHg2nztz.d.cts} +22 -16
  37. package/build/gpio-CHg2nztz.d.cts.map +1 -0
  38. package/build/{gpio-BDMTNT6V.d.mts → gpio-CbGcoT9b.d.mts} +22 -16
  39. package/build/gpio-CbGcoT9b.d.mts.map +1 -0
  40. package/build/gpio.cjs +1 -1
  41. package/build/gpio.cjs.map +1 -1
  42. package/build/gpio.d.cts +2 -2
  43. package/build/gpio.d.mts +2 -2
  44. package/build/gpio.mjs +1 -1
  45. package/build/gpio.mjs.map +1 -1
  46. package/build/{hmi-Cw9EEy_5.d.cts → hmi-B6LU4yaL.d.cts} +11 -3
  47. package/build/hmi-B6LU4yaL.d.cts.map +1 -0
  48. package/build/{hmi-Cw9EEy_5.d.mts → hmi-B6LU4yaL.d.mts} +11 -3
  49. package/build/hmi-B6LU4yaL.d.mts.map +1 -0
  50. package/build/hmi.cjs +1 -1
  51. package/build/hmi.cjs.map +1 -1
  52. package/build/hmi.d.cts +2 -2
  53. package/build/hmi.d.mts +2 -2
  54. package/build/hmi.mjs +1 -1
  55. package/build/hmi.mjs.map +1 -1
  56. package/build/{management-C0iQha23.d.cts → management-DliNzoq1.d.cts} +243 -12
  57. package/build/management-DliNzoq1.d.cts.map +1 -0
  58. package/build/{management-C0iQha23.d.mts → management-DliNzoq1.d.mts} +243 -12
  59. package/build/management-DliNzoq1.d.mts.map +1 -0
  60. package/build/management.cjs +1 -1
  61. package/build/management.cjs.map +1 -1
  62. package/build/management.d.cts +2 -2
  63. package/build/management.d.mts +2 -2
  64. package/build/management.mjs +1 -1
  65. package/build/management.mjs.map +1 -1
  66. package/build/node.cjs +1 -1
  67. package/build/node.cjs.map +1 -1
  68. package/build/node.d.cts +2 -2
  69. package/build/node.d.cts.map +1 -1
  70. package/build/node.d.mts +2 -2
  71. package/build/node.d.mts.map +1 -1
  72. package/build/node.mjs +1 -1
  73. package/build/node.mjs.map +1 -1
  74. package/build/package-BGnFmux8.mjs +2 -0
  75. package/build/package-BGnFmux8.mjs.map +1 -0
  76. package/build/package-Dd7S_M7n.cjs +2 -0
  77. package/build/package-Dd7S_M7n.cjs.map +1 -0
  78. package/build/{power-BQGaOZjf.d.mts → power-DUs5Sw8n.d.mts} +22 -22
  79. package/build/{power-BQGaOZjf.d.mts.map → power-DUs5Sw8n.d.mts.map} +1 -1
  80. package/build/{power-CR0Hkay6.d.cts → power-zT64Y-Q9.d.cts} +22 -22
  81. package/build/{power-CR0Hkay6.d.cts.map → power-zT64Y-Q9.d.cts.map} +1 -1
  82. package/build/power.cjs +1 -1
  83. package/build/power.cjs.map +1 -1
  84. package/build/power.d.cts +2 -2
  85. package/build/power.d.mts +2 -2
  86. package/build/power.mjs +1 -1
  87. package/build/power.mjs.map +1 -1
  88. package/build/{relays-BCn6rQN1.d.cts → relays-8EGNIK1T.d.cts} +5 -2
  89. package/build/relays-8EGNIK1T.d.cts.map +1 -0
  90. package/build/{relays-BCn6rQN1.d.mts → relays-8EGNIK1T.d.mts} +5 -2
  91. package/build/relays-8EGNIK1T.d.mts.map +1 -0
  92. package/build/relays.cjs +1 -1
  93. package/build/relays.cjs.map +1 -1
  94. package/build/relays.d.cts +2 -2
  95. package/build/relays.d.mts +2 -2
  96. package/build/relays.mjs +1 -1
  97. package/build/relays.mjs.map +1 -1
  98. package/package.json +11 -1
  99. package/build/at1000-CQR8byxP.cjs +0 -3
  100. package/build/at1000-CQR8byxP.cjs.map +0 -1
  101. package/build/at1000-CrXOo07c.d.cts +0 -50
  102. package/build/at1000-CrXOo07c.d.cts.map +0 -1
  103. package/build/at1000-Dg_S21jO.mjs +0 -3
  104. package/build/at1000-Dg_S21jO.mjs.map +0 -1
  105. package/build/at1000-RNA4yNKP.d.mts +0 -50
  106. package/build/at1000-RNA4yNKP.d.mts.map +0 -1
  107. package/build/gpio-BDMTNT6V.d.mts.map +0 -1
  108. package/build/gpio-D1QpFMeE.d.cts.map +0 -1
  109. package/build/hmi-Cw9EEy_5.d.cts.map +0 -1
  110. package/build/hmi-Cw9EEy_5.d.mts.map +0 -1
  111. package/build/management-C0iQha23.d.cts.map +0 -1
  112. package/build/management-C0iQha23.d.mts.map +0 -1
  113. package/build/relays-BCn6rQN1.d.cts.map +0 -1
  114. package/build/relays-BCn6rQN1.d.mts.map +0 -1
package/README.md CHANGED
@@ -25,6 +25,27 @@ npm install @ikalogic/at1000
25
25
 
26
26
  For a complete guide on setting up and using the AT1000, please refer to the [Getting Started Guide](https://ikalogic.com/kb/at1000-api/100getting-started/).
27
27
 
28
+ ## Real-time events
29
+
30
+ Subscribe to live device state changes over a WebSocket. Fetch initial state via
31
+ REST, then listen for updates. Requires a runtime with a global `WebSocket`
32
+ (Node >= 22, Bun, or a browser).
33
+
34
+ ```ts
35
+ import { AT1000 } from '@ikalogic/at1000';
36
+
37
+ const device = await AT1000.create('at1000.local');
38
+
39
+ await device.events.connect();
40
+ const unsubscribe = device.events.on('gpio.state', (event) => {
41
+ console.log(`GPIO ${event.id} =`, event.data.value);
42
+ });
43
+
44
+ // later
45
+ unsubscribe();
46
+ device.events.close();
47
+ ```
48
+
28
49
  ## License
29
50
 
30
51
  This package is distributed under the [MIT License](LICENSE).
@@ -0,0 +1,69 @@
1
+ import { s as ComApi } from "./com-BzaKOH_B.mjs";
2
+ import { i as EventsApi } from "./events-PyyK1XbE.mjs";
3
+ import { f as ManagementApi, u as Information } from "./management-DliNzoq1.mjs";
4
+ import { s as GpioApi } from "./gpio-CbGcoT9b.mjs";
5
+ import { r as HmiApi } from "./hmi-B6LU4yaL.mjs";
6
+ import { i as PowerApi } from "./power-DUs5Sw8n.mjs";
7
+ import { n as RelaysApi } from "./relays-8EGNIK1T.mjs";
8
+
9
+ //#region src/at1000.d.ts
10
+ /** Structured error thrown for AT1000 API responses carrying `{ code, message, suggestion, path }`. */
11
+ declare class ApiError extends Error {
12
+ readonly code: string;
13
+ /** Raw human-readable server message (without code/suggestion/path). */
14
+ readonly detail: string;
15
+ readonly suggestion: string;
16
+ readonly path: string;
17
+ constructor(body: {
18
+ code: string;
19
+ message: string;
20
+ suggestion: string;
21
+ path: string;
22
+ });
23
+ }
24
+ /** @hideconstructor */
25
+ declare class AT1000Base {
26
+ /**
27
+ * IP address or hostname of the AT1000 device
28
+ */
29
+ readonly host: string;
30
+ /**
31
+ * Device information
32
+ */
33
+ readonly info: Information;
34
+ private fetch;
35
+ /**
36
+ * COM API
37
+ */
38
+ readonly com: ComApi;
39
+ /**
40
+ * HMI API
41
+ */
42
+ readonly hmi: HmiApi;
43
+ /**
44
+ * GPIO API
45
+ */
46
+ readonly gpio: GpioApi;
47
+ /**
48
+ * Management API
49
+ */
50
+ readonly management: ManagementApi;
51
+ /**
52
+ * Power management API
53
+ */
54
+ readonly power: PowerApi;
55
+ /**
56
+ * Relays API
57
+ */
58
+ readonly relays: RelaysApi;
59
+ /**
60
+ * Real-time events API (WebSocket). Call `events.connect()` then `events.on(...)`.
61
+ */
62
+ readonly events: EventsApi;
63
+ constructor(host: string, info: Information);
64
+ reset(): Promise<void>;
65
+ static create: (host: string) => Promise<AT1000Base>;
66
+ }
67
+ //#endregion
68
+ export { ApiError as n, AT1000Base as t };
69
+ //# sourceMappingURL=at1000-3evjqv7V.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"at1000-3evjqv7V.d.mts","names":[],"sources":["../src/at1000.ts"],"mappings":";;;;;;;;;;cAWa,QAAA,SAAiB,KAAK;EAAA,SACxB,IAAA;EADW;EAAA,SAGX,MAAA;EAAA,SACA,UAAA;EAAA,SACA,IAAA;cAEG,IAAA;IAAQ,IAAA;IAAc,OAAA;IAAiB,UAAA;IAAoB,IAAA;EAAA;AAAA;;cAsB5D,UAAA;EAtB0E;AAsBvF;;EAtBuF,SA0BrE,IAAA;EAIM;;;EAAA,SAAN,IAAA,EAAM,WAAA;EAAA,QAEd,KAAA;EAoBe;;;EAAA,SAhBP,GAAA,EAAK,MAAA;EA8EV;;;EAAA,SA1EK,GAAA,EAAK,MAAA;EAkF+B;;;EAAA,SA9EpC,IAAA,EAAM,OAAA;EAZd;;;EAAA,SAgBQ,UAAA,EAAY,aAAA;EARP;;;EAAA,SAYL,KAAA,EAAO,QAAA;EAJK;;;EAAA,SAQZ,MAAA,EAAQ,SAAA;EAAA;;;EAAA,SAIR,MAAA,EAAQ,SAAA;cAEZ,IAAA,UAAc,IAAA,EAAM,WAAA;EAoD1B,KAAA,IAAK,OAAA;EAAA,OAQG,MAAA,GAAgB,IAAA,aAAe,OAAA,CAAQ,UAAA;AAAA"}
@@ -0,0 +1,3 @@
1
+ const e=require("./chunk-CMqjfN_6.cjs"),t=require("./com.cjs"),n=require("./gpio.cjs"),r=require("./hmi.cjs"),i=require("./management.cjs"),a=require("./power.cjs"),o=require("./relays.cjs");let s=require("up-fetch"),c=require("valibot");c=e.t(c,1);const l=c.array(c.pipe(c.number(),c.integer(),c.minValue(0),c.maxValue(255))),u={"com.can_config":t.canConfig,"com.rs232_config":t.rs232Config,"com.rs485_config":t.rs485Config,"com.spi_config":t.spiConfig,"com.i2c_config":t.i2cConfig,"com.uart_config":t.uartConfig,"com.can_rx":c.array(t.canFrame),"com.uart_rx":l,"com.rs232_rx":l,"com.rs485_rx":l,"gpio.state":c.union([n.digitalState,n.analogState]),"gpio.config":c.union([n.analogInputConfiguration,n.analogOutputConfiguration,n.digitalInputConfiguration,n.digitalOutputConfiguration,n.openDrainConfiguration]),"gpio.sync":c.object({hold_state:c.picklist([`held`,`released`])}),"hmi.text":r.text,"hmi.colors":r.color,"hmi.progress":c.optional(r.progress),"hmi.play":r.playbackConfiguration,"hmi.knob":r.internalKnobEvent,"management.datetime":c.object({epoch_seconds:c.number()}),"management.calibration":i.calibrationData,"management.calibration_state":i.calibrationState,"management.project":i.projectEvent,"management.update_status":i.updateStatus,"power.dut":a.dutState,"power.usb":a.usbState,"relays.state":o.relayState,"relays.states":o.states},d=[`com.reset`,`gpio.reset`,`hmi.clear`,`hmi.reset`,`power.reset`,`relays.reset`],f=c.object({type:c.string(),id:c.optional(c.pipe(c.number(),c.integer())),ts:c.number(),data:c.optional(c.unknown())}),p=5e3,m=(...e)=>console.debug(`[at1000:events]`,...e);var h=class{host;socket;listeners=new Map;stateListeners=new Set;shouldReconnect=!1;reconnectDelayMs=500;reconnectTimer;_connected=!1;constructor(e){this.host=e}get connected(){return this._connected}async connect(e){if(typeof WebSocket>`u`)throw Error(`WebSocket is not available in this runtime (Node >= 22, Bun, or a browser is required)`);this._connected||await this.openSocket(e?.timeout??p)}close(){this.shouldReconnect=!1,this.reconnectTimer!==void 0&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0);let e=this.socket;this.socket=void 0,e!==void 0&&e.close(),this.setConnected(!1)}on(e,t){let n=this.listeners.get(e);return n===void 0&&(n=new Set,this.listeners.set(e,n)),n.add(t),()=>{let n=this.listeners.get(e);n!==void 0&&(n.delete(t),n.size===0&&this.listeners.delete(e))}}onStateChange(e){return this.stateListeners.add(e),()=>{this.stateListeners.delete(e)}}openSocket(e){let{promise:t,resolve:n,reject:r}=Promise.withResolvers(),i=`ws://${_(this.host)}/v0/events`,a=new WebSocket(i);this.socket=a;let o=!1,s=setTimeout(()=>{o||(o=!0,a.close(),r(Error(`Timed out connecting to ${i}`)))},e);return a.onopen=()=>{this.socket!==a||o||(o=!0,clearTimeout(s),this.shouldReconnect=!0,this.reconnectDelayMs=500,this.setConnected(!0),m(`open`,i),n())},a.onmessage=e=>{this.socket===a&&this.handleMessage(e.data)},a.onerror=()=>{m(`error`,i),this.socket===a&&!o&&(o=!0,clearTimeout(s),r(Error(`Failed to connect to ${i}`)))},a.onclose=()=>{if(this.socket===a){if(m(`close`,i),clearTimeout(s),this.setConnected(!1),!o){o=!0,r(Error(`Connection to ${i} closed before opening`));return}this.shouldReconnect&&this.scheduleReconnect()}},t}scheduleReconnect(){if(!this.shouldReconnect||this.reconnectTimer!==void 0)return;let e=this.reconnectDelayMs;m(`reconnecting in`,e,`ms`),this.reconnectDelayMs=Math.min(this.reconnectDelayMs*2,8e3),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0,this.shouldReconnect&&this.openSocket(p).catch(()=>{})},e)}handleMessage(e){if(typeof e!=`string`){m(`drop: non-string frame`);return}let t;try{t=JSON.parse(e)}catch{m(`drop: invalid JSON`,e);return}m(`recv`,t);let n=c.safeParse(f,t);if(!n.success){m(`drop: malformed envelope`,n.issues);return}let{type:r,id:i,ts:a}=n.output;if(r in u){let e=u[r],t=c.safeParse(e,n.output.data);if(!t.success){m(`drop: payload validation failed for`,r,t.issues);return}let o={type:r,id:i,ts:a,data:t.output};m(`dispatch`,r),this.emitTo(r,o),this.emitTo(`*`,o);return}if(d.includes(r)){let e={type:r,id:i,ts:a};m(`dispatch (dataless)`,r),this.emitTo(r,e),this.emitTo(`*`,e);return}m(`drop: unknown type`,r),this.emitTo(`*`,{type:r,id:i,ts:a,data:n.output.data})}emitTo(e,t){let n=this.listeners.get(e);if(n!==void 0)for(let e of n)try{e(t)}catch{}}setConnected(e){if(this._connected!==e){this._connected=e;for(let t of this.stateListeners)try{t(e)}catch{}}}},g=class extends Error{code;detail;suggestion;path;constructor(e){let t=e.suggestion.replace(/\n/g,`
2
+ `);super(`${e.message} (${e.code})\n Suggestion: ${t}\n Path: ${e.path}`),this.code=e.code,this.detail=e.message,this.suggestion=e.suggestion,this.path=e.path}};function _(e){return e.includes(`:`)&&!e.startsWith(`[`)&&e.indexOf(`:`)!==e.lastIndexOf(`:`)?`[${e}]`:e}var v=class e{host;info;fetch;com;hmi;gpio;management;power;relays;events;constructor(e,c){this.host=e,this.info=c,this.fetch=(0,s.up)(fetch,()=>({baseUrl:`http://${_(this.host)}/v0`,timeout:1e3,parseRejected:async(e,t)=>{let n,r=e.headers.get(`content-type`);try{n=r?.includes(`application/json`)?await e.json():await e.text()}catch{n=`Failed to parse response body`}throw typeof n==`object`&&[`code`,`message`,`suggestion`,`path`].every(e=>e in n)?new g({code:n.code,message:n.message,suggestion:n.suggestion,path:n.path}):Error(`API request to ${t.method} ${e.url} failed with status ${e.status}: ${typeof n==`string`?n:JSON.stringify(n)}`)}})),this.com=new t.ComApi(this.fetch),this.hmi=new r.HmiApi(this.fetch),this.gpio=new n.GpioApi(this.fetch,this.info),this.management=new i.ManagementApi(this.fetch),this.power=new a.PowerApi(this.fetch,this.info),this.relays=new o.RelaysApi(this.fetch),this.events=new h(this.host)}async reset(){await this.com.reset(),await this.hmi.reset(),await this.gpio.reset(),await this.power.reset(),await this.relays.reset()}static create=async t=>new e(t,await new i.ManagementApi((0,s.up)(fetch,()=>({baseUrl:`http://${_(t)}/v0`,timeout:1e3}))).info())};Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return v}});
3
+ //# sourceMappingURL=at1000-BAZbVgLx.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"at1000-BAZbVgLx.cjs","names":["v","canConfig","rs232Config","rs485Config","spiConfig","i2cConfig","uartConfig","canFrame","digitalState","analogState","analogInputConfiguration","analogOutputConfiguration","digitalInputConfiguration","digitalOutputConfiguration","openDrainConfiguration","text","color","progress","playbackConfiguration","internalKnobEvent","calibrationData","calibrationState","projectEvent","updateStatus","dutState","usbState","relayState","states","ComApi","HmiApi","GpioApi","ManagementApi","PowerApi","RelaysApi"],"sources":["../src/api/events.ts","../src/at1000.ts"],"sourcesContent":["import * as v from 'valibot';\n\nimport { format_host_for_url } from '../at1000';\nimport {\n canConfig,\n canFrame,\n i2cConfig,\n rs232Config,\n rs485Config,\n spiConfig,\n uartConfig,\n} from './com';\nimport {\n analogInputConfiguration,\n analogOutputConfiguration,\n analogState,\n digitalInputConfiguration,\n digitalOutputConfiguration,\n digitalState,\n openDrainConfiguration,\n} from './gpio';\nimport { color, internalKnobEvent, playbackConfiguration, progress, text } from './hmi';\nimport { calibrationData, calibrationState, projectEvent, updateStatus } from './management';\nimport { dutState, usbState } from './power';\nimport { relayState, states } from './relays';\n\n// Receive-side serial bytes. Distinct from com's `serialData`, whose string->bytes\n// transform is for request bodies; events deliver raw byte arrays.\nconst rxBytes = v.array(v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(255)));\n\n// Schema for the `data` payload of each event, keyed by the wire `type` literal.\nconst eventDataSchemas = {\n 'com.can_config': canConfig,\n 'com.rs232_config': rs232Config,\n 'com.rs485_config': rs485Config,\n 'com.spi_config': spiConfig,\n 'com.i2c_config': i2cConfig,\n 'com.uart_config': uartConfig,\n 'com.can_rx': v.array(canFrame),\n 'com.uart_rx': rxBytes,\n 'com.rs232_rx': rxBytes,\n 'com.rs485_rx': rxBytes,\n 'gpio.state': v.union([digitalState, analogState]),\n 'gpio.config': v.union([\n analogInputConfiguration,\n analogOutputConfiguration,\n digitalInputConfiguration,\n digitalOutputConfiguration,\n openDrainConfiguration,\n ]),\n // Mirrors the glz::meta of gpio::sync_hold_state_t (apps/server/src/api/resources/gpio.hpp).\n 'gpio.sync': v.object({ hold_state: v.picklist(['held', 'released']) }),\n 'hmi.text': text,\n 'hmi.colors': color,\n // `data` absent (progress hidden) is accepted by v.optional.\n 'hmi.progress': v.optional(progress),\n 'hmi.play': playbackConfiguration,\n 'hmi.knob': internalKnobEvent,\n 'management.datetime': v.object({ epoch_seconds: v.number() }),\n 'management.calibration': calibrationData,\n 'management.calibration_state': calibrationState,\n 'management.project': projectEvent,\n 'management.update_status': updateStatus,\n 'power.dut': dutState,\n 'power.usb': usbState,\n 'relays.state': relayState,\n 'relays.states': states,\n} as const;\n\nconst dataLessEvents = [\n 'com.reset',\n 'gpio.reset',\n 'hmi.clear',\n 'hmi.reset',\n 'power.reset',\n 'relays.reset',\n] as const;\n\nconst envelope = v.object({\n type: v.string(),\n id: v.optional(v.pipe(v.number(), v.integer())),\n ts: v.number(),\n data: v.optional(v.unknown()),\n});\n\ntype EventDataSchemas = typeof eventDataSchemas;\n\n/** Every event `type` literal the server can emit. */\nexport type EventType = keyof EventDataSchemas | (typeof dataLessEvents)[number];\n\ntype PayloadEvent = {\n [K in keyof EventDataSchemas]: {\n type: K;\n id?: number;\n ts: number;\n data: v.InferOutput<EventDataSchemas[K]>;\n };\n}[keyof EventDataSchemas];\n\ntype DataLessEvent = { type: (typeof dataLessEvents)[number]; id?: number; ts: number };\n\n/** A fully-typed device event for a known `type`. */\nexport type DeviceEvent = PayloadEvent | DataLessEvent;\n\n/**\n * Shape delivered to `'*'` listeners. Forward-compatible: a newer server may emit\n * a `type` this SDK does not model, surfaced here with an untyped `data`.\n */\nexport type AnyDeviceEvent = DeviceEvent | {\n type: string;\n id?: number;\n ts: number;\n data?: unknown;\n};\n\ntype EventListener = (event: AnyDeviceEvent) => void;\ntype StateListener = (connected: boolean) => void;\n\nconst reconnectBaseDelayMs = 500;\nconst reconnectMaxDelayMs = 8_000;\nconst defaultConnectTimeoutMs = 5_000;\n\nconst debugLog = (...args: unknown[]): void => console.debug('[at1000:events]', ...args);\n\n/** @hideconstructor */\nexport class EventsApi {\n private socket: WebSocket | undefined;\n private readonly listeners = new Map<string, Set<EventListener>>();\n private readonly stateListeners = new Set<StateListener>();\n private shouldReconnect = false;\n private reconnectDelayMs = reconnectBaseDelayMs;\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n private _connected = false;\n\n constructor(private readonly host: string) {}\n\n /** Whether the WebSocket is currently open. */\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Open the event stream. Resolves once the socket is open, rejects on failure or\n * after `timeout` ms. A no-op while already connected. Auto-reconnect arms only\n * after the first successful open, so a failed initial `connect()` (e.g. old\n * firmware without `/v0/events`) rejects without scheduling retries.\n */\n async connect(options?: { timeout?: number }): Promise<void> {\n if (typeof WebSocket === 'undefined') {\n throw new Error(\n 'WebSocket is not available in this runtime (Node >= 22, Bun, or a browser is required)',\n );\n }\n if (this._connected) {\n return;\n }\n await this.openSocket(options?.timeout ?? defaultConnectTimeoutMs);\n }\n\n /** Close the stream and cancel any pending reconnect. */\n close(): void {\n this.shouldReconnect = false;\n if (this.reconnectTimer !== undefined) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n const socket = this.socket;\n this.socket = undefined;\n if (socket !== undefined) {\n socket.close();\n }\n this.setConnected(false);\n }\n\n /**\n * Subscribe to an event `type`, or `'*'` for every event. Returns an unsubscribe\n * function. Listeners survive reconnects and are isolated: a throwing listener\n * does not break dispatch to the others.\n */\n on<T extends EventType>(\n type: T,\n listener: (event: Extract<DeviceEvent, { type: T }>) => void,\n ): () => void;\n on(type: '*', listener: (event: AnyDeviceEvent) => void): () => void;\n on(type: string, listener: (event: any) => void): () => void {\n let set = this.listeners.get(type);\n if (set === undefined) {\n set = new Set();\n this.listeners.set(type, set);\n }\n set.add(listener);\n return () => {\n const current = this.listeners.get(type);\n if (current === undefined) {\n return;\n }\n current.delete(listener);\n if (current.size === 0) {\n this.listeners.delete(type);\n }\n };\n }\n\n /** Subscribe to connection state changes. Returns an unsubscribe function. */\n onStateChange(listener: StateListener): () => void {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n private openSocket(timeoutMs: number): Promise<void> {\n const { promise, resolve, reject } = Promise.withResolvers<void>();\n const url = `ws://${format_host_for_url(this.host)}/v0/events`;\n const socket = new WebSocket(url);\n this.socket = socket;\n\n let settled = false;\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n socket.close();\n reject(new Error(`Timed out connecting to ${url}`));\n }\n }, timeoutMs);\n\n socket.onopen = () => {\n if (this.socket !== socket || settled) {\n return;\n }\n settled = true;\n clearTimeout(timer);\n this.shouldReconnect = true;\n this.reconnectDelayMs = reconnectBaseDelayMs;\n this.setConnected(true);\n debugLog('open', url);\n resolve();\n };\n\n socket.onmessage = (event) => {\n if (this.socket === socket) {\n this.handleMessage(event.data);\n }\n };\n\n socket.onerror = () => {\n debugLog('error', url);\n if (this.socket === socket && !settled) {\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to connect to ${url}`));\n }\n };\n\n socket.onclose = () => {\n if (this.socket !== socket) {\n return;\n }\n debugLog('close', url);\n clearTimeout(timer);\n this.setConnected(false);\n if (!settled) {\n settled = true;\n reject(new Error(`Connection to ${url} closed before opening`));\n return;\n }\n if (this.shouldReconnect) {\n this.scheduleReconnect();\n }\n };\n\n return promise;\n }\n\n private scheduleReconnect(): void {\n if (!this.shouldReconnect || this.reconnectTimer !== undefined) {\n return;\n }\n const delay = this.reconnectDelayMs;\n debugLog('reconnecting in', delay, 'ms');\n this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, reconnectMaxDelayMs);\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = undefined;\n if (!this.shouldReconnect) {\n return;\n }\n // A failed attempt rejects this promise; onclose then schedules the next retry.\n this.openSocket(defaultConnectTimeoutMs).catch(() => {});\n }, delay);\n }\n\n private handleMessage(data: unknown): void {\n if (typeof data !== 'string') {\n debugLog('drop: non-string frame');\n return;\n }\n let raw: unknown;\n try {\n raw = JSON.parse(data);\n } catch {\n debugLog('drop: invalid JSON', data);\n return;\n }\n\n debugLog('recv', raw);\n const parsed = v.safeParse(envelope, raw);\n if (!parsed.success) {\n debugLog('drop: malformed envelope', parsed.issues);\n return;\n }\n const { type, id, ts } = parsed.output;\n\n if (type in eventDataSchemas) {\n const schema = eventDataSchemas[type as keyof EventDataSchemas] as v.GenericSchema;\n const payload = v.safeParse(schema, parsed.output.data);\n if (!payload.success) {\n debugLog('drop: payload validation failed for', type, payload.issues);\n return;\n }\n const event: AnyDeviceEvent = { type, id, ts, data: payload.output };\n debugLog('dispatch', type);\n this.emitTo(type, event);\n this.emitTo('*', event);\n return;\n }\n\n if ((dataLessEvents as readonly string[]).includes(type)) {\n const event: AnyDeviceEvent = { type, id, ts };\n debugLog('dispatch (dataless)', type);\n this.emitTo(type, event);\n this.emitTo('*', event);\n return;\n }\n\n // Unknown type: deliver the raw envelope to '*' only (server may be newer).\n debugLog('drop: unknown type', type);\n this.emitTo('*', { type, id, ts, data: parsed.output.data });\n }\n\n private emitTo(key: string, event: AnyDeviceEvent): void {\n const set = this.listeners.get(key);\n if (set === undefined) {\n return;\n }\n for (const listener of set) {\n try {\n listener(event);\n } catch {\n // One throwing listener must not break dispatch to the others.\n }\n }\n }\n\n private setConnected(connected: boolean): void {\n if (this._connected === connected) {\n return;\n }\n this._connected = connected;\n for (const listener of this.stateListeners) {\n try {\n listener(connected);\n } catch {\n // Ignore throwing state listeners.\n }\n }\n }\n}\n","import { up, type UpFetch } from 'up-fetch';\n\nimport { ComApi } from './api/com';\nimport { EventsApi } from './api/events';\nimport { GpioApi } from './api/gpio';\nimport { HmiApi } from './api/hmi';\nimport { type Information, ManagementApi } from './api/management';\nimport { PowerApi } from './api/power';\nimport { RelaysApi } from './api/relays';\n\n/** Structured error thrown for AT1000 API responses carrying `{ code, message, suggestion, path }`. */\nexport class ApiError extends Error {\n readonly code: string;\n /** Raw human-readable server message (without code/suggestion/path). */\n readonly detail: string;\n readonly suggestion: string;\n readonly path: string;\n\n constructor(body: { code: string; message: string; suggestion: string; path: string }) {\n const formattedSuggestion = body.suggestion.replace(/\\n/g, '\\n ');\n super(\n `${body.message} (${body.code})\\n Suggestion: ${formattedSuggestion}\\n Path: ${body.path}`,\n );\n this.code = body.code;\n this.detail = body.message;\n this.suggestion = body.suggestion;\n this.path = body.path;\n }\n}\n\n/** Wraps IPv6 literal addresses in brackets for use in URLs (RFC 3986). */\nexport function format_host_for_url(host: string): string {\n // Only bracket real IPv6 literals (>=2 colons); leave host:port untouched.\n if (host.includes(':') && !host.startsWith('[') && host.indexOf(':') !== host.lastIndexOf(':')) {\n return `[${host}]`;\n }\n return host;\n}\n\n/** @hideconstructor */\nexport class AT1000Base {\n /**\n * IP address or hostname of the AT1000 device\n */\n public readonly host: string;\n /**\n * Device information\n */\n public readonly info: Information;\n\n private fetch: UpFetch;\n /**\n * COM API\n */\n public readonly com: ComApi;\n /**\n * HMI API\n */\n public readonly hmi: HmiApi;\n /**\n * GPIO API\n */\n public readonly gpio: GpioApi;\n /**\n * Management API\n */\n public readonly management: ManagementApi;\n /**\n * Power management API\n */\n public readonly power: PowerApi;\n /**\n * Relays API\n */\n public readonly relays: RelaysApi;\n /**\n * Real-time events API (WebSocket). Call `events.connect()` then `events.on(...)`.\n */\n public readonly events: EventsApi;\n\n constructor(host: string, info: Information) {\n this.host = host;\n this.info = info;\n\n this.fetch = up(\n fetch,\n () => ({\n baseUrl: `http://${format_host_for_url(this.host)}/v0`,\n timeout: 1000,\n parseRejected: async (response, request) => {\n let errorData: any;\n const contentType = response.headers.get('content-type');\n try {\n if (contentType?.includes('application/json')) {\n errorData = await response.json();\n } else {\n errorData = await response.text();\n }\n } catch {\n errorData = 'Failed to parse response body';\n }\n\n if (\n typeof errorData === 'object'\n && ['code', 'message', 'suggestion', 'path'].every((key) => key in errorData)\n ) {\n throw new ApiError({\n code: errorData.code,\n message: errorData.message,\n suggestion: errorData.suggestion,\n path: errorData.path,\n });\n }\n\n throw new Error(\n `API request to ${request.method} ${response.url} failed with status ${response.status}: ${\n typeof errorData === 'string' ? errorData : JSON.stringify(errorData)\n }`,\n );\n },\n }),\n );\n\n this.com = new ComApi(this.fetch);\n this.hmi = new HmiApi(this.fetch);\n this.gpio = new GpioApi(this.fetch, this.info);\n this.management = new ManagementApi(this.fetch);\n this.power = new PowerApi(this.fetch, this.info);\n this.relays = new RelaysApi(this.fetch);\n this.events = new EventsApi(this.host);\n }\n\n async reset() {\n await this.com.reset();\n await this.hmi.reset();\n await this.gpio.reset();\n await this.power.reset();\n await this.relays.reset();\n }\n\n public static create = async (host: string): Promise<AT1000Base> => {\n // Create a temporary client to fetch device info\n const management = new ManagementApi(\n up(fetch, () => ({ baseUrl: `http://${format_host_for_url(host)}/v0`, timeout: 1000 })),\n );\n const info = await management.info();\n\n return new AT1000Base(host, info);\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import{ComApi as e,canConfig as t,canFrame as n,i2cConfig as r,rs232Config as i,rs485Config as a,spiConfig as o,uartConfig as s}from"./com.mjs";import{GpioApi as c,analogInputConfiguration as l,analogOutputConfiguration as u,analogState as d,digitalInputConfiguration as f,digitalOutputConfiguration as p,digitalState as m,openDrainConfiguration as h}from"./gpio.mjs";import{HmiApi as g,color as _,internalKnobEvent as v,playbackConfiguration as y,progress as b,text as x}from"./hmi.mjs";import{ManagementApi as S,calibrationData as C,calibrationState as w,projectEvent as T,updateStatus as E}from"./management.mjs";import{PowerApi as D,dutState as O,usbState as k}from"./power.mjs";import{RelaysApi as A,relayState as j,states as M}from"./relays.mjs";import{up as N}from"up-fetch";import*as P from"valibot";const F=P.array(P.pipe(P.number(),P.integer(),P.minValue(0),P.maxValue(255))),I={"com.can_config":t,"com.rs232_config":i,"com.rs485_config":a,"com.spi_config":o,"com.i2c_config":r,"com.uart_config":s,"com.can_rx":P.array(n),"com.uart_rx":F,"com.rs232_rx":F,"com.rs485_rx":F,"gpio.state":P.union([m,d]),"gpio.config":P.union([l,u,f,p,h]),"gpio.sync":P.object({hold_state:P.picklist([`held`,`released`])}),"hmi.text":x,"hmi.colors":_,"hmi.progress":P.optional(b),"hmi.play":y,"hmi.knob":v,"management.datetime":P.object({epoch_seconds:P.number()}),"management.calibration":C,"management.calibration_state":w,"management.project":T,"management.update_status":E,"power.dut":O,"power.usb":k,"relays.state":j,"relays.states":M},L=[`com.reset`,`gpio.reset`,`hmi.clear`,`hmi.reset`,`power.reset`,`relays.reset`],R=P.object({type:P.string(),id:P.optional(P.pipe(P.number(),P.integer())),ts:P.number(),data:P.optional(P.unknown())}),z=5e3,B=(...e)=>console.debug(`[at1000:events]`,...e);var V=class{host;socket;listeners=new Map;stateListeners=new Set;shouldReconnect=!1;reconnectDelayMs=500;reconnectTimer;_connected=!1;constructor(e){this.host=e}get connected(){return this._connected}async connect(e){if(typeof WebSocket>`u`)throw Error(`WebSocket is not available in this runtime (Node >= 22, Bun, or a browser is required)`);this._connected||await this.openSocket(e?.timeout??z)}close(){this.shouldReconnect=!1,this.reconnectTimer!==void 0&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0);let e=this.socket;this.socket=void 0,e!==void 0&&e.close(),this.setConnected(!1)}on(e,t){let n=this.listeners.get(e);return n===void 0&&(n=new Set,this.listeners.set(e,n)),n.add(t),()=>{let n=this.listeners.get(e);n!==void 0&&(n.delete(t),n.size===0&&this.listeners.delete(e))}}onStateChange(e){return this.stateListeners.add(e),()=>{this.stateListeners.delete(e)}}openSocket(e){let{promise:t,resolve:n,reject:r}=Promise.withResolvers(),i=`ws://${U(this.host)}/v0/events`,a=new WebSocket(i);this.socket=a;let o=!1,s=setTimeout(()=>{o||(o=!0,a.close(),r(Error(`Timed out connecting to ${i}`)))},e);return a.onopen=()=>{this.socket!==a||o||(o=!0,clearTimeout(s),this.shouldReconnect=!0,this.reconnectDelayMs=500,this.setConnected(!0),B(`open`,i),n())},a.onmessage=e=>{this.socket===a&&this.handleMessage(e.data)},a.onerror=()=>{B(`error`,i),this.socket===a&&!o&&(o=!0,clearTimeout(s),r(Error(`Failed to connect to ${i}`)))},a.onclose=()=>{if(this.socket===a){if(B(`close`,i),clearTimeout(s),this.setConnected(!1),!o){o=!0,r(Error(`Connection to ${i} closed before opening`));return}this.shouldReconnect&&this.scheduleReconnect()}},t}scheduleReconnect(){if(!this.shouldReconnect||this.reconnectTimer!==void 0)return;let e=this.reconnectDelayMs;B(`reconnecting in`,e,`ms`),this.reconnectDelayMs=Math.min(this.reconnectDelayMs*2,8e3),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0,this.shouldReconnect&&this.openSocket(z).catch(()=>{})},e)}handleMessage(e){if(typeof e!=`string`){B(`drop: non-string frame`);return}let t;try{t=JSON.parse(e)}catch{B(`drop: invalid JSON`,e);return}B(`recv`,t);let n=P.safeParse(R,t);if(!n.success){B(`drop: malformed envelope`,n.issues);return}let{type:r,id:i,ts:a}=n.output;if(r in I){let e=I[r],t=P.safeParse(e,n.output.data);if(!t.success){B(`drop: payload validation failed for`,r,t.issues);return}let o={type:r,id:i,ts:a,data:t.output};B(`dispatch`,r),this.emitTo(r,o),this.emitTo(`*`,o);return}if(L.includes(r)){let e={type:r,id:i,ts:a};B(`dispatch (dataless)`,r),this.emitTo(r,e),this.emitTo(`*`,e);return}B(`drop: unknown type`,r),this.emitTo(`*`,{type:r,id:i,ts:a,data:n.output.data})}emitTo(e,t){let n=this.listeners.get(e);if(n!==void 0)for(let e of n)try{e(t)}catch{}}setConnected(e){if(this._connected!==e){this._connected=e;for(let t of this.stateListeners)try{t(e)}catch{}}}},H=class extends Error{code;detail;suggestion;path;constructor(e){let t=e.suggestion.replace(/\n/g,`
2
+ `);super(`${e.message} (${e.code})\n Suggestion: ${t}\n Path: ${e.path}`),this.code=e.code,this.detail=e.message,this.suggestion=e.suggestion,this.path=e.path}};function U(e){return e.includes(`:`)&&!e.startsWith(`[`)&&e.indexOf(`:`)!==e.lastIndexOf(`:`)?`[${e}]`:e}var W=class t{host;info;fetch;com;hmi;gpio;management;power;relays;events;constructor(t,n){this.host=t,this.info=n,this.fetch=N(fetch,()=>({baseUrl:`http://${U(this.host)}/v0`,timeout:1e3,parseRejected:async(e,t)=>{let n,r=e.headers.get(`content-type`);try{n=r?.includes(`application/json`)?await e.json():await e.text()}catch{n=`Failed to parse response body`}throw typeof n==`object`&&[`code`,`message`,`suggestion`,`path`].every(e=>e in n)?new H({code:n.code,message:n.message,suggestion:n.suggestion,path:n.path}):Error(`API request to ${t.method} ${e.url} failed with status ${e.status}: ${typeof n==`string`?n:JSON.stringify(n)}`)}})),this.com=new e(this.fetch),this.hmi=new g(this.fetch),this.gpio=new c(this.fetch,this.info),this.management=new S(this.fetch),this.power=new D(this.fetch,this.info),this.relays=new A(this.fetch),this.events=new V(this.host)}async reset(){await this.com.reset(),await this.hmi.reset(),await this.gpio.reset(),await this.power.reset(),await this.relays.reset()}static create=async e=>new t(e,await new S(N(fetch,()=>({baseUrl:`http://${U(e)}/v0`,timeout:1e3}))).info())};export{H as n,V as r,W as t};
3
+ //# sourceMappingURL=at1000-Cxx254i3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"at1000-Cxx254i3.mjs","names":[],"sources":["../src/api/events.ts","../src/at1000.ts"],"sourcesContent":["import * as v from 'valibot';\n\nimport { format_host_for_url } from '../at1000';\nimport {\n canConfig,\n canFrame,\n i2cConfig,\n rs232Config,\n rs485Config,\n spiConfig,\n uartConfig,\n} from './com';\nimport {\n analogInputConfiguration,\n analogOutputConfiguration,\n analogState,\n digitalInputConfiguration,\n digitalOutputConfiguration,\n digitalState,\n openDrainConfiguration,\n} from './gpio';\nimport { color, internalKnobEvent, playbackConfiguration, progress, text } from './hmi';\nimport { calibrationData, calibrationState, projectEvent, updateStatus } from './management';\nimport { dutState, usbState } from './power';\nimport { relayState, states } from './relays';\n\n// Receive-side serial bytes. Distinct from com's `serialData`, whose string->bytes\n// transform is for request bodies; events deliver raw byte arrays.\nconst rxBytes = v.array(v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(255)));\n\n// Schema for the `data` payload of each event, keyed by the wire `type` literal.\nconst eventDataSchemas = {\n 'com.can_config': canConfig,\n 'com.rs232_config': rs232Config,\n 'com.rs485_config': rs485Config,\n 'com.spi_config': spiConfig,\n 'com.i2c_config': i2cConfig,\n 'com.uart_config': uartConfig,\n 'com.can_rx': v.array(canFrame),\n 'com.uart_rx': rxBytes,\n 'com.rs232_rx': rxBytes,\n 'com.rs485_rx': rxBytes,\n 'gpio.state': v.union([digitalState, analogState]),\n 'gpio.config': v.union([\n analogInputConfiguration,\n analogOutputConfiguration,\n digitalInputConfiguration,\n digitalOutputConfiguration,\n openDrainConfiguration,\n ]),\n // Mirrors the glz::meta of gpio::sync_hold_state_t (apps/server/src/api/resources/gpio.hpp).\n 'gpio.sync': v.object({ hold_state: v.picklist(['held', 'released']) }),\n 'hmi.text': text,\n 'hmi.colors': color,\n // `data` absent (progress hidden) is accepted by v.optional.\n 'hmi.progress': v.optional(progress),\n 'hmi.play': playbackConfiguration,\n 'hmi.knob': internalKnobEvent,\n 'management.datetime': v.object({ epoch_seconds: v.number() }),\n 'management.calibration': calibrationData,\n 'management.calibration_state': calibrationState,\n 'management.project': projectEvent,\n 'management.update_status': updateStatus,\n 'power.dut': dutState,\n 'power.usb': usbState,\n 'relays.state': relayState,\n 'relays.states': states,\n} as const;\n\nconst dataLessEvents = [\n 'com.reset',\n 'gpio.reset',\n 'hmi.clear',\n 'hmi.reset',\n 'power.reset',\n 'relays.reset',\n] as const;\n\nconst envelope = v.object({\n type: v.string(),\n id: v.optional(v.pipe(v.number(), v.integer())),\n ts: v.number(),\n data: v.optional(v.unknown()),\n});\n\ntype EventDataSchemas = typeof eventDataSchemas;\n\n/** Every event `type` literal the server can emit. */\nexport type EventType = keyof EventDataSchemas | (typeof dataLessEvents)[number];\n\ntype PayloadEvent = {\n [K in keyof EventDataSchemas]: {\n type: K;\n id?: number;\n ts: number;\n data: v.InferOutput<EventDataSchemas[K]>;\n };\n}[keyof EventDataSchemas];\n\ntype DataLessEvent = { type: (typeof dataLessEvents)[number]; id?: number; ts: number };\n\n/** A fully-typed device event for a known `type`. */\nexport type DeviceEvent = PayloadEvent | DataLessEvent;\n\n/**\n * Shape delivered to `'*'` listeners. Forward-compatible: a newer server may emit\n * a `type` this SDK does not model, surfaced here with an untyped `data`.\n */\nexport type AnyDeviceEvent = DeviceEvent | {\n type: string;\n id?: number;\n ts: number;\n data?: unknown;\n};\n\ntype EventListener = (event: AnyDeviceEvent) => void;\ntype StateListener = (connected: boolean) => void;\n\nconst reconnectBaseDelayMs = 500;\nconst reconnectMaxDelayMs = 8_000;\nconst defaultConnectTimeoutMs = 5_000;\n\nconst debugLog = (...args: unknown[]): void => console.debug('[at1000:events]', ...args);\n\n/** @hideconstructor */\nexport class EventsApi {\n private socket: WebSocket | undefined;\n private readonly listeners = new Map<string, Set<EventListener>>();\n private readonly stateListeners = new Set<StateListener>();\n private shouldReconnect = false;\n private reconnectDelayMs = reconnectBaseDelayMs;\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n private _connected = false;\n\n constructor(private readonly host: string) {}\n\n /** Whether the WebSocket is currently open. */\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Open the event stream. Resolves once the socket is open, rejects on failure or\n * after `timeout` ms. A no-op while already connected. Auto-reconnect arms only\n * after the first successful open, so a failed initial `connect()` (e.g. old\n * firmware without `/v0/events`) rejects without scheduling retries.\n */\n async connect(options?: { timeout?: number }): Promise<void> {\n if (typeof WebSocket === 'undefined') {\n throw new Error(\n 'WebSocket is not available in this runtime (Node >= 22, Bun, or a browser is required)',\n );\n }\n if (this._connected) {\n return;\n }\n await this.openSocket(options?.timeout ?? defaultConnectTimeoutMs);\n }\n\n /** Close the stream and cancel any pending reconnect. */\n close(): void {\n this.shouldReconnect = false;\n if (this.reconnectTimer !== undefined) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n const socket = this.socket;\n this.socket = undefined;\n if (socket !== undefined) {\n socket.close();\n }\n this.setConnected(false);\n }\n\n /**\n * Subscribe to an event `type`, or `'*'` for every event. Returns an unsubscribe\n * function. Listeners survive reconnects and are isolated: a throwing listener\n * does not break dispatch to the others.\n */\n on<T extends EventType>(\n type: T,\n listener: (event: Extract<DeviceEvent, { type: T }>) => void,\n ): () => void;\n on(type: '*', listener: (event: AnyDeviceEvent) => void): () => void;\n on(type: string, listener: (event: any) => void): () => void {\n let set = this.listeners.get(type);\n if (set === undefined) {\n set = new Set();\n this.listeners.set(type, set);\n }\n set.add(listener);\n return () => {\n const current = this.listeners.get(type);\n if (current === undefined) {\n return;\n }\n current.delete(listener);\n if (current.size === 0) {\n this.listeners.delete(type);\n }\n };\n }\n\n /** Subscribe to connection state changes. Returns an unsubscribe function. */\n onStateChange(listener: StateListener): () => void {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n private openSocket(timeoutMs: number): Promise<void> {\n const { promise, resolve, reject } = Promise.withResolvers<void>();\n const url = `ws://${format_host_for_url(this.host)}/v0/events`;\n const socket = new WebSocket(url);\n this.socket = socket;\n\n let settled = false;\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n socket.close();\n reject(new Error(`Timed out connecting to ${url}`));\n }\n }, timeoutMs);\n\n socket.onopen = () => {\n if (this.socket !== socket || settled) {\n return;\n }\n settled = true;\n clearTimeout(timer);\n this.shouldReconnect = true;\n this.reconnectDelayMs = reconnectBaseDelayMs;\n this.setConnected(true);\n debugLog('open', url);\n resolve();\n };\n\n socket.onmessage = (event) => {\n if (this.socket === socket) {\n this.handleMessage(event.data);\n }\n };\n\n socket.onerror = () => {\n debugLog('error', url);\n if (this.socket === socket && !settled) {\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to connect to ${url}`));\n }\n };\n\n socket.onclose = () => {\n if (this.socket !== socket) {\n return;\n }\n debugLog('close', url);\n clearTimeout(timer);\n this.setConnected(false);\n if (!settled) {\n settled = true;\n reject(new Error(`Connection to ${url} closed before opening`));\n return;\n }\n if (this.shouldReconnect) {\n this.scheduleReconnect();\n }\n };\n\n return promise;\n }\n\n private scheduleReconnect(): void {\n if (!this.shouldReconnect || this.reconnectTimer !== undefined) {\n return;\n }\n const delay = this.reconnectDelayMs;\n debugLog('reconnecting in', delay, 'ms');\n this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, reconnectMaxDelayMs);\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = undefined;\n if (!this.shouldReconnect) {\n return;\n }\n // A failed attempt rejects this promise; onclose then schedules the next retry.\n this.openSocket(defaultConnectTimeoutMs).catch(() => {});\n }, delay);\n }\n\n private handleMessage(data: unknown): void {\n if (typeof data !== 'string') {\n debugLog('drop: non-string frame');\n return;\n }\n let raw: unknown;\n try {\n raw = JSON.parse(data);\n } catch {\n debugLog('drop: invalid JSON', data);\n return;\n }\n\n debugLog('recv', raw);\n const parsed = v.safeParse(envelope, raw);\n if (!parsed.success) {\n debugLog('drop: malformed envelope', parsed.issues);\n return;\n }\n const { type, id, ts } = parsed.output;\n\n if (type in eventDataSchemas) {\n const schema = eventDataSchemas[type as keyof EventDataSchemas] as v.GenericSchema;\n const payload = v.safeParse(schema, parsed.output.data);\n if (!payload.success) {\n debugLog('drop: payload validation failed for', type, payload.issues);\n return;\n }\n const event: AnyDeviceEvent = { type, id, ts, data: payload.output };\n debugLog('dispatch', type);\n this.emitTo(type, event);\n this.emitTo('*', event);\n return;\n }\n\n if ((dataLessEvents as readonly string[]).includes(type)) {\n const event: AnyDeviceEvent = { type, id, ts };\n debugLog('dispatch (dataless)', type);\n this.emitTo(type, event);\n this.emitTo('*', event);\n return;\n }\n\n // Unknown type: deliver the raw envelope to '*' only (server may be newer).\n debugLog('drop: unknown type', type);\n this.emitTo('*', { type, id, ts, data: parsed.output.data });\n }\n\n private emitTo(key: string, event: AnyDeviceEvent): void {\n const set = this.listeners.get(key);\n if (set === undefined) {\n return;\n }\n for (const listener of set) {\n try {\n listener(event);\n } catch {\n // One throwing listener must not break dispatch to the others.\n }\n }\n }\n\n private setConnected(connected: boolean): void {\n if (this._connected === connected) {\n return;\n }\n this._connected = connected;\n for (const listener of this.stateListeners) {\n try {\n listener(connected);\n } catch {\n // Ignore throwing state listeners.\n }\n }\n }\n}\n","import { up, type UpFetch } from 'up-fetch';\n\nimport { ComApi } from './api/com';\nimport { EventsApi } from './api/events';\nimport { GpioApi } from './api/gpio';\nimport { HmiApi } from './api/hmi';\nimport { type Information, ManagementApi } from './api/management';\nimport { PowerApi } from './api/power';\nimport { RelaysApi } from './api/relays';\n\n/** Structured error thrown for AT1000 API responses carrying `{ code, message, suggestion, path }`. */\nexport class ApiError extends Error {\n readonly code: string;\n /** Raw human-readable server message (without code/suggestion/path). */\n readonly detail: string;\n readonly suggestion: string;\n readonly path: string;\n\n constructor(body: { code: string; message: string; suggestion: string; path: string }) {\n const formattedSuggestion = body.suggestion.replace(/\\n/g, '\\n ');\n super(\n `${body.message} (${body.code})\\n Suggestion: ${formattedSuggestion}\\n Path: ${body.path}`,\n );\n this.code = body.code;\n this.detail = body.message;\n this.suggestion = body.suggestion;\n this.path = body.path;\n }\n}\n\n/** Wraps IPv6 literal addresses in brackets for use in URLs (RFC 3986). */\nexport function format_host_for_url(host: string): string {\n // Only bracket real IPv6 literals (>=2 colons); leave host:port untouched.\n if (host.includes(':') && !host.startsWith('[') && host.indexOf(':') !== host.lastIndexOf(':')) {\n return `[${host}]`;\n }\n return host;\n}\n\n/** @hideconstructor */\nexport class AT1000Base {\n /**\n * IP address or hostname of the AT1000 device\n */\n public readonly host: string;\n /**\n * Device information\n */\n public readonly info: Information;\n\n private fetch: UpFetch;\n /**\n * COM API\n */\n public readonly com: ComApi;\n /**\n * HMI API\n */\n public readonly hmi: HmiApi;\n /**\n * GPIO API\n */\n public readonly gpio: GpioApi;\n /**\n * Management API\n */\n public readonly management: ManagementApi;\n /**\n * Power management API\n */\n public readonly power: PowerApi;\n /**\n * Relays API\n */\n public readonly relays: RelaysApi;\n /**\n * Real-time events API (WebSocket). Call `events.connect()` then `events.on(...)`.\n */\n public readonly events: EventsApi;\n\n constructor(host: string, info: Information) {\n this.host = host;\n this.info = info;\n\n this.fetch = up(\n fetch,\n () => ({\n baseUrl: `http://${format_host_for_url(this.host)}/v0`,\n timeout: 1000,\n parseRejected: async (response, request) => {\n let errorData: any;\n const contentType = response.headers.get('content-type');\n try {\n if (contentType?.includes('application/json')) {\n errorData = await response.json();\n } else {\n errorData = await response.text();\n }\n } catch {\n errorData = 'Failed to parse response body';\n }\n\n if (\n typeof errorData === 'object'\n && ['code', 'message', 'suggestion', 'path'].every((key) => key in errorData)\n ) {\n throw new ApiError({\n code: errorData.code,\n message: errorData.message,\n suggestion: errorData.suggestion,\n path: errorData.path,\n });\n }\n\n throw new Error(\n `API request to ${request.method} ${response.url} failed with status ${response.status}: ${\n typeof errorData === 'string' ? errorData : JSON.stringify(errorData)\n }`,\n );\n },\n }),\n );\n\n this.com = new ComApi(this.fetch);\n this.hmi = new HmiApi(this.fetch);\n this.gpio = new GpioApi(this.fetch, this.info);\n this.management = new ManagementApi(this.fetch);\n this.power = new PowerApi(this.fetch, this.info);\n this.relays = new RelaysApi(this.fetch);\n this.events = new EventsApi(this.host);\n }\n\n async reset() {\n await this.com.reset();\n await this.hmi.reset();\n await this.gpio.reset();\n await this.power.reset();\n await this.relays.reset();\n }\n\n public static create = async (host: string): Promise<AT1000Base> => {\n // Create a temporary client to fetch device info\n const management = new ManagementApi(\n up(fetch, () => ({ baseUrl: `http://${format_host_for_url(host)}/v0`, timeout: 1000 })),\n );\n const info = await management.info();\n\n return new AT1000Base(host, info);\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import { s as ComApi } from "./com-BzaKOH_B.cjs";
2
+ import { i as EventsApi } from "./events-PyyK1XbE.cjs";
3
+ import { f as ManagementApi, u as Information } from "./management-DliNzoq1.cjs";
4
+ import { s as GpioApi } from "./gpio-CHg2nztz.cjs";
5
+ import { r as HmiApi } from "./hmi-B6LU4yaL.cjs";
6
+ import { i as PowerApi } from "./power-zT64Y-Q9.cjs";
7
+ import { n as RelaysApi } from "./relays-8EGNIK1T.cjs";
8
+
9
+ //#region src/at1000.d.ts
10
+ /** Structured error thrown for AT1000 API responses carrying `{ code, message, suggestion, path }`. */
11
+ declare class ApiError extends Error {
12
+ readonly code: string;
13
+ /** Raw human-readable server message (without code/suggestion/path). */
14
+ readonly detail: string;
15
+ readonly suggestion: string;
16
+ readonly path: string;
17
+ constructor(body: {
18
+ code: string;
19
+ message: string;
20
+ suggestion: string;
21
+ path: string;
22
+ });
23
+ }
24
+ /** @hideconstructor */
25
+ declare class AT1000Base {
26
+ /**
27
+ * IP address or hostname of the AT1000 device
28
+ */
29
+ readonly host: string;
30
+ /**
31
+ * Device information
32
+ */
33
+ readonly info: Information;
34
+ private fetch;
35
+ /**
36
+ * COM API
37
+ */
38
+ readonly com: ComApi;
39
+ /**
40
+ * HMI API
41
+ */
42
+ readonly hmi: HmiApi;
43
+ /**
44
+ * GPIO API
45
+ */
46
+ readonly gpio: GpioApi;
47
+ /**
48
+ * Management API
49
+ */
50
+ readonly management: ManagementApi;
51
+ /**
52
+ * Power management API
53
+ */
54
+ readonly power: PowerApi;
55
+ /**
56
+ * Relays API
57
+ */
58
+ readonly relays: RelaysApi;
59
+ /**
60
+ * Real-time events API (WebSocket). Call `events.connect()` then `events.on(...)`.
61
+ */
62
+ readonly events: EventsApi;
63
+ constructor(host: string, info: Information);
64
+ reset(): Promise<void>;
65
+ static create: (host: string) => Promise<AT1000Base>;
66
+ }
67
+ //#endregion
68
+ export { ApiError as n, AT1000Base as t };
69
+ //# sourceMappingURL=at1000-D_y0MxFy.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"at1000-D_y0MxFy.d.cts","names":[],"sources":["../src/at1000.ts"],"mappings":";;;;;;;;;;cAWa,QAAA,SAAiB,KAAK;EAAA,SACxB,IAAA;EADW;EAAA,SAGX,MAAA;EAAA,SACA,UAAA;EAAA,SACA,IAAA;cAEG,IAAA;IAAQ,IAAA;IAAc,OAAA;IAAiB,UAAA;IAAoB,IAAA;EAAA;AAAA;;cAsB5D,UAAA;EAtB0E;AAsBvF;;EAtBuF,SA0BrE,IAAA;EAIM;;;EAAA,SAAN,IAAA,EAAM,WAAA;EAAA,QAEd,KAAA;EAoBe;;;EAAA,SAhBP,GAAA,EAAK,MAAA;EA8EV;;;EAAA,SA1EK,GAAA,EAAK,MAAA;EAkF+B;;;EAAA,SA9EpC,IAAA,EAAM,OAAA;EAZd;;;EAAA,SAgBQ,UAAA,EAAY,aAAA;EARP;;;EAAA,SAYL,KAAA,EAAO,QAAA;EAJK;;;EAAA,SAQZ,MAAA,EAAQ,SAAA;EAAA;;;EAAA,SAIR,MAAA,EAAQ,SAAA;cAEZ,IAAA,UAAc,IAAA,EAAM,WAAA;EAoD1B,KAAA,IAAK,OAAA;EAAA,OAQG,MAAA,GAAgB,IAAA,aAAe,OAAA,CAAQ,UAAA;AAAA"}
package/build/browser.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./at1000-CQR8byxP.cjs");var t=class extends e.t{static apiVersion=e.n};exports.AT1000=t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./package-Dd7S_M7n.cjs"),t=require("./at1000-BAZbVgLx.cjs");var n=class extends t.t{static apiVersion=e.t};exports.AT1000=n,exports.ApiError=t.n;
2
2
  //# sourceMappingURL=browser.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.cjs","names":["AT1000Base","version"],"sources":["../src/browser.ts"],"sourcesContent":["import { version } from '../package.json';\nimport { AT1000Base } from './at1000';\n\n/** @hideconstructor */\nexport class AT1000 extends AT1000Base {\n static readonly apiVersion = version;\n}\n"],"mappings":"4GAIA,IAAa,EAAb,cAA4BA,EAAAA,CAAW,CACrC,OAAgB,WAAaC,EAAAA,CAC/B"}
1
+ {"version":3,"file":"browser.cjs","names":["AT1000Base","version"],"sources":["../src/browser.ts"],"sourcesContent":["import { version } from '../package.json';\nimport { AT1000Base } from './at1000';\n\nexport { ApiError } from './at1000';\n\n/** @hideconstructor */\nexport class AT1000 extends AT1000Base {\n static readonly apiVersion = version;\n}\n"],"mappings":"gJAMA,IAAa,EAAb,cAA4BA,EAAAA,CAAW,CACrC,OAAgB,WAAaC,EAAAA,CAC/B"}
@@ -1,4 +1,4 @@
1
- import { t as AT1000Base } from "./at1000-CrXOo07c.cjs";
1
+ import { n as ApiError, t as AT1000Base } from "./at1000-D_y0MxFy.cjs";
2
2
 
3
3
  //#region src/browser.d.ts
4
4
  /** @hideconstructor */
@@ -6,5 +6,5 @@ declare class AT1000 extends AT1000Base {
6
6
  static readonly apiVersion: string;
7
7
  }
8
8
  //#endregion
9
- export { AT1000 };
9
+ export { AT1000, ApiError };
10
10
  //# sourceMappingURL=browser.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.cts","names":[],"sources":["../src/browser.ts"],"mappings":";;;;cAIa,MAAA,SAAe,UAAU;EAAA,gBACpB,UAAU;AAAA"}
1
+ {"version":3,"file":"browser.d.cts","names":[],"sources":["../src/browser.ts"],"mappings":";;;;cAMa,MAAA,SAAe,UAAU;EAAA,gBACpB,UAAU;AAAA"}
@@ -1,4 +1,4 @@
1
- import { t as AT1000Base } from "./at1000-RNA4yNKP.mjs";
1
+ import { n as ApiError, t as AT1000Base } from "./at1000-3evjqv7V.mjs";
2
2
 
3
3
  //#region src/browser.d.ts
4
4
  /** @hideconstructor */
@@ -6,5 +6,5 @@ declare class AT1000 extends AT1000Base {
6
6
  static readonly apiVersion: string;
7
7
  }
8
8
  //#endregion
9
- export { AT1000 };
9
+ export { AT1000, ApiError };
10
10
  //# sourceMappingURL=browser.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.mts","names":[],"sources":["../src/browser.ts"],"mappings":";;;;cAIa,MAAA,SAAe,UAAU;EAAA,gBACpB,UAAU;AAAA"}
1
+ {"version":3,"file":"browser.d.mts","names":[],"sources":["../src/browser.ts"],"mappings":";;;;cAMa,MAAA,SAAe,UAAU;EAAA,gBACpB,UAAU;AAAA"}
package/build/browser.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{n as e,t}from"./at1000-Dg_S21jO.mjs";var n=class extends t{static apiVersion=e};export{n as AT1000};
1
+ import{t as e}from"./package-BGnFmux8.mjs";import{n as t,t as n}from"./at1000-Cxx254i3.mjs";var r=class extends n{static apiVersion=e};export{r as AT1000,t as ApiError};
2
2
  //# sourceMappingURL=browser.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.mjs","names":[],"sources":["../src/browser.ts"],"sourcesContent":["import { version } from '../package.json';\nimport { AT1000Base } from './at1000';\n\n/** @hideconstructor */\nexport class AT1000 extends AT1000Base {\n static readonly apiVersion = version;\n}\n"],"mappings":"4CAIA,IAAa,EAAb,cAA4B,CAAW,CACrC,OAAgB,WAAa,CAC/B"}
1
+ {"version":3,"file":"browser.mjs","names":[],"sources":["../src/browser.ts"],"sourcesContent":["import { version } from '../package.json';\nimport { AT1000Base } from './at1000';\n\nexport { ApiError } from './at1000';\n\n/** @hideconstructor */\nexport class AT1000 extends AT1000Base {\n static readonly apiVersion = version;\n}\n"],"mappings":"4FAMA,IAAa,EAAb,cAA4B,CAAW,CACrC,OAAgB,WAAa,CAC/B"}