@ikalogic/at1000 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/{at1000-BGZGhgco.mjs → at1000-Bav5xUM1.mjs} +3 -3
- package/build/{at1000-BGZGhgco.mjs.map → at1000-Bav5xUM1.mjs.map} +1 -1
- package/build/{at1000-D9r6GCs0.cjs → at1000-DjoKZEha.cjs} +3 -3
- package/build/at1000-DjoKZEha.cjs.map +1 -0
- package/build/browser.cjs +1 -1
- package/build/browser.d.cts +3 -3
- package/build/browser.d.cts.map +1 -1
- package/build/browser.d.mts +3 -3
- package/build/browser.d.mts.map +1 -1
- package/build/browser.mjs +1 -1
- package/build/{com-XQjoTBdU.d.cts → com-Bkkgrsep.d.cts} +177 -177
- package/build/{com-XQjoTBdU.d.cts.map → com-Bkkgrsep.d.cts.map} +1 -1
- package/build/{com-XQjoTBdU.d.mts → com-Bkkgrsep.d.mts} +177 -177
- package/build/{com-XQjoTBdU.d.mts.map → com-Bkkgrsep.d.mts.map} +1 -1
- package/build/com.cjs +1 -1
- package/build/com.d.cts +1 -1
- package/build/com.d.mts +1 -1
- package/build/{common-BSrNhbq_.cjs → common-BUohXSqz.cjs} +2 -2
- package/build/{common-BSrNhbq_.cjs.map → common-BUohXSqz.cjs.map} +1 -1
- package/build/{events-Cf7iAMZP.d.cts → events-8aTmwMUi.d.cts} +6 -6
- package/build/events-8aTmwMUi.d.cts.map +1 -0
- package/build/{events-Cf7iAMZP.d.mts → events-8aTmwMUi.d.mts} +6 -6
- package/build/events-8aTmwMUi.d.mts.map +1 -0
- package/build/events.cjs +1 -1
- package/build/events.d.cts +1 -1
- package/build/events.d.mts +1 -1
- package/build/events.mjs +1 -1
- package/build/gpio-5-tRkdTW.d.mts +712 -0
- package/build/gpio-5-tRkdTW.d.mts.map +1 -0
- package/build/gpio-D3QEeLKh.d.cts +712 -0
- package/build/gpio-D3QEeLKh.d.cts.map +1 -0
- package/build/gpio.cjs +1 -1
- package/build/gpio.cjs.map +1 -1
- package/build/gpio.d.cts +2 -2
- package/build/gpio.d.mts +2 -2
- package/build/gpio.mjs +1 -1
- package/build/gpio.mjs.map +1 -1
- package/build/{hmi-B-potLdl.d.cts → hmi-BEMiVKDF.d.cts} +2 -2
- package/build/hmi-BEMiVKDF.d.cts.map +1 -0
- package/build/{hmi-B-potLdl.d.mts → hmi-BEMiVKDF.d.mts} +2 -2
- package/build/hmi-BEMiVKDF.d.mts.map +1 -0
- package/build/hmi.cjs +1 -1
- package/build/hmi.d.cts +1 -1
- package/build/hmi.d.mts +1 -1
- package/build/{host-DJ8SAGMC.d.mts → host-BBjSKRqs.d.mts} +6 -6
- package/build/host-BBjSKRqs.d.mts.map +1 -0
- package/build/{host-BCOEcUog.d.cts → host-DlP73amZ.d.cts} +6 -6
- package/build/host-DlP73amZ.d.cts.map +1 -0
- package/build/{management-BGrPAG5k.cjs → management-DqBsC1HB.cjs} +2 -2
- package/build/management-DqBsC1HB.cjs.map +1 -0
- package/build/{management-CuI4cenF.mjs → management-StKcAkgo.mjs} +2 -2
- package/build/management-StKcAkgo.mjs.map +1 -0
- package/build/management.cjs +1 -1
- package/build/management.d.cts +81 -82
- package/build/management.d.cts.map +1 -1
- package/build/management.d.mts +81 -82
- package/build/management.d.mts.map +1 -1
- package/build/management.mjs +1 -1
- package/build/node.cjs +1 -1
- package/build/node.cjs.map +1 -1
- package/build/node.d.cts +3 -3
- package/build/node.d.cts.map +1 -1
- package/build/node.d.mts +3 -3
- package/build/node.d.mts.map +1 -1
- package/build/node.mjs +1 -1
- package/build/node.mjs.map +1 -1
- package/build/package-DNfLRnEC.mjs +2 -0
- package/build/package-DNfLRnEC.mjs.map +1 -0
- package/build/{package-DDLRsahz.cjs → package-iERj9NJ6.cjs} +2 -2
- package/build/package-iERj9NJ6.cjs.map +1 -0
- package/build/{power-B8QFbKdP.d.cts → power-B3oUS0wU.d.cts} +18 -18
- package/build/power-B3oUS0wU.d.cts.map +1 -0
- package/build/{power-BZmXOJyK.d.mts → power-D08VFff6.d.mts} +18 -18
- package/build/power-D08VFff6.d.mts.map +1 -0
- package/build/power.cjs +1 -1
- package/build/power.d.cts +1 -1
- package/build/power.d.mts +1 -1
- package/build/relays-TADiLsPk.d.cts.map +1 -1
- package/build/relays-TADiLsPk.d.mts.map +1 -1
- package/build/relays.cjs +1 -1
- package/build/{rolldown-runtime-CMqjfN_6.cjs → rolldown-runtime-BocRIvOZ.cjs} +1 -1
- package/build/session-qWW9mqWH.d.cts.map +1 -1
- package/build/session-qWW9mqWH.d.mts.map +1 -1
- package/build/session.cjs +1 -1
- package/build/session.mjs +1 -1
- package/package.json +12 -22
- package/build/at1000-D9r6GCs0.cjs.map +0 -1
- package/build/events-Cf7iAMZP.d.cts.map +0 -1
- package/build/events-Cf7iAMZP.d.mts.map +0 -1
- package/build/gpio-Bk1VEKYF.d.cts +0 -416
- package/build/gpio-Bk1VEKYF.d.cts.map +0 -1
- package/build/gpio-bFe112uI.d.mts +0 -416
- package/build/gpio-bFe112uI.d.mts.map +0 -1
- package/build/hmi-B-potLdl.d.cts.map +0 -1
- package/build/hmi-B-potLdl.d.mts.map +0 -1
- package/build/host-BCOEcUog.d.cts.map +0 -1
- package/build/host-DJ8SAGMC.d.mts.map +0 -1
- package/build/management-BGrPAG5k.cjs.map +0 -1
- package/build/management-CuI4cenF.mjs.map +0 -1
- package/build/package-CXtuh0C5.mjs +0 -2
- package/build/package-CXtuh0C5.mjs.map +0 -1
- package/build/package-DDLRsahz.cjs.map +0 -1
- package/build/power-B8QFbKdP.d.cts.map +0 -1
- package/build/power-BZmXOJyK.d.mts.map +0 -1
|
@@ -1,3 +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{i as c,l,n as u,r as d,s as f,u as p}from"./management-
|
|
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 Y(e){if(typeof e!=`object`||!e)return!1;let t=e;return[`code`,`message`,`suggestion`,`path`].every(e=>typeof t[e]==`string`)}async function X(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 Y(n)?new J(n):Error(`API request to ${t} ${e.url} failed with status ${e.status}: ${typeof n==`string`?n:JSON.stringify(n)}`)}var Z=class t{host;info;token;sessionSupported;fetch;com;hmi;gpio;management;power;relays;events;session;constructor(t,n,r,i=!0){this.host=t,this.info=n,this.token=r,this.sessionSupported=i,this.fetch=F(fetch,()=>({baseUrl:`http://${p(this.host)}/v0`,timeout:1e3,headers:this.token?{"X-AT1000-Session":this.token}:void 0,parseRejected:(e,t)=>X(e,t.method)})),this.com=new e(this.fetch),this.hmi=new C(this.fetch),this.gpio=new m(this.fetch,this.info),this.management=new u(this.fetch,this.host),this.power=new k(this.fetch,this.info),this.relays=new M(this.fetch),this.events=new q(this.host),this.session=new B(this.fetch)}async reset(){await this.com.reset(),await this.hmi.reset(),await this.gpio.reset(),await this.power.reset(),await this.relays.reset()}static async openBase(e,n,r){if(!e?.trim())throw Error(`No device address provided. Pass a host from findDevices() or findLocalDevice().`);let i=`http://${p(e)}/v0`,a=
|
|
3
|
-
//# sourceMappingURL=at1000-
|
|
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{i as c,l,n as u,r as d,s as f,u as p}from"./management-StKcAkgo.mjs";import{GpioApi as m,analogInputConfiguration as h,analogOutputConfiguration as g,analogState as _,digitalInputConfiguration as v,digitalOutputConfiguration as y,digitalState as b,openDrainConfiguration as x,syncState as S}from"./gpio.mjs";import{HmiApi as C,color as w,internalKnobEvent as T,playbackConfiguration as E,progress as D,text as O}from"./hmi.mjs";import{PowerApi as k,dutState as A,usbState as j}from"./power.mjs";import{RelaysApi as M,relayState as N,states as P}from"./relays.mjs";import{up as F}from"up-fetch";import*as I from"valibot";const L=I.object({active:I.boolean(),kind:I.optional(I.picklist([`remote`,`project`])),label:I.optional(I.string()),project:I.optional(I.string()),since:I.optional(I.number())}),R=I.object({token:I.string()});async function z(e,t){let n=await fetch(`${e}/session/open`,{method:`POST`,body:JSON.stringify(t),headers:{"content-type":`application/json`},signal:AbortSignal.timeout(3e4)});if(n.status!==404&&n.status!==405)return n.ok||await X(n,`POST`),I.parse(R,await n.json()).token}var B=class{fetch;constructor(e){this.fetch=e}state(){return this.fetch(`/session/state`,{schema:L})}};const V=I.array(I.pipe(I.number(),I.integer(),I.minValue(0),I.maxValue(255))),H={"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":I.array(n),"com.uart_rx":V,"com.rs232_rx":V,"com.rs485_rx":V,"gpio.state":I.union([b,_]),"gpio.config":I.union([h,g,v,y,x]),"gpio.sync":S,"hmi.text":O,"hmi.colors":w,"hmi.progress":I.optional(D),"hmi.play":E,"hmi.knob":T,"management.datetime":I.object({epoch_seconds:I.number()}),"management.calibration":d,"management.calibration_state":c,"management.project":f,"management.update_status":l,"power.dut":A,"power.usb":j,"relays.state":N,"relays.states":P,"session.state":L},U=[`com.reset`,`gpio.reset`,`hmi.clear`,`hmi.reset`,`power.reset`,`relays.reset`],W=I.object({type:I.string(),id:I.optional(I.pipe(I.number(),I.integer())),ts:I.number(),data:I.optional(I.unknown())}),G=5e3,K=(...e)=>console.debug(`[at1000:events]`,...e);var q=class{host;socket;listeners=new Map;stateListeners=new Set;shouldReconnect=!1;reconnectDelayMs=500;reconnectTimer;_connected=!1;connectPromise;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)`);if(!this._connected){this.connectPromise??=this.openSocket(e?.timeout??G);try{await this.connectPromise}finally{this.connectPromise=void 0}}}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://${p(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),K(`open`,i),n())},a.onmessage=e=>{this.socket===a&&this.handleMessage(e.data)},a.onerror=()=>{K(`error`,i),this.socket===a&&!o&&(o=!0,clearTimeout(s),r(Error(`Failed to connect to ${i}`)))},a.onclose=()=>{if(this.socket===a){if(K(`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;K(`reconnecting in`,e,`ms`),this.reconnectDelayMs=Math.min(this.reconnectDelayMs*2,8e3),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0,this.shouldReconnect&&this.openSocket(G).catch(()=>{})},e)}handleMessage(e){if(typeof e!=`string`){K(`drop: non-string frame`);return}let t;try{t=JSON.parse(e)}catch{K(`drop: invalid JSON`,e);return}K(`recv`,t);let n=I.safeParse(W,t);if(!n.success){K(`drop: malformed envelope`,n.issues);return}let{type:r,id:i,ts:a}=n.output;if(r in H){let e=H[r],t=I.safeParse(e,n.output.data);if(!t.success){K(`drop: payload validation failed for`,r,t.issues);return}let o={type:r,id:i,ts:a,data:t.output};K(`dispatch`,r),this.emitTo(r,o),this.emitTo(`*`,o);return}if(U.includes(r)){let e={type:r,id:i,ts:a};K(`dispatch (dataless)`,r),this.emitTo(r,e),this.emitTo(`*`,e);return}K(`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{}}}},J=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 Y(e){if(typeof e!=`object`||!e)return!1;let t=e;return[`code`,`message`,`suggestion`,`path`].every(e=>typeof t[e]==`string`)}async function X(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 Y(n)?new J(n):Error(`API request to ${t} ${e.url} failed with status ${e.status}: ${typeof n==`string`?n:JSON.stringify(n)}`)}var Z=class t{host;info;token;sessionSupported;fetch;com;hmi;gpio;management;power;relays;events;session;constructor(t,n,r,i=!0){this.host=t,this.info=n,this.token=r,this.sessionSupported=i,this.fetch=F(fetch,()=>({baseUrl:`http://${p(this.host)}/v0`,timeout:1e3,headers:this.token?{"X-AT1000-Session":this.token}:void 0,parseRejected:(e,t)=>X(e,t.method)})),this.com=new e(this.fetch),this.hmi=new C(this.fetch),this.gpio=new m(this.fetch,this.info),this.management=new u(this.fetch,this.host),this.power=new k(this.fetch,this.info),this.relays=new M(this.fetch),this.events=new q(this.host),this.session=new B(this.fetch)}async reset(){await this.com.reset(),await this.hmi.reset(),await this.gpio.reset(),await this.power.reset(),await this.relays.reset()}static async openBase(e,n,r){if(!e?.trim())throw Error(`No device address provided. Pass a host from findDevices() or findLocalDevice().`);let i=`http://${p(e)}/v0`,a=F(fetch,()=>({baseUrl:i,timeout:1e3,parseRejected:(e,t)=>X(e,t.method)})),o=await new u(a,e).info();if(n?.readonly)return new t(e,o);let s=await z(i,{kind:r?.kind??`remote`,label:n?.label??r?.defaultLabel,project:r?.project});return s===void 0?new t(e,o,void 0,!1):new t(e,o,s)}};export{B as a,q as i,J as n,z as o,X as r,L as s,Z as t};
|
|
3
|
+
//# sourceMappingURL=at1000-Bav5xUM1.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"at1000-BGZGhgco.mjs","names":[],"sources":["../src/api/session.ts","../src/api/events.ts","../src/at1000.ts"],"sourcesContent":["import type { UpFetch } from 'up-fetch';\nimport * as v from 'valibot';\n\nimport { parseApiError } from '../at1000';\n\nexport const sessionState = v.object({\n active: v.boolean(),\n kind: v.optional(v.picklist(['remote', 'project'])),\n label: v.optional(v.string()),\n project: v.optional(v.string()),\n since: v.optional(v.number()),\n});\nexport type SessionState = v.InferOutput<typeof sessionState>;\n\n/** Body for `POST /v0/session/open`. */\nexport type SessionOpenBody = { kind: 'remote' | 'project'; label?: string; project?: string };\n\nconst openResponse = v.object({ token: v.string() });\n\n/**\n * Opens an exclusive session on the device (last-open-wins). Returns the minted\n * session token, or `undefined` when the server predates the session model.\n *\n * Deliberately uses a direct `fetch` (not the shared up-fetch client) so the\n * response status is inspectable before any structured-error handler throws:\n * pre-session servers answer the unmatched route with 404/405, which we degrade\n * to `undefined` rather than surface as a failure. A 30 s timeout (not the SDK\n * default 1 s) covers a remote-kind open synchronously stopping any running\n * project container; the host is already known-alive from the preceding info\n * probe, so the long timeout never hangs on dead hosts.\n */\nexport async function open_session(\n baseUrl: string,\n body: SessionOpenBody,\n): Promise<string | undefined> {\n const response = await fetch(`${baseUrl}/session/open`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json' },\n signal: AbortSignal.timeout(30_000),\n });\n\n if (response.status === 404 || response.status === 405) {\n return undefined; // legacy server without the session model\n }\n if (!response.ok) {\n await parseApiError(response, 'POST');\n }\n\n return v.parse(openResponse, await response.json()).token;\n}\n\n/** @hideconstructor */\nexport class SessionApi {\n constructor(private readonly fetch: UpFetch) {}\n\n /**\n * Reads the current device session state.\n * @returns The active holder (kind/label/project/since) or `{ active: false }`.\n */\n state(): Promise<SessionState> {\n return this.fetch('/session/state', { schema: sessionState });\n }\n}\n","import * as v from 'valibot';\n\nimport { format_host_for_url } from '../host';\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 syncState,\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';\nimport { sessionState } from './session';\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': syncState,\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 'session.state': sessionState,\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 private connectPromise: Promise<void> | undefined;\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 // Coalesce concurrent opens: the layout holder-banner effect and the\n // projects page both call connect() on the shared per-client socket. Without\n // this a second caller starts a second openSocket() that orphans the first\n // (leaked WS + a connect() that never settles). Reuse the in-flight open.\n this.connectPromise ??= this.openSocket(options?.timeout ?? defaultConnectTimeoutMs);\n try {\n await this.connectPromise;\n } finally {\n this.connectPromise = undefined;\n }\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 { type UpFetch, up } 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';\nimport { SessionApi, open_session } from './api/session';\nimport { format_host_for_url } from './host';\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/** Type guard for the AT1000 structured error body. */\nfunction isApiErrorBody(\n value: unknown,\n): value is { code: string; message: string; suggestion: string; path: string } {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const record = value as Record<string, unknown>;\n return ['code', 'message', 'suggestion', 'path'].every((key) => typeof record[key] === 'string');\n}\n\n/**\n * Shared rejection handler for every AT1000 up-fetch client. Parses a structured\n * `{ code, message, suggestion, path }` body into an {@link ApiError}, otherwise\n * throws a generic error carrying the HTTP status. Written once so the per-client\n * factory and `open_session` share one code path.\n */\nexport async function parseApiError(response: Response, method: string): Promise<never> {\n let errorData: unknown;\n const contentType = response.headers.get('content-type');\n try {\n errorData = contentType?.includes('application/json')\n ? await response.json()\n : await response.text();\n } catch {\n errorData = 'Failed to parse response body';\n }\n\n if (isApiErrorBody(errorData)) {\n throw new ApiError(errorData);\n }\n\n throw new Error(\n `API request to ${method} ${response.url} failed with status ${response.status}: ${\n typeof errorData === 'string' ? errorData : JSON.stringify(errorData)\n }`,\n );\n}\n\n/** Options for opening a device. */\nexport interface OpenOptions {\n /** Open read-only: skip `session/open`, mint no token, send no session header. */\n readonly?: boolean;\n /** Human-readable holder label shown to other clients and on the front panel. */\n label?: string;\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 * Session token minted by `open()`, or `undefined` for a read-only or legacy\n * (pre-session) client. Sent as the `X-AT1000-Session` header on every request.\n */\n public readonly token: string | undefined;\n /**\n * `false` only when a pre-session server was detected during an exclusive\n * `open()` (no `/v0/session/*` routes). Read-only opens never probe and report `true`.\n */\n public readonly sessionSupported: boolean;\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 * Session API. Read the current holder via `session.state()`.\n */\n public readonly session: SessionApi;\n\n constructor(host: string, info: Information, token?: string, sessionSupported = true) {\n this.host = host;\n this.info = info;\n this.token = token;\n this.sessionSupported = sessionSupported;\n\n this.fetch = up(\n fetch,\n () => ({\n baseUrl: `http://${format_host_for_url(this.host)}/v0`,\n timeout: 1000,\n headers: this.token\n ? { 'X-AT1000-Session': this.token }\n : undefined,\n parseRejected: (response, request) => parseApiError(response, request.method),\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, this.host);\n this.power = new PowerApi(this.fetch, this.info);\n this.relays = new RelaysApi(this.fetch);\n this.events = new EventsApi(this.host);\n this.session = new SessionApi(this.fetch);\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 protected static async openBase(\n host: string,\n options?: OpenOptions,\n session?: { kind: 'remote' | 'project'; project?: string; defaultLabel?: string },\n ): Promise<AT1000Base> {\n if (!host?.trim()) {\n throw new Error(\n 'No device address provided. Pass a host from findDevices() or findLocalDevice().',\n );\n }\n const base = `http://${format_host_for_url(host)}/v0`;\n\n // Re-fetch info to validate reachability (bare client, no session header yet).\n const bare = up(\n fetch,\n () => ({\n baseUrl: base,\n timeout: 1000,\n parseRejected: (response, request) =>\n parseApiError(response, request.method),\n }),\n );\n const info = await new ManagementApi(bare, host).info();\n\n if (options?.readonly) {\n return new AT1000Base(host, info);\n }\n\n const token = await open_session(base, {\n kind: session?.kind ?? 'remote',\n label: options?.label ?? session?.defaultLabel,\n project: session?.project,\n });\n // `undefined` => pre-session server: keep a working, tokenless client.\n return token === undefined\n ? new AT1000Base(host, info, undefined, false)\n : new AT1000Base(host, info, token);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"at1000-Bav5xUM1.mjs","names":[],"sources":["../src/api/session.ts","../src/api/events.ts","../src/at1000.ts"],"sourcesContent":["import type { UpFetch } from 'up-fetch';\nimport * as v from 'valibot';\n\nimport { parseApiError } from '../at1000';\n\nexport const sessionState = v.object({\n active: v.boolean(),\n kind: v.optional(v.picklist(['remote', 'project'])),\n label: v.optional(v.string()),\n project: v.optional(v.string()),\n since: v.optional(v.number()),\n});\nexport type SessionState = v.InferOutput<typeof sessionState>;\n\n/** Body for `POST /v0/session/open`. */\nexport type SessionOpenBody = { kind: 'remote' | 'project'; label?: string; project?: string };\n\nconst openResponse = v.object({ token: v.string() });\n\n/**\n * Opens an exclusive session on the device (last-open-wins). Returns the minted\n * session token, or `undefined` when the server predates the session model.\n *\n * Deliberately uses a direct `fetch` (not the shared up-fetch client) so the\n * response status is inspectable before any structured-error handler throws:\n * pre-session servers answer the unmatched route with 404/405, which we degrade\n * to `undefined` rather than surface as a failure. A 30 s timeout (not the SDK\n * default 1 s) covers a remote-kind open synchronously stopping any running\n * project container; the host is already known-alive from the preceding info\n * probe, so the long timeout never hangs on dead hosts.\n */\nexport async function open_session(\n baseUrl: string,\n body: SessionOpenBody,\n): Promise<string | undefined> {\n const response = await fetch(`${baseUrl}/session/open`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json' },\n signal: AbortSignal.timeout(30_000),\n });\n\n if (response.status === 404 || response.status === 405) {\n return undefined; // legacy server without the session model\n }\n if (!response.ok) {\n await parseApiError(response, 'POST');\n }\n\n return v.parse(openResponse, await response.json()).token;\n}\n\n/** @hideconstructor */\nexport class SessionApi {\n constructor(private readonly fetch: UpFetch) {}\n\n /**\n * Reads the current device session state.\n * @returns The active holder (kind/label/project/since) or `{ active: false }`.\n */\n state(): Promise<SessionState> {\n return this.fetch('/session/state', { schema: sessionState });\n }\n}\n","import * as v from 'valibot';\n\nimport { format_host_for_url } from '../host';\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 syncState,\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';\nimport { sessionState } from './session';\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': syncState,\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 'session.state': sessionState,\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 private connectPromise: Promise<void> | undefined;\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 // Coalesce concurrent opens: the layout holder-banner effect and the\n // projects page both call connect() on the shared per-client socket. Without\n // this a second caller starts a second openSocket() that orphans the first\n // (leaked WS + a connect() that never settles). Reuse the in-flight open.\n this.connectPromise ??= this.openSocket(options?.timeout ?? defaultConnectTimeoutMs);\n try {\n await this.connectPromise;\n } finally {\n this.connectPromise = undefined;\n }\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 { type UpFetch, up } 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';\nimport { SessionApi, open_session } from './api/session';\nimport { format_host_for_url } from './host';\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/** Type guard for the AT1000 structured error body. */\nfunction isApiErrorBody(\n value: unknown,\n): value is { code: string; message: string; suggestion: string; path: string } {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const record = value as Record<string, unknown>;\n return ['code', 'message', 'suggestion', 'path'].every((key) => typeof record[key] === 'string');\n}\n\n/**\n * Shared rejection handler for every AT1000 up-fetch client. Parses a structured\n * `{ code, message, suggestion, path }` body into an {@link ApiError}, otherwise\n * throws a generic error carrying the HTTP status. Written once so the per-client\n * factory and `open_session` share one code path.\n */\nexport async function parseApiError(response: Response, method: string): Promise<never> {\n let errorData: unknown;\n const contentType = response.headers.get('content-type');\n try {\n errorData = contentType?.includes('application/json')\n ? await response.json()\n : await response.text();\n } catch {\n errorData = 'Failed to parse response body';\n }\n\n if (isApiErrorBody(errorData)) {\n throw new ApiError(errorData);\n }\n\n throw new Error(\n `API request to ${method} ${response.url} failed with status ${response.status}: ${\n typeof errorData === 'string' ? errorData : JSON.stringify(errorData)\n }`,\n );\n}\n\n/** Options for opening a device. */\nexport interface OpenOptions {\n /** Open read-only: skip `session/open`, mint no token, send no session header. */\n readonly?: boolean;\n /** Human-readable holder label shown to other clients and on the front panel. */\n label?: string;\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 * Session token minted by `open()`, or `undefined` for a read-only or legacy\n * (pre-session) client. Sent as the `X-AT1000-Session` header on every request.\n */\n public readonly token: string | undefined;\n /**\n * `false` only when a pre-session server was detected during an exclusive\n * `open()` (no `/v0/session/*` routes). Read-only opens never probe and report `true`.\n */\n public readonly sessionSupported: boolean;\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 * Session API. Read the current holder via `session.state()`.\n */\n public readonly session: SessionApi;\n\n constructor(host: string, info: Information, token?: string, sessionSupported = true) {\n this.host = host;\n this.info = info;\n this.token = token;\n this.sessionSupported = sessionSupported;\n\n this.fetch = up(\n fetch,\n () => ({\n baseUrl: `http://${format_host_for_url(this.host)}/v0`,\n timeout: 1000,\n headers: this.token\n ? { 'X-AT1000-Session': this.token }\n : undefined,\n parseRejected: (response, request) => parseApiError(response, request.method),\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, this.host);\n this.power = new PowerApi(this.fetch, this.info);\n this.relays = new RelaysApi(this.fetch);\n this.events = new EventsApi(this.host);\n this.session = new SessionApi(this.fetch);\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 protected static async openBase(\n host: string,\n options?: OpenOptions,\n session?: { kind: 'remote' | 'project'; project?: string; defaultLabel?: string },\n ): Promise<AT1000Base> {\n if (!host?.trim()) {\n throw new Error(\n 'No device address provided. Pass a host from findDevices() or findLocalDevice().',\n );\n }\n const base = `http://${format_host_for_url(host)}/v0`;\n\n // Re-fetch info to validate reachability (bare client, no session header yet).\n const bare = up(\n fetch,\n () => ({\n baseUrl: base,\n timeout: 1000,\n parseRejected: (response, request) =>\n parseApiError(response, request.method),\n }),\n );\n const info = await new ManagementApi(bare, host).info();\n\n if (options?.readonly) {\n return new AT1000Base(host, info);\n }\n\n const token = await open_session(base, {\n kind: session?.kind ?? 'remote',\n label: options?.label ?? session?.defaultLabel,\n project: session?.project,\n });\n // `undefined` => pre-session server: keep a working, tokenless client.\n return token === undefined\n ? new AT1000Base(host, info, undefined, false)\n : new AT1000Base(host, info, token);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const e=require("./rolldown-runtime-
|
|
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 x(e){if(typeof e!=`object`||!e)return!1;let t=e;return[`code`,`message`,`suggestion`,`path`].every(e=>typeof t[e]==`string`)}async function S(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 x(n)?new b(n):Error(`API request to ${t} ${e.url} failed with status ${e.status}: ${typeof n==`string`?n:JSON.stringify(n)}`)}var C=class e{host;info;token;sessionSupported;fetch;com;hmi;gpio;management;power;relays;events;session;constructor(e,c,l,u=!0){this.host=e,this.info=c,this.token=l,this.sessionSupported=u,this.fetch=(0,s.up)(fetch,()=>({baseUrl:`http://${n.u(this.host)}/v0`,timeout:1e3,headers:this.token?{"X-AT1000-Session":this.token}:void 0,parseRejected:(e,t)=>S(e,t.method)})),this.com=new t.ComApi(this.fetch),this.hmi=new i.HmiApi(this.fetch),this.gpio=new r.GpioApi(this.fetch,this.info),this.management=new n.n(this.fetch,this.host),this.power=new a.PowerApi(this.fetch,this.info),this.relays=new o.RelaysApi(this.fetch),this.events=new y(this.host),this.session=new f(this.fetch)}async reset(){await this.com.reset(),await this.hmi.reset(),await this.gpio.reset(),await this.power.reset(),await this.relays.reset()}static async openBase(t,r,i){if(!t?.trim())throw Error(`No device address provided. Pass a host from findDevices() or findLocalDevice().`);let a=`http://${n.u(t)}/v0`,o=
|
|
3
|
-
//# sourceMappingURL=at1000-
|
|
1
|
+
const e=require("./rolldown-runtime-BocRIvOZ.cjs"),t=require("./com.cjs"),n=require("./management-DqBsC1HB.cjs"),r=require("./gpio.cjs"),i=require("./hmi.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.object({active:c.boolean(),kind:c.optional(c.picklist([`remote`,`project`])),label:c.optional(c.string()),project:c.optional(c.string()),since:c.optional(c.number())}),u=c.object({token:c.string()});async function d(e,t){let n=await fetch(`${e}/session/open`,{method:`POST`,body:JSON.stringify(t),headers:{"content-type":`application/json`},signal:AbortSignal.timeout(3e4)});if(n.status!==404&&n.status!==405)return n.ok||await S(n,`POST`),c.parse(u,await n.json()).token}var f=class{fetch;constructor(e){this.fetch=e}state(){return this.fetch(`/session/state`,{schema:l})}};const p=c.array(c.pipe(c.number(),c.integer(),c.minValue(0),c.maxValue(255))),m={"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":p,"com.rs232_rx":p,"com.rs485_rx":p,"gpio.state":c.union([r.digitalState,r.analogState]),"gpio.config":c.union([r.analogInputConfiguration,r.analogOutputConfiguration,r.digitalInputConfiguration,r.digitalOutputConfiguration,r.openDrainConfiguration]),"gpio.sync":r.syncState,"hmi.text":i.text,"hmi.colors":i.color,"hmi.progress":c.optional(i.progress),"hmi.play":i.playbackConfiguration,"hmi.knob":i.internalKnobEvent,"management.datetime":c.object({epoch_seconds:c.number()}),"management.calibration":n.r,"management.calibration_state":n.i,"management.project":n.s,"management.update_status":n.l,"power.dut":a.dutState,"power.usb":a.usbState,"relays.state":o.relayState,"relays.states":o.states,"session.state":l},h=[`com.reset`,`gpio.reset`,`hmi.clear`,`hmi.reset`,`power.reset`,`relays.reset`],g=c.object({type:c.string(),id:c.optional(c.pipe(c.number(),c.integer())),ts:c.number(),data:c.optional(c.unknown())}),_=5e3,v=(...e)=>console.debug(`[at1000:events]`,...e);var y=class{host;socket;listeners=new Map;stateListeners=new Set;shouldReconnect=!1;reconnectDelayMs=500;reconnectTimer;_connected=!1;connectPromise;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)`);if(!this._connected){this.connectPromise??=this.openSocket(e?.timeout??_);try{await this.connectPromise}finally{this.connectPromise=void 0}}}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:r,reject:i}=Promise.withResolvers(),a=`ws://${n.u(this.host)}/v0/events`,o=new WebSocket(a);this.socket=o;let s=!1,c=setTimeout(()=>{s||(s=!0,o.close(),i(Error(`Timed out connecting to ${a}`)))},e);return o.onopen=()=>{this.socket!==o||s||(s=!0,clearTimeout(c),this.shouldReconnect=!0,this.reconnectDelayMs=500,this.setConnected(!0),v(`open`,a),r())},o.onmessage=e=>{this.socket===o&&this.handleMessage(e.data)},o.onerror=()=>{v(`error`,a),this.socket===o&&!s&&(s=!0,clearTimeout(c),i(Error(`Failed to connect to ${a}`)))},o.onclose=()=>{if(this.socket===o){if(v(`close`,a),clearTimeout(c),this.setConnected(!1),!s){s=!0,i(Error(`Connection to ${a} closed before opening`));return}this.shouldReconnect&&this.scheduleReconnect()}},t}scheduleReconnect(){if(!this.shouldReconnect||this.reconnectTimer!==void 0)return;let e=this.reconnectDelayMs;v(`reconnecting in`,e,`ms`),this.reconnectDelayMs=Math.min(this.reconnectDelayMs*2,8e3),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0,this.shouldReconnect&&this.openSocket(_).catch(()=>{})},e)}handleMessage(e){if(typeof e!=`string`){v(`drop: non-string frame`);return}let t;try{t=JSON.parse(e)}catch{v(`drop: invalid JSON`,e);return}v(`recv`,t);let n=c.safeParse(g,t);if(!n.success){v(`drop: malformed envelope`,n.issues);return}let{type:r,id:i,ts:a}=n.output;if(r in m){let e=m[r],t=c.safeParse(e,n.output.data);if(!t.success){v(`drop: payload validation failed for`,r,t.issues);return}let o={type:r,id:i,ts:a,data:t.output};v(`dispatch`,r),this.emitTo(r,o),this.emitTo(`*`,o);return}if(h.includes(r)){let e={type:r,id:i,ts:a};v(`dispatch (dataless)`,r),this.emitTo(r,e),this.emitTo(`*`,e);return}v(`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{}}}},b=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 x(e){if(typeof e!=`object`||!e)return!1;let t=e;return[`code`,`message`,`suggestion`,`path`].every(e=>typeof t[e]==`string`)}async function S(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 x(n)?new b(n):Error(`API request to ${t} ${e.url} failed with status ${e.status}: ${typeof n==`string`?n:JSON.stringify(n)}`)}var C=class e{host;info;token;sessionSupported;fetch;com;hmi;gpio;management;power;relays;events;session;constructor(e,c,l,u=!0){this.host=e,this.info=c,this.token=l,this.sessionSupported=u,this.fetch=(0,s.up)(fetch,()=>({baseUrl:`http://${n.u(this.host)}/v0`,timeout:1e3,headers:this.token?{"X-AT1000-Session":this.token}:void 0,parseRejected:(e,t)=>S(e,t.method)})),this.com=new t.ComApi(this.fetch),this.hmi=new i.HmiApi(this.fetch),this.gpio=new r.GpioApi(this.fetch,this.info),this.management=new n.n(this.fetch,this.host),this.power=new a.PowerApi(this.fetch,this.info),this.relays=new o.RelaysApi(this.fetch),this.events=new y(this.host),this.session=new f(this.fetch)}async reset(){await this.com.reset(),await this.hmi.reset(),await this.gpio.reset(),await this.power.reset(),await this.relays.reset()}static async openBase(t,r,i){if(!t?.trim())throw Error(`No device address provided. Pass a host from findDevices() or findLocalDevice().`);let a=`http://${n.u(t)}/v0`,o=(0,s.up)(fetch,()=>({baseUrl:a,timeout:1e3,parseRejected:(e,t)=>S(e,t.method)})),c=await new n.n(o,t).info();if(r?.readonly)return new e(t,c);let l=await d(a,{kind:i?.kind??`remote`,label:r?.label??i?.defaultLabel,project:i?.project});return l===void 0?new e(t,c,void 0,!1):new e(t,c,l)}};Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return C}});
|
|
3
|
+
//# sourceMappingURL=at1000-DjoKZEha.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"at1000-DjoKZEha.cjs","names":["v","v","canConfig","rs232Config","rs485Config","spiConfig","i2cConfig","uartConfig","canFrame","digitalState","analogState","analogInputConfiguration","analogOutputConfiguration","digitalInputConfiguration","digitalOutputConfiguration","openDrainConfiguration","syncState","text","color","progress","playbackConfiguration","internalKnobEvent","calibrationData","calibrationState","projectEvent","updateStatus","dutState","usbState","relayState","states","format_host_for_url","up","format_host_for_url","ComApi","HmiApi","GpioApi","ManagementApi","PowerApi","RelaysApi"],"sources":["../src/api/session.ts","../src/api/events.ts","../src/at1000.ts"],"sourcesContent":["import type { UpFetch } from 'up-fetch';\nimport * as v from 'valibot';\n\nimport { parseApiError } from '../at1000';\n\nexport const sessionState = v.object({\n active: v.boolean(),\n kind: v.optional(v.picklist(['remote', 'project'])),\n label: v.optional(v.string()),\n project: v.optional(v.string()),\n since: v.optional(v.number()),\n});\nexport type SessionState = v.InferOutput<typeof sessionState>;\n\n/** Body for `POST /v0/session/open`. */\nexport type SessionOpenBody = { kind: 'remote' | 'project'; label?: string; project?: string };\n\nconst openResponse = v.object({ token: v.string() });\n\n/**\n * Opens an exclusive session on the device (last-open-wins). Returns the minted\n * session token, or `undefined` when the server predates the session model.\n *\n * Deliberately uses a direct `fetch` (not the shared up-fetch client) so the\n * response status is inspectable before any structured-error handler throws:\n * pre-session servers answer the unmatched route with 404/405, which we degrade\n * to `undefined` rather than surface as a failure. A 30 s timeout (not the SDK\n * default 1 s) covers a remote-kind open synchronously stopping any running\n * project container; the host is already known-alive from the preceding info\n * probe, so the long timeout never hangs on dead hosts.\n */\nexport async function open_session(\n baseUrl: string,\n body: SessionOpenBody,\n): Promise<string | undefined> {\n const response = await fetch(`${baseUrl}/session/open`, {\n method: 'POST',\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json' },\n signal: AbortSignal.timeout(30_000),\n });\n\n if (response.status === 404 || response.status === 405) {\n return undefined; // legacy server without the session model\n }\n if (!response.ok) {\n await parseApiError(response, 'POST');\n }\n\n return v.parse(openResponse, await response.json()).token;\n}\n\n/** @hideconstructor */\nexport class SessionApi {\n constructor(private readonly fetch: UpFetch) {}\n\n /**\n * Reads the current device session state.\n * @returns The active holder (kind/label/project/since) or `{ active: false }`.\n */\n state(): Promise<SessionState> {\n return this.fetch('/session/state', { schema: sessionState });\n }\n}\n","import * as v from 'valibot';\n\nimport { format_host_for_url } from '../host';\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 syncState,\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';\nimport { sessionState } from './session';\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': syncState,\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 'session.state': sessionState,\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 private connectPromise: Promise<void> | undefined;\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 // Coalesce concurrent opens: the layout holder-banner effect and the\n // projects page both call connect() on the shared per-client socket. Without\n // this a second caller starts a second openSocket() that orphans the first\n // (leaked WS + a connect() that never settles). Reuse the in-flight open.\n this.connectPromise ??= this.openSocket(options?.timeout ?? defaultConnectTimeoutMs);\n try {\n await this.connectPromise;\n } finally {\n this.connectPromise = undefined;\n }\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 { type UpFetch, up } 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';\nimport { SessionApi, open_session } from './api/session';\nimport { format_host_for_url } from './host';\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/** Type guard for the AT1000 structured error body. */\nfunction isApiErrorBody(\n value: unknown,\n): value is { code: string; message: string; suggestion: string; path: string } {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const record = value as Record<string, unknown>;\n return ['code', 'message', 'suggestion', 'path'].every((key) => typeof record[key] === 'string');\n}\n\n/**\n * Shared rejection handler for every AT1000 up-fetch client. Parses a structured\n * `{ code, message, suggestion, path }` body into an {@link ApiError}, otherwise\n * throws a generic error carrying the HTTP status. Written once so the per-client\n * factory and `open_session` share one code path.\n */\nexport async function parseApiError(response: Response, method: string): Promise<never> {\n let errorData: unknown;\n const contentType = response.headers.get('content-type');\n try {\n errorData = contentType?.includes('application/json')\n ? await response.json()\n : await response.text();\n } catch {\n errorData = 'Failed to parse response body';\n }\n\n if (isApiErrorBody(errorData)) {\n throw new ApiError(errorData);\n }\n\n throw new Error(\n `API request to ${method} ${response.url} failed with status ${response.status}: ${\n typeof errorData === 'string' ? errorData : JSON.stringify(errorData)\n }`,\n );\n}\n\n/** Options for opening a device. */\nexport interface OpenOptions {\n /** Open read-only: skip `session/open`, mint no token, send no session header. */\n readonly?: boolean;\n /** Human-readable holder label shown to other clients and on the front panel. */\n label?: string;\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 * Session token minted by `open()`, or `undefined` for a read-only or legacy\n * (pre-session) client. Sent as the `X-AT1000-Session` header on every request.\n */\n public readonly token: string | undefined;\n /**\n * `false` only when a pre-session server was detected during an exclusive\n * `open()` (no `/v0/session/*` routes). Read-only opens never probe and report `true`.\n */\n public readonly sessionSupported: boolean;\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 * Session API. Read the current holder via `session.state()`.\n */\n public readonly session: SessionApi;\n\n constructor(host: string, info: Information, token?: string, sessionSupported = true) {\n this.host = host;\n this.info = info;\n this.token = token;\n this.sessionSupported = sessionSupported;\n\n this.fetch = up(\n fetch,\n () => ({\n baseUrl: `http://${format_host_for_url(this.host)}/v0`,\n timeout: 1000,\n headers: this.token\n ? { 'X-AT1000-Session': this.token }\n : undefined,\n parseRejected: (response, request) => parseApiError(response, request.method),\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, this.host);\n this.power = new PowerApi(this.fetch, this.info);\n this.relays = new RelaysApi(this.fetch);\n this.events = new EventsApi(this.host);\n this.session = new SessionApi(this.fetch);\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 protected static async openBase(\n host: string,\n options?: OpenOptions,\n session?: { kind: 'remote' | 'project'; project?: string; defaultLabel?: string },\n ): Promise<AT1000Base> {\n if (!host?.trim()) {\n throw new Error(\n 'No device address provided. Pass a host from findDevices() or findLocalDevice().',\n );\n }\n const base = `http://${format_host_for_url(host)}/v0`;\n\n // Re-fetch info to validate reachability (bare client, no session header yet).\n const bare = up(\n fetch,\n () => ({\n baseUrl: base,\n timeout: 1000,\n parseRejected: (response, request) =>\n parseApiError(response, request.method),\n }),\n );\n const info = await new ManagementApi(bare, host).info();\n\n if (options?.readonly) {\n return new AT1000Base(host, info);\n }\n\n const token = await open_session(base, {\n kind: session?.kind ?? 'remote',\n label: options?.label ?? session?.defaultLabel,\n project: session?.project,\n });\n // `undefined` => pre-session server: keep a working, tokenless client.\n return token === undefined\n ? new AT1000Base(host, info, undefined, false)\n : new AT1000Base(host, info, token);\n }\n}\n"],"mappings":"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"}
|
package/build/browser.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./package-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./package-iERj9NJ6.cjs"),t=require("./management-DqBsC1HB.cjs"),n=require("./at1000-DjoKZEha.cjs");var r=class extends n.t{static apiVersion=e.t;static isStandalone(){return!1}static async open(e,t){return await n.t.openBase(e,t)}};exports.AT1000=r,exports.ApiError=n.n,exports.format_host_for_url=t.u,exports.host_without_port=t.d;
|
|
2
2
|
//# sourceMappingURL=browser.cjs.map
|
package/build/browser.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ProjectTemplate } from "./management.cjs";
|
|
2
|
-
import { a as OpenOptions, i as ApiError, n as host_without_port, r as AT1000Base, t as format_host_for_url } from "./host-
|
|
2
|
+
import { a as OpenOptions, i as ApiError, n as host_without_port, r as AT1000Base, t as format_host_for_url } from "./host-DlP73amZ.cjs";
|
|
3
3
|
//#region src/browser.d.ts
|
|
4
4
|
/** @hideconstructor */
|
|
5
|
-
declare class AT1000 extends AT1000Base {
|
|
5
|
+
export declare class AT1000 extends AT1000Base {
|
|
6
6
|
static readonly apiVersion: string;
|
|
7
7
|
/** The browser never runs on-device; standalone mode is unavailable. */
|
|
8
8
|
static isStandalone(): boolean;
|
|
@@ -14,5 +14,5 @@ declare class AT1000 extends AT1000Base {
|
|
|
14
14
|
static open(host: string, options?: OpenOptions): Promise<AT1000>;
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
17
|
-
export {
|
|
17
|
+
export { ApiError, type OpenOptions, type ProjectTemplate, format_host_for_url, host_without_port };
|
|
18
18
|
//# sourceMappingURL=browser.d.cts.map
|
package/build/browser.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.cts","names":[],"sources":["../src/browser.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"browser.d.cts","names":[],"sources":["../src/browser.ts"],"mappings":";;;;qBASa,eAAe;kBACV;;SAGT;;;;;;SASM,KAAK,cAAc,UAAU,cAAc,QAAQ"}
|
package/build/browser.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ProjectTemplate } from "./management.mjs";
|
|
2
|
-
import { a as OpenOptions, i as ApiError, n as host_without_port, r as AT1000Base, t as format_host_for_url } from "./host-
|
|
2
|
+
import { a as OpenOptions, i as ApiError, n as host_without_port, r as AT1000Base, t as format_host_for_url } from "./host-BBjSKRqs.mjs";
|
|
3
3
|
//#region src/browser.d.ts
|
|
4
4
|
/** @hideconstructor */
|
|
5
|
-
declare class AT1000 extends AT1000Base {
|
|
5
|
+
export declare class AT1000 extends AT1000Base {
|
|
6
6
|
static readonly apiVersion: string;
|
|
7
7
|
/** The browser never runs on-device; standalone mode is unavailable. */
|
|
8
8
|
static isStandalone(): boolean;
|
|
@@ -14,5 +14,5 @@ declare class AT1000 extends AT1000Base {
|
|
|
14
14
|
static open(host: string, options?: OpenOptions): Promise<AT1000>;
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
17
|
-
export {
|
|
17
|
+
export { ApiError, type OpenOptions, type ProjectTemplate, format_host_for_url, host_without_port };
|
|
18
18
|
//# sourceMappingURL=browser.d.mts.map
|
package/build/browser.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.mts","names":[],"sources":["../src/browser.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"browser.d.mts","names":[],"sources":["../src/browser.ts"],"mappings":";;;;qBASa,eAAe;kBACV;;SAGT;;;;;;SASM,KAAK,cAAc,UAAU,cAAc,QAAQ"}
|
package/build/browser.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"./package-
|
|
1
|
+
import{t as e}from"./package-DNfLRnEC.mjs";import{d as t,u as n}from"./management-StKcAkgo.mjs";import{n as r,t as i}from"./at1000-Bav5xUM1.mjs";var a=class extends i{static apiVersion=e;static isStandalone(){return!1}static async open(e,t){return await i.openBase(e,t)}};export{a as AT1000,r as ApiError,n as format_host_for_url,t as host_without_port};
|
|
2
2
|
//# sourceMappingURL=browser.mjs.map
|