@adcops/autocore-react 3.3.8 → 3.3.10

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 (215) hide show
  1. package/LICENSE +58 -58
  2. package/additional-docs/AutoCoreTagContext.md +441 -441
  3. package/additional-docs/ButtonApiSpecs.md +48 -48
  4. package/additional-docs/GlobalEventEmitter.md +243 -243
  5. package/additional-docs/general_recommendations.md +22 -22
  6. package/additional-docs/react_performance_notes.md +94 -94
  7. package/dist/assets/svg/blockly_logo.svg +82 -82
  8. package/dist/assets/svg/distance.svg +40 -40
  9. package/dist/assets/svg/python_logo.svg +246 -246
  10. package/dist/assets/svg/rotation_ccw.svg +50 -50
  11. package/dist/assets/svg/rotation_ccw_a.svg +57 -57
  12. package/dist/assets/svg/rotation_ccw_b.svg +57 -57
  13. package/dist/assets/svg/rotation_ccw_c.svg +57 -57
  14. package/dist/assets/svg/rotation_cw.svg +49 -49
  15. package/dist/assets/svg/rotation_cw_a.svg +30 -30
  16. package/dist/assets/svg/rotation_cw_b.svg +30 -30
  17. package/dist/assets/svg/rotation_cw_c.svg +30 -30
  18. package/dist/assets/svg/speed.svg +39 -39
  19. package/dist/components/BlocklyEditor.css +93 -93
  20. package/dist/components/JogPanel.css +41 -41
  21. package/dist/components/ProgressBarWithValue.css +27 -27
  22. package/dist/components/ValueIndicator.css +31 -31
  23. package/dist/components/osk.css +123 -123
  24. package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
  25. package/dist/core/AutoCoreTagContext.js +1 -1
  26. package/dist/hub/HubBase.d.ts +3 -3
  27. package/dist/hub/HubBase.d.ts.map +1 -1
  28. package/dist/hub/HubBase.js +1 -1
  29. package/package.json +104 -104
  30. package/readme.md +343 -343
  31. package/src/assets/BlocklyLogo.tsx +27 -27
  32. package/src/assets/Distance.tsx +18 -18
  33. package/src/assets/JogLong.tsx +13 -13
  34. package/src/assets/JogMedium.tsx +13 -13
  35. package/src/assets/JogShort.tsx +13 -13
  36. package/src/assets/PythonLogo.tsx +83 -83
  37. package/src/assets/Rotation3D.tsx +13 -13
  38. package/src/assets/RotationCcw.tsx +33 -33
  39. package/src/assets/RotationCcwA.tsx +45 -45
  40. package/src/assets/RotationCcwB.tsx +45 -45
  41. package/src/assets/RotationCcwC.tsx +45 -45
  42. package/src/assets/RotationCw.tsx +31 -31
  43. package/src/assets/RotationCwA.tsx +42 -42
  44. package/src/assets/RotationCwB.tsx +42 -42
  45. package/src/assets/RotationCwC.tsx +42 -42
  46. package/src/assets/Run.tsx +13 -13
  47. package/src/assets/Speed.tsx +18 -18
  48. package/src/assets/SpeedFast.tsx +13 -13
  49. package/src/assets/SpeedMedium.tsx +13 -13
  50. package/src/assets/SpeedNone.tsx +13 -13
  51. package/src/assets/SpeedSlow.tsx +13 -13
  52. package/src/assets/Walk.tsx +13 -13
  53. package/src/assets/index.ts +22 -22
  54. package/src/assets/svg/blockly_logo.svg +82 -82
  55. package/src/assets/svg/distance.svg +40 -40
  56. package/src/assets/svg/python_logo.svg +246 -246
  57. package/src/assets/svg/rotation_ccw.svg +50 -50
  58. package/src/assets/svg/rotation_ccw_a.svg +57 -57
  59. package/src/assets/svg/rotation_ccw_b.svg +57 -57
  60. package/src/assets/svg/rotation_ccw_c.svg +57 -57
  61. package/src/assets/svg/rotation_cw.svg +49 -49
  62. package/src/assets/svg/rotation_cw_a.svg +30 -30
  63. package/src/assets/svg/rotation_cw_b.svg +30 -30
  64. package/src/assets/svg/rotation_cw_c.svg +30 -30
  65. package/src/assets/svg/speed.svg +39 -39
  66. package/src/components/AutoCoreDevPanel.tsx +414 -414
  67. package/src/components/BlocklyEditor.css +93 -93
  68. package/src/components/BlocklyEditor.tsx +609 -609
  69. package/src/components/CodeEditor.tsx +155 -155
  70. package/src/components/FileList.tsx +390 -390
  71. package/src/components/FileSelect.tsx +128 -128
  72. package/src/components/FitText.tsx +35 -35
  73. package/src/components/Indicator.tsx +188 -188
  74. package/src/components/IndicatorButton.tsx +214 -214
  75. package/src/components/IndicatorRect.tsx +172 -172
  76. package/src/components/JogPanel.css +41 -41
  77. package/src/components/JogPanel.tsx +461 -461
  78. package/src/components/Lamp.tsx +243 -243
  79. package/src/components/Osk.tsx +192 -192
  80. package/src/components/OskDialog.tsx +164 -164
  81. package/src/components/ProgressBarWithValue.css +27 -27
  82. package/src/components/ProgressBarWithValue.tsx +48 -48
  83. package/src/components/TextInput.tsx +195 -195
  84. package/src/components/ToggleGroup.tsx +322 -322
  85. package/src/components/ValueDisplay.tsx +236 -236
  86. package/src/components/ValueIndicator.css +31 -31
  87. package/src/components/ValueIndicator.tsx +135 -135
  88. package/src/components/ValueInput.tsx +368 -368
  89. package/src/components/osk.css +123 -123
  90. package/src/core/ActionMode.ts +19 -19
  91. package/src/core/AutoCoreTagContext.tsx +625 -614
  92. package/src/core/AutoCoreTagTypes.ts +334 -334
  93. package/src/core/CoreStreamTypes.ts +512 -512
  94. package/src/core/EventEmitterContext.tsx +434 -434
  95. package/src/core/IndicatorButtonState.ts +34 -34
  96. package/src/core/IndicatorColor.ts +35 -35
  97. package/src/core/MaskPatterns.ts +87 -87
  98. package/src/core/NumerableTypes.ts +80 -80
  99. package/src/core/PositionContext.ts +59 -59
  100. package/src/core/UniqueId.ts +41 -41
  101. package/src/core/ValueSimulator.ts +166 -166
  102. package/src/core/hoc.tsx +65 -65
  103. package/src/hooks/adsHooks.tsx +287 -287
  104. package/src/hooks/commandHooks.tsx +300 -300
  105. package/src/hooks/index.ts +12 -12
  106. package/src/hooks/useAutoCoreTag.ts +103 -103
  107. package/src/hooks/useScaledValue.tsx +99 -99
  108. package/src/hub/CommandMessage.ts +89 -89
  109. package/src/hub/DebugPanel.ts +307 -307
  110. package/src/hub/HubBase.ts +249 -236
  111. package/src/hub/HubSimulate.ts +124 -124
  112. package/src/hub/HubTauri.ts +140 -140
  113. package/src/hub/HubWebSocket.ts +250 -250
  114. package/src/hub/debug.ts +211 -211
  115. package/src/hub/index.ts +81 -81
  116. package/src/themes/adc-dark/_extensions.scss +166 -166
  117. package/src/themes/adc-dark/_variables.scss +913 -913
  118. package/src/themes/adc-dark/blue/_fonts.scss +23 -23
  119. package/src/themes/adc-dark/blue/adc_theme.scss +31 -31
  120. package/src/themes/adc-dark/blue/theme.scss +14 -14
  121. package/src/themes/theme-base/_colors.scss +17 -17
  122. package/src/themes/theme-base/_common.scss +74 -74
  123. package/src/themes/theme-base/_components.scss +111 -111
  124. package/src/themes/theme-base/_mixins.scss +243 -243
  125. package/src/themes/theme-base/components/button/_button.scss +644 -644
  126. package/src/themes/theme-base/components/button/_speeddial.scss +91 -91
  127. package/src/themes/theme-base/components/button/_splitbutton.scss +358 -358
  128. package/src/themes/theme-base/components/data/_carousel.scss +39 -39
  129. package/src/themes/theme-base/components/data/_datascroller.scss +47 -47
  130. package/src/themes/theme-base/components/data/_datatable.scss +388 -388
  131. package/src/themes/theme-base/components/data/_dataview.scss +47 -47
  132. package/src/themes/theme-base/components/data/_filter.scss +137 -137
  133. package/src/themes/theme-base/components/data/_orderlist.scss +86 -86
  134. package/src/themes/theme-base/components/data/_organizationchart.scss +50 -50
  135. package/src/themes/theme-base/components/data/_paginator.scss +91 -91
  136. package/src/themes/theme-base/components/data/_picklist.scss +73 -73
  137. package/src/themes/theme-base/components/data/_timeline.scss +38 -38
  138. package/src/themes/theme-base/components/data/_tree.scss +184 -184
  139. package/src/themes/theme-base/components/data/_treetable.scss +431 -431
  140. package/src/themes/theme-base/components/file/_fileupload.scss +41 -41
  141. package/src/themes/theme-base/components/input/_autocomplete.scss +94 -94
  142. package/src/themes/theme-base/components/input/_calendar.scss +251 -251
  143. package/src/themes/theme-base/components/input/_cascadeselect.scss +107 -107
  144. package/src/themes/theme-base/components/input/_checkbox.scss +181 -181
  145. package/src/themes/theme-base/components/input/_chips.scss +102 -102
  146. package/src/themes/theme-base/components/input/_colorpicker.scss +17 -17
  147. package/src/themes/theme-base/components/input/_dropdown.scss +252 -252
  148. package/src/themes/theme-base/components/input/_editor.scss +122 -122
  149. package/src/themes/theme-base/components/input/_iconfield.scss +9 -9
  150. package/src/themes/theme-base/components/input/_inputgroup.scss +74 -74
  151. package/src/themes/theme-base/components/input/_inputicon.scss +14 -14
  152. package/src/themes/theme-base/components/input/_inputnumber.scss +4 -4
  153. package/src/themes/theme-base/components/input/_inputotp.scss +10 -10
  154. package/src/themes/theme-base/components/input/_inputswitch.scss +99 -99
  155. package/src/themes/theme-base/components/input/_inputtext.scss +101 -101
  156. package/src/themes/theme-base/components/input/_listbox.scss +138 -138
  157. package/src/themes/theme-base/components/input/_mention.scss +30 -30
  158. package/src/themes/theme-base/components/input/_multiselect.scss +278 -278
  159. package/src/themes/theme-base/components/input/_password.scss +32 -32
  160. package/src/themes/theme-base/components/input/_radiobutton.scss +169 -169
  161. package/src/themes/theme-base/components/input/_rating.scss +80 -80
  162. package/src/themes/theme-base/components/input/_selectbutton.scss +49 -49
  163. package/src/themes/theme-base/components/input/_slider.scss +49 -49
  164. package/src/themes/theme-base/components/input/_togglebutton.scss +99 -99
  165. package/src/themes/theme-base/components/input/_treeselect.scss +151 -151
  166. package/src/themes/theme-base/components/input/_tristatecheckbox.scss +46 -46
  167. package/src/themes/theme-base/components/menu/_breadcrumb.scss +42 -42
  168. package/src/themes/theme-base/components/menu/_contextmenu.scss +39 -39
  169. package/src/themes/theme-base/components/menu/_dock.scss +109 -109
  170. package/src/themes/theme-base/components/menu/_megamenu.scss +141 -141
  171. package/src/themes/theme-base/components/menu/_menu.scss +33 -33
  172. package/src/themes/theme-base/components/menu/_menubar.scss +216 -216
  173. package/src/themes/theme-base/components/menu/_panelmenu.scss +153 -153
  174. package/src/themes/theme-base/components/menu/_slidemenu.scss +60 -60
  175. package/src/themes/theme-base/components/menu/_steps.scss +57 -57
  176. package/src/themes/theme-base/components/menu/_tabmenu.scss +50 -50
  177. package/src/themes/theme-base/components/menu/_tieredmenu.scss +43 -43
  178. package/src/themes/theme-base/components/messages/_inlinemessage.scss +69 -69
  179. package/src/themes/theme-base/components/messages/_message.scss +107 -107
  180. package/src/themes/theme-base/components/messages/_toast.scss +100 -100
  181. package/src/themes/theme-base/components/misc/_avatar.scss +33 -33
  182. package/src/themes/theme-base/components/misc/_badge.scss +76 -76
  183. package/src/themes/theme-base/components/misc/_chip.scss +38 -38
  184. package/src/themes/theme-base/components/misc/_inplace.scss +17 -17
  185. package/src/themes/theme-base/components/misc/_metergroup.scss +80 -80
  186. package/src/themes/theme-base/components/misc/_progressbar.scss +17 -17
  187. package/src/themes/theme-base/components/misc/_scrolltop.scss +24 -24
  188. package/src/themes/theme-base/components/misc/_skeleton.scss +7 -7
  189. package/src/themes/theme-base/components/misc/_tag.scss +39 -39
  190. package/src/themes/theme-base/components/misc/_terminal.scss +12 -12
  191. package/src/themes/theme-base/components/multimedia/_galleria.scss +153 -153
  192. package/src/themes/theme-base/components/multimedia/_image.scss +53 -53
  193. package/src/themes/theme-base/components/overlay/_confirmpopup.scss +72 -72
  194. package/src/themes/theme-base/components/overlay/_dialog.scss +78 -78
  195. package/src/themes/theme-base/components/overlay/_overlaypanel.scss +64 -64
  196. package/src/themes/theme-base/components/overlay/_sidebar.scss +23 -23
  197. package/src/themes/theme-base/components/overlay/_tooltip.scss +33 -33
  198. package/src/themes/theme-base/components/panel/_accordion.scss +118 -118
  199. package/src/themes/theme-base/components/panel/_card.scss +30 -30
  200. package/src/themes/theme-base/components/panel/_divider.scss +30 -30
  201. package/src/themes/theme-base/components/panel/_fieldset.scss +47 -47
  202. package/src/themes/theme-base/components/panel/_panel.scss +47 -47
  203. package/src/themes/theme-base/components/panel/_scrollpanel.scss +10 -10
  204. package/src/themes/theme-base/components/panel/_splitter.scss +23 -23
  205. package/src/themes/theme-base/components/panel/_stepper.scss +136 -136
  206. package/src/themes/theme-base/components/panel/_tabview.scss +147 -147
  207. package/src/themes/theme-base/components/panel/_toolbar.scss +11 -11
  208. package/terser.config.cjs +25 -25
  209. package/todo.md +18 -18
  210. package/tools/build-themes.cjs +65 -65
  211. package/tools/copy-distribution-files.cjs +77 -77
  212. package/tools/minify.cjs +55 -55
  213. package/tsconfig.json +48 -48
  214. package/typedoc.json +12 -12
  215. package/.claude/settings.local.json +0 -7
@@ -1,512 +1,512 @@
1
- /*
2
- * Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
3
- *
4
- * CoreStream Protocol Types for TypeScript
5
- *
6
- * This module defines the JSON-wrapped CoreStream protocol used for
7
- * WebSocket communication between web clients and autocore-server.
8
- *
9
- * The protocol supports four message types:
10
- * - SDO: Service Data Object - read/write individual values
11
- * - Cyclic: Subscription-based push updates
12
- * - System: Module lifecycle (announce, health, start/stop)
13
- * - RPC: Remote procedure calls for arbitrary functions
14
- *
15
- * Addressing supports two modes:
16
- * - FQDN: Human-readable names like "vfd.frequency"
17
- * - Index:SubIndex: CANopen-style addresses like {index: 0x2000, subindex: 1}
18
- */
19
-
20
- // ============================================================================
21
- // Core Types
22
- // ============================================================================
23
-
24
- /** Message type discriminator */
25
- export type MessageType = "sdo" | "cyclic" | "system" | "rpc";
26
-
27
- /**
28
- * Address can be either a symbol name (FQDN) or CANopen-style index:subindex.
29
- * Modules accept both formats and resolve internally.
30
- */
31
- export type SymbolAddress =
32
- | { symbol: string } // FQDN: "vfd.frequency"
33
- | { index: number; subindex: number }; // OD: {index: 0x2000, subindex: 1}
34
-
35
- /** Data types supported by the protocol */
36
- export type DataType =
37
- | "bool"
38
- | "i8" | "i16" | "i32" | "i64"
39
- | "u8" | "u16" | "u32" | "u64"
40
- | "f32" | "f64"
41
- | "string"
42
- | "bytes" // Base64-encoded binary
43
- | "json"; // Arbitrary JSON value
44
-
45
- /** Access mode for symbols */
46
- export type AccessMode = "ro" | "wo" | "rw";
47
-
48
- // ============================================================================
49
- // Base Message Structure
50
- // ============================================================================
51
-
52
- /** Base structure for all CoreStream messages */
53
- export interface CoreStreamMessage<T extends MessageType = MessageType> {
54
- /** Message type discriminator */
55
- type: T;
56
- /** Command within the message type */
57
- command: string;
58
- /** Request ID for request/response matching (optional for push messages) */
59
- request_id?: number;
60
- /** Message payload (type-specific) */
61
- payload: unknown;
62
- }
63
-
64
- // ============================================================================
65
- // SDO Messages (Service Data Object - Read/Write)
66
- // ============================================================================
67
-
68
- export type SdoCommand =
69
- | "upload_request" // Read request (client → server)
70
- | "upload_response" // Read response (server → client)
71
- | "download_request" // Write request (client → server)
72
- | "download_response" // Write response (server → client)
73
- | "abort"; // Error/abort
74
-
75
- /** SDO Upload Request - Read a value */
76
- export interface SdoUploadRequest extends CoreStreamMessage<"sdo"> {
77
- command: "upload_request";
78
- request_id: number;
79
- payload: SymbolAddress;
80
- }
81
-
82
- /** SDO Upload Response - Read result */
83
- export interface SdoUploadResponse extends CoreStreamMessage<"sdo"> {
84
- command: "upload_response";
85
- request_id: number;
86
- payload: {
87
- /** The address that was read (echoed back) */
88
- address: SymbolAddress;
89
- /** The value read */
90
- value: unknown;
91
- /** Data type of the value */
92
- datatype: DataType;
93
- };
94
- }
95
-
96
- /** SDO Download Request - Write a value */
97
- export interface SdoDownloadRequest extends CoreStreamMessage<"sdo"> {
98
- command: "download_request";
99
- request_id: number;
100
- payload: SymbolAddress & {
101
- /** The value to write */
102
- value: unknown;
103
- /** Optional: data type hint */
104
- datatype?: DataType;
105
- };
106
- }
107
-
108
- /** SDO Download Response - Write acknowledgment */
109
- export interface SdoDownloadResponse extends CoreStreamMessage<"sdo"> {
110
- command: "download_response";
111
- request_id: number;
112
- payload: {
113
- /** The address that was written (echoed back) */
114
- address: SymbolAddress;
115
- /** Whether the write succeeded */
116
- success: boolean;
117
- };
118
- }
119
-
120
- /** SDO Abort - Error response */
121
- export interface SdoAbort extends CoreStreamMessage<"sdo"> {
122
- command: "abort";
123
- request_id: number;
124
- payload: {
125
- /** The address that caused the error */
126
- address: SymbolAddress;
127
- /** CANopen-style abort code */
128
- abort_code: number;
129
- /** Human-readable error message */
130
- message: string;
131
- };
132
- }
133
-
134
- /** Standard SDO abort codes (CANopen-compatible) */
135
- export const SdoAbortCode = {
136
- TOGGLE_BIT_NOT_ALTERED: 0x05030000,
137
- SDO_PROTOCOL_TIMEOUT: 0x05040000,
138
- INVALID_COMMAND: 0x05040001,
139
- OUT_OF_MEMORY: 0x05040005,
140
- UNSUPPORTED_ACCESS: 0x06010000,
141
- WRITE_ONLY: 0x06010001,
142
- READ_ONLY: 0x06010002,
143
- OBJECT_NOT_EXIST: 0x06020000,
144
- SUBINDEX_NOT_EXIST: 0x06090011,
145
- GENERAL_ERROR: 0x08000000,
146
- DATA_TRANSFER_ERROR: 0x08000020,
147
- DATA_LENGTH_MISMATCH: 0x08000022,
148
- } as const;
149
-
150
- export type SdoMessage =
151
- | SdoUploadRequest
152
- | SdoUploadResponse
153
- | SdoDownloadRequest
154
- | SdoDownloadResponse
155
- | SdoAbort;
156
-
157
- // ============================================================================
158
- // Cyclic Messages (Subscriptions and Push Updates)
159
- // ============================================================================
160
-
161
- export type CyclicCommand =
162
- | "subscribe" // Subscribe to updates
163
- | "subscribe_ack" // Subscribe acknowledgment
164
- | "unsubscribe" // Unsubscribe
165
- | "unsubscribe_ack" // Unsubscribe acknowledgment
166
- | "data_update" // Single value update (push)
167
- | "data_update_batch"; // Multiple values update (push)
168
-
169
- /** Subscription options for cyclic updates */
170
- export interface SubscriptionOptions {
171
- /** Minimum interval between updates in milliseconds */
172
- sampling_interval_ms?: number;
173
- /** Only send updates when value changes */
174
- on_change_only?: boolean;
175
- /** Deadband as percentage of full scale (0-100) */
176
- deadband_percent?: number;
177
- /** Deadband as absolute value */
178
- deadband_absolute?: number;
179
- /** Queue size for buffering (default: 1 = latest only) */
180
- queue_size?: number;
181
- }
182
-
183
- /** Subscribe to cyclic updates for a symbol */
184
- export interface CyclicSubscribe extends CoreStreamMessage<"cyclic"> {
185
- command: "subscribe";
186
- request_id: number;
187
- payload: SymbolAddress & {
188
- /** Optional subscription options */
189
- options?: SubscriptionOptions;
190
- };
191
- }
192
-
193
- /** Subscribe acknowledgment */
194
- export interface CyclicSubscribeAck extends CoreStreamMessage<"cyclic"> {
195
- command: "subscribe_ack";
196
- request_id: number;
197
- payload: {
198
- /** The address subscribed to */
199
- address: SymbolAddress;
200
- /** Whether subscription succeeded */
201
- success: boolean;
202
- /** Error message if failed */
203
- error?: string;
204
- /** Effective options after server clamping */
205
- effective_options?: SubscriptionOptions;
206
- };
207
- }
208
-
209
- /** Unsubscribe from cyclic updates */
210
- export interface CyclicUnsubscribe extends CoreStreamMessage<"cyclic"> {
211
- command: "unsubscribe";
212
- request_id: number;
213
- payload: SymbolAddress;
214
- }
215
-
216
- /** Unsubscribe acknowledgment */
217
- export interface CyclicUnsubscribeAck extends CoreStreamMessage<"cyclic"> {
218
- command: "unsubscribe_ack";
219
- request_id: number;
220
- payload: {
221
- address: SymbolAddress;
222
- success: boolean;
223
- };
224
- }
225
-
226
- /** Single value update (pushed from server) */
227
- export interface CyclicDataUpdate extends CoreStreamMessage<"cyclic"> {
228
- command: "data_update";
229
- payload: {
230
- /** The symbol that changed */
231
- symbol: string;
232
- /** The new value */
233
- value: unknown;
234
- /** Server timestamp (nanoseconds since epoch, optional) */
235
- timestamp_ns?: number;
236
- /** Data type */
237
- datatype?: DataType;
238
- };
239
- }
240
-
241
- /** Batch value update (multiple values in one message) */
242
- export interface CyclicDataUpdateBatch extends CoreStreamMessage<"cyclic"> {
243
- command: "data_update_batch";
244
- payload: {
245
- /** Array of updates */
246
- updates: Array<{
247
- symbol: string;
248
- value: unknown;
249
- timestamp_ns?: number;
250
- datatype?: DataType;
251
- }>;
252
- };
253
- }
254
-
255
- export type CyclicMessage =
256
- | CyclicSubscribe
257
- | CyclicSubscribeAck
258
- | CyclicUnsubscribe
259
- | CyclicUnsubscribeAck
260
- | CyclicDataUpdate
261
- | CyclicDataUpdateBatch;
262
-
263
- // ============================================================================
264
- // System Messages (Module Lifecycle)
265
- // ============================================================================
266
-
267
- export type SystemCommand =
268
- | "announce" // Module registration
269
- | "announce_ack" // Registration acknowledgment
270
- | "discovery" // Request all modules
271
- | "discovery_response" // Module list
272
- | "leaving" // Module disconnecting
273
- | "start" // Start cyclic operation
274
- | "stop" // Stop cyclic operation
275
- | "health_request" // Health check
276
- | "health_response"; // Health status
277
-
278
- /** Symbol descriptor in module announcement */
279
- export interface SymbolDescriptor {
280
- /** Human-readable symbol name (FQDN within module) */
281
- name: string;
282
- /** CANopen-style index */
283
- index: number;
284
- /** CANopen-style subindex */
285
- subindex: number;
286
- /** Data type */
287
- datatype: DataType;
288
- /** Access mode */
289
- access: AccessMode;
290
- /** Optional description */
291
- description?: string;
292
- /** Optional unit (e.g., "mm", "Hz") */
293
- unit?: string;
294
- /** Optional min value (for validation) */
295
- min?: number;
296
- /** Optional max value (for validation) */
297
- max?: number;
298
- }
299
-
300
- /** Module capabilities */
301
- export interface ModuleCapabilities {
302
- /** Supports SDO read */
303
- sdo_read: boolean;
304
- /** Supports SDO write */
305
- sdo_write: boolean;
306
- /** Supports cyclic subscriptions */
307
- cyclic: boolean;
308
- /** Supports batch operations */
309
- batch: boolean;
310
- /** Maximum symbols per batch */
311
- max_batch_size?: number;
312
- }
313
-
314
- /** Module announces itself and its symbol catalog */
315
- export interface SystemAnnounce extends CoreStreamMessage<"system"> {
316
- command: "announce";
317
- payload: {
318
- /** Module name (used as FQDN prefix) */
319
- module_name: string;
320
- /** Module version */
321
- version?: string;
322
- /** Module capabilities */
323
- capabilities: ModuleCapabilities;
324
- /** Symbol catalog */
325
- symbols: SymbolDescriptor[];
326
- };
327
- }
328
-
329
- /** Announcement acknowledgment */
330
- export interface SystemAnnounceAck extends CoreStreamMessage<"system"> {
331
- command: "announce_ack";
332
- payload: {
333
- /** Whether announcement was accepted */
334
- success: boolean;
335
- /** Assigned module ID (if applicable) */
336
- module_id?: number;
337
- /** Error message if failed */
338
- error?: string;
339
- };
340
- }
341
-
342
- /** Request list of all connected modules */
343
- export interface SystemDiscovery extends CoreStreamMessage<"system"> {
344
- command: "discovery";
345
- request_id: number;
346
- payload: Record<string, never>; // Empty object
347
- }
348
-
349
- /** Discovery response with module list */
350
- export interface SystemDiscoveryResponse extends CoreStreamMessage<"system"> {
351
- command: "discovery_response";
352
- request_id: number;
353
- payload: {
354
- modules: Array<{
355
- module_name: string;
356
- version?: string;
357
- capabilities: ModuleCapabilities;
358
- symbol_count: number;
359
- state: "running" | "stopped" | "error";
360
- }>;
361
- };
362
- }
363
-
364
- /** Module leaving notification */
365
- export interface SystemLeaving extends CoreStreamMessage<"system"> {
366
- command: "leaving";
367
- payload: {
368
- module_name: string;
369
- reason?: string;
370
- };
371
- }
372
-
373
- /** Health check request */
374
- export interface SystemHealthRequest extends CoreStreamMessage<"system"> {
375
- command: "health_request";
376
- request_id: number;
377
- payload: {
378
- /** Optional: specific module to check */
379
- module_name?: string;
380
- };
381
- }
382
-
383
- /** Health check response */
384
- export interface SystemHealthResponse extends CoreStreamMessage<"system"> {
385
- command: "health_response";
386
- request_id: number;
387
- payload: {
388
- healthy: boolean;
389
- modules: Array<{
390
- module_name: string;
391
- state: "running" | "stopped" | "error";
392
- uptime_secs?: number;
393
- error?: string;
394
- }>;
395
- };
396
- }
397
-
398
- export type SystemMessage =
399
- | SystemAnnounce
400
- | SystemAnnounceAck
401
- | SystemDiscovery
402
- | SystemDiscoveryResponse
403
- | SystemLeaving
404
- | SystemHealthRequest
405
- | SystemHealthResponse;
406
-
407
- // ============================================================================
408
- // RPC Messages (Remote Procedure Calls)
409
- // ============================================================================
410
-
411
- export type RpcCommand =
412
- | "invoke" // Call a function
413
- | "response"; // Function result
414
-
415
- /** RPC invoke request - call a function on a module */
416
- export interface RpcInvoke extends CoreStreamMessage<"rpc"> {
417
- command: "invoke";
418
- request_id: number;
419
- payload: {
420
- /** Target module name */
421
- module: string;
422
- /** Method/function name */
423
- method: string;
424
- /** Arguments (JSON object) */
425
- args?: Record<string, unknown>;
426
- };
427
- }
428
-
429
- /** RPC response - function result */
430
- export interface RpcResponse extends CoreStreamMessage<"rpc"> {
431
- command: "response";
432
- request_id: number;
433
- payload: {
434
- /** Whether the call succeeded */
435
- success: boolean;
436
- /** Return value (if success) */
437
- data?: unknown;
438
- /** Error message (if failed) */
439
- error?: string;
440
- /** Error code (if failed) */
441
- error_code?: number;
442
- };
443
- }
444
-
445
- export type RpcMessage = RpcInvoke | RpcResponse;
446
-
447
- // ============================================================================
448
- // Union Type for All Messages
449
- // ============================================================================
450
-
451
- export type AnyCoreSteamMessage =
452
- | SdoMessage
453
- | CyclicMessage
454
- | SystemMessage
455
- | RpcMessage;
456
-
457
- // ============================================================================
458
- // Helper Functions
459
- // ============================================================================
460
-
461
- /** Type guard for SDO messages */
462
- export function isSdoMessage(msg: CoreStreamMessage): msg is SdoMessage {
463
- return msg.type === "sdo";
464
- }
465
-
466
- /** Type guard for Cyclic messages */
467
- export function isCyclicMessage(msg: CoreStreamMessage): msg is CyclicMessage {
468
- return msg.type === "cyclic";
469
- }
470
-
471
- /** Type guard for System messages */
472
- export function isSystemMessage(msg: CoreStreamMessage): msg is SystemMessage {
473
- return msg.type === "system";
474
- }
475
-
476
- /** Type guard for RPC messages */
477
- export function isRpcMessage(msg: CoreStreamMessage): msg is RpcMessage {
478
- return msg.type === "rpc";
479
- }
480
-
481
- /** Check if an address uses symbol format */
482
- export function isSymbolAddress(addr: SymbolAddress): addr is { symbol: string } {
483
- return "symbol" in addr;
484
- }
485
-
486
- /** Check if an address uses index:subindex format */
487
- export function isIndexAddress(addr: SymbolAddress): addr is { index: number; subindex: number } {
488
- return "index" in addr && "subindex" in addr;
489
- }
490
-
491
- /** Convert address to string for display/logging */
492
- export function addressToString(addr: SymbolAddress): string {
493
- if (isSymbolAddress(addr)) {
494
- return addr.symbol;
495
- } else {
496
- return `0x${addr.index.toString(16).padStart(4, '0')}:${addr.subindex.toString(16).padStart(2, '0')}`;
497
- }
498
- }
499
-
500
- /** Parse an address string (either "vfd.frequency" or "0x2000:01") */
501
- export function parseAddress(str: string): SymbolAddress {
502
- // Check for hex format: 0xNNNN:NN or NNNN:NN
503
- const hexMatch = str.match(/^(?:0x)?([0-9a-fA-F]{1,4}):([0-9a-fA-F]{1,2})$/);
504
- if (hexMatch) {
505
- return {
506
- index: parseInt(hexMatch[1], 16),
507
- subindex: parseInt(hexMatch[2], 16),
508
- };
509
- }
510
- // Otherwise treat as symbol name
511
- return { symbol: str };
512
- }
1
+ /*
2
+ * Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
3
+ *
4
+ * CoreStream Protocol Types for TypeScript
5
+ *
6
+ * This module defines the JSON-wrapped CoreStream protocol used for
7
+ * WebSocket communication between web clients and autocore-server.
8
+ *
9
+ * The protocol supports four message types:
10
+ * - SDO: Service Data Object - read/write individual values
11
+ * - Cyclic: Subscription-based push updates
12
+ * - System: Module lifecycle (announce, health, start/stop)
13
+ * - RPC: Remote procedure calls for arbitrary functions
14
+ *
15
+ * Addressing supports two modes:
16
+ * - FQDN: Human-readable names like "vfd.frequency"
17
+ * - Index:SubIndex: CANopen-style addresses like {index: 0x2000, subindex: 1}
18
+ */
19
+
20
+ // ============================================================================
21
+ // Core Types
22
+ // ============================================================================
23
+
24
+ /** Message type discriminator */
25
+ export type MessageType = "sdo" | "cyclic" | "system" | "rpc";
26
+
27
+ /**
28
+ * Address can be either a symbol name (FQDN) or CANopen-style index:subindex.
29
+ * Modules accept both formats and resolve internally.
30
+ */
31
+ export type SymbolAddress =
32
+ | { symbol: string } // FQDN: "vfd.frequency"
33
+ | { index: number; subindex: number }; // OD: {index: 0x2000, subindex: 1}
34
+
35
+ /** Data types supported by the protocol */
36
+ export type DataType =
37
+ | "bool"
38
+ | "i8" | "i16" | "i32" | "i64"
39
+ | "u8" | "u16" | "u32" | "u64"
40
+ | "f32" | "f64"
41
+ | "string"
42
+ | "bytes" // Base64-encoded binary
43
+ | "json"; // Arbitrary JSON value
44
+
45
+ /** Access mode for symbols */
46
+ export type AccessMode = "ro" | "wo" | "rw";
47
+
48
+ // ============================================================================
49
+ // Base Message Structure
50
+ // ============================================================================
51
+
52
+ /** Base structure for all CoreStream messages */
53
+ export interface CoreStreamMessage<T extends MessageType = MessageType> {
54
+ /** Message type discriminator */
55
+ type: T;
56
+ /** Command within the message type */
57
+ command: string;
58
+ /** Request ID for request/response matching (optional for push messages) */
59
+ request_id?: number;
60
+ /** Message payload (type-specific) */
61
+ payload: unknown;
62
+ }
63
+
64
+ // ============================================================================
65
+ // SDO Messages (Service Data Object - Read/Write)
66
+ // ============================================================================
67
+
68
+ export type SdoCommand =
69
+ | "upload_request" // Read request (client → server)
70
+ | "upload_response" // Read response (server → client)
71
+ | "download_request" // Write request (client → server)
72
+ | "download_response" // Write response (server → client)
73
+ | "abort"; // Error/abort
74
+
75
+ /** SDO Upload Request - Read a value */
76
+ export interface SdoUploadRequest extends CoreStreamMessage<"sdo"> {
77
+ command: "upload_request";
78
+ request_id: number;
79
+ payload: SymbolAddress;
80
+ }
81
+
82
+ /** SDO Upload Response - Read result */
83
+ export interface SdoUploadResponse extends CoreStreamMessage<"sdo"> {
84
+ command: "upload_response";
85
+ request_id: number;
86
+ payload: {
87
+ /** The address that was read (echoed back) */
88
+ address: SymbolAddress;
89
+ /** The value read */
90
+ value: unknown;
91
+ /** Data type of the value */
92
+ datatype: DataType;
93
+ };
94
+ }
95
+
96
+ /** SDO Download Request - Write a value */
97
+ export interface SdoDownloadRequest extends CoreStreamMessage<"sdo"> {
98
+ command: "download_request";
99
+ request_id: number;
100
+ payload: SymbolAddress & {
101
+ /** The value to write */
102
+ value: unknown;
103
+ /** Optional: data type hint */
104
+ datatype?: DataType;
105
+ };
106
+ }
107
+
108
+ /** SDO Download Response - Write acknowledgment */
109
+ export interface SdoDownloadResponse extends CoreStreamMessage<"sdo"> {
110
+ command: "download_response";
111
+ request_id: number;
112
+ payload: {
113
+ /** The address that was written (echoed back) */
114
+ address: SymbolAddress;
115
+ /** Whether the write succeeded */
116
+ success: boolean;
117
+ };
118
+ }
119
+
120
+ /** SDO Abort - Error response */
121
+ export interface SdoAbort extends CoreStreamMessage<"sdo"> {
122
+ command: "abort";
123
+ request_id: number;
124
+ payload: {
125
+ /** The address that caused the error */
126
+ address: SymbolAddress;
127
+ /** CANopen-style abort code */
128
+ abort_code: number;
129
+ /** Human-readable error message */
130
+ message: string;
131
+ };
132
+ }
133
+
134
+ /** Standard SDO abort codes (CANopen-compatible) */
135
+ export const SdoAbortCode = {
136
+ TOGGLE_BIT_NOT_ALTERED: 0x05030000,
137
+ SDO_PROTOCOL_TIMEOUT: 0x05040000,
138
+ INVALID_COMMAND: 0x05040001,
139
+ OUT_OF_MEMORY: 0x05040005,
140
+ UNSUPPORTED_ACCESS: 0x06010000,
141
+ WRITE_ONLY: 0x06010001,
142
+ READ_ONLY: 0x06010002,
143
+ OBJECT_NOT_EXIST: 0x06020000,
144
+ SUBINDEX_NOT_EXIST: 0x06090011,
145
+ GENERAL_ERROR: 0x08000000,
146
+ DATA_TRANSFER_ERROR: 0x08000020,
147
+ DATA_LENGTH_MISMATCH: 0x08000022,
148
+ } as const;
149
+
150
+ export type SdoMessage =
151
+ | SdoUploadRequest
152
+ | SdoUploadResponse
153
+ | SdoDownloadRequest
154
+ | SdoDownloadResponse
155
+ | SdoAbort;
156
+
157
+ // ============================================================================
158
+ // Cyclic Messages (Subscriptions and Push Updates)
159
+ // ============================================================================
160
+
161
+ export type CyclicCommand =
162
+ | "subscribe" // Subscribe to updates
163
+ | "subscribe_ack" // Subscribe acknowledgment
164
+ | "unsubscribe" // Unsubscribe
165
+ | "unsubscribe_ack" // Unsubscribe acknowledgment
166
+ | "data_update" // Single value update (push)
167
+ | "data_update_batch"; // Multiple values update (push)
168
+
169
+ /** Subscription options for cyclic updates */
170
+ export interface SubscriptionOptions {
171
+ /** Minimum interval between updates in milliseconds */
172
+ sampling_interval_ms?: number;
173
+ /** Only send updates when value changes */
174
+ on_change_only?: boolean;
175
+ /** Deadband as percentage of full scale (0-100) */
176
+ deadband_percent?: number;
177
+ /** Deadband as absolute value */
178
+ deadband_absolute?: number;
179
+ /** Queue size for buffering (default: 1 = latest only) */
180
+ queue_size?: number;
181
+ }
182
+
183
+ /** Subscribe to cyclic updates for a symbol */
184
+ export interface CyclicSubscribe extends CoreStreamMessage<"cyclic"> {
185
+ command: "subscribe";
186
+ request_id: number;
187
+ payload: SymbolAddress & {
188
+ /** Optional subscription options */
189
+ options?: SubscriptionOptions;
190
+ };
191
+ }
192
+
193
+ /** Subscribe acknowledgment */
194
+ export interface CyclicSubscribeAck extends CoreStreamMessage<"cyclic"> {
195
+ command: "subscribe_ack";
196
+ request_id: number;
197
+ payload: {
198
+ /** The address subscribed to */
199
+ address: SymbolAddress;
200
+ /** Whether subscription succeeded */
201
+ success: boolean;
202
+ /** Error message if failed */
203
+ error?: string;
204
+ /** Effective options after server clamping */
205
+ effective_options?: SubscriptionOptions;
206
+ };
207
+ }
208
+
209
+ /** Unsubscribe from cyclic updates */
210
+ export interface CyclicUnsubscribe extends CoreStreamMessage<"cyclic"> {
211
+ command: "unsubscribe";
212
+ request_id: number;
213
+ payload: SymbolAddress;
214
+ }
215
+
216
+ /** Unsubscribe acknowledgment */
217
+ export interface CyclicUnsubscribeAck extends CoreStreamMessage<"cyclic"> {
218
+ command: "unsubscribe_ack";
219
+ request_id: number;
220
+ payload: {
221
+ address: SymbolAddress;
222
+ success: boolean;
223
+ };
224
+ }
225
+
226
+ /** Single value update (pushed from server) */
227
+ export interface CyclicDataUpdate extends CoreStreamMessage<"cyclic"> {
228
+ command: "data_update";
229
+ payload: {
230
+ /** The symbol that changed */
231
+ symbol: string;
232
+ /** The new value */
233
+ value: unknown;
234
+ /** Server timestamp (nanoseconds since epoch, optional) */
235
+ timestamp_ns?: number;
236
+ /** Data type */
237
+ datatype?: DataType;
238
+ };
239
+ }
240
+
241
+ /** Batch value update (multiple values in one message) */
242
+ export interface CyclicDataUpdateBatch extends CoreStreamMessage<"cyclic"> {
243
+ command: "data_update_batch";
244
+ payload: {
245
+ /** Array of updates */
246
+ updates: Array<{
247
+ symbol: string;
248
+ value: unknown;
249
+ timestamp_ns?: number;
250
+ datatype?: DataType;
251
+ }>;
252
+ };
253
+ }
254
+
255
+ export type CyclicMessage =
256
+ | CyclicSubscribe
257
+ | CyclicSubscribeAck
258
+ | CyclicUnsubscribe
259
+ | CyclicUnsubscribeAck
260
+ | CyclicDataUpdate
261
+ | CyclicDataUpdateBatch;
262
+
263
+ // ============================================================================
264
+ // System Messages (Module Lifecycle)
265
+ // ============================================================================
266
+
267
+ export type SystemCommand =
268
+ | "announce" // Module registration
269
+ | "announce_ack" // Registration acknowledgment
270
+ | "discovery" // Request all modules
271
+ | "discovery_response" // Module list
272
+ | "leaving" // Module disconnecting
273
+ | "start" // Start cyclic operation
274
+ | "stop" // Stop cyclic operation
275
+ | "health_request" // Health check
276
+ | "health_response"; // Health status
277
+
278
+ /** Symbol descriptor in module announcement */
279
+ export interface SymbolDescriptor {
280
+ /** Human-readable symbol name (FQDN within module) */
281
+ name: string;
282
+ /** CANopen-style index */
283
+ index: number;
284
+ /** CANopen-style subindex */
285
+ subindex: number;
286
+ /** Data type */
287
+ datatype: DataType;
288
+ /** Access mode */
289
+ access: AccessMode;
290
+ /** Optional description */
291
+ description?: string;
292
+ /** Optional unit (e.g., "mm", "Hz") */
293
+ unit?: string;
294
+ /** Optional min value (for validation) */
295
+ min?: number;
296
+ /** Optional max value (for validation) */
297
+ max?: number;
298
+ }
299
+
300
+ /** Module capabilities */
301
+ export interface ModuleCapabilities {
302
+ /** Supports SDO read */
303
+ sdo_read: boolean;
304
+ /** Supports SDO write */
305
+ sdo_write: boolean;
306
+ /** Supports cyclic subscriptions */
307
+ cyclic: boolean;
308
+ /** Supports batch operations */
309
+ batch: boolean;
310
+ /** Maximum symbols per batch */
311
+ max_batch_size?: number;
312
+ }
313
+
314
+ /** Module announces itself and its symbol catalog */
315
+ export interface SystemAnnounce extends CoreStreamMessage<"system"> {
316
+ command: "announce";
317
+ payload: {
318
+ /** Module name (used as FQDN prefix) */
319
+ module_name: string;
320
+ /** Module version */
321
+ version?: string;
322
+ /** Module capabilities */
323
+ capabilities: ModuleCapabilities;
324
+ /** Symbol catalog */
325
+ symbols: SymbolDescriptor[];
326
+ };
327
+ }
328
+
329
+ /** Announcement acknowledgment */
330
+ export interface SystemAnnounceAck extends CoreStreamMessage<"system"> {
331
+ command: "announce_ack";
332
+ payload: {
333
+ /** Whether announcement was accepted */
334
+ success: boolean;
335
+ /** Assigned module ID (if applicable) */
336
+ module_id?: number;
337
+ /** Error message if failed */
338
+ error?: string;
339
+ };
340
+ }
341
+
342
+ /** Request list of all connected modules */
343
+ export interface SystemDiscovery extends CoreStreamMessage<"system"> {
344
+ command: "discovery";
345
+ request_id: number;
346
+ payload: Record<string, never>; // Empty object
347
+ }
348
+
349
+ /** Discovery response with module list */
350
+ export interface SystemDiscoveryResponse extends CoreStreamMessage<"system"> {
351
+ command: "discovery_response";
352
+ request_id: number;
353
+ payload: {
354
+ modules: Array<{
355
+ module_name: string;
356
+ version?: string;
357
+ capabilities: ModuleCapabilities;
358
+ symbol_count: number;
359
+ state: "running" | "stopped" | "error";
360
+ }>;
361
+ };
362
+ }
363
+
364
+ /** Module leaving notification */
365
+ export interface SystemLeaving extends CoreStreamMessage<"system"> {
366
+ command: "leaving";
367
+ payload: {
368
+ module_name: string;
369
+ reason?: string;
370
+ };
371
+ }
372
+
373
+ /** Health check request */
374
+ export interface SystemHealthRequest extends CoreStreamMessage<"system"> {
375
+ command: "health_request";
376
+ request_id: number;
377
+ payload: {
378
+ /** Optional: specific module to check */
379
+ module_name?: string;
380
+ };
381
+ }
382
+
383
+ /** Health check response */
384
+ export interface SystemHealthResponse extends CoreStreamMessage<"system"> {
385
+ command: "health_response";
386
+ request_id: number;
387
+ payload: {
388
+ healthy: boolean;
389
+ modules: Array<{
390
+ module_name: string;
391
+ state: "running" | "stopped" | "error";
392
+ uptime_secs?: number;
393
+ error?: string;
394
+ }>;
395
+ };
396
+ }
397
+
398
+ export type SystemMessage =
399
+ | SystemAnnounce
400
+ | SystemAnnounceAck
401
+ | SystemDiscovery
402
+ | SystemDiscoveryResponse
403
+ | SystemLeaving
404
+ | SystemHealthRequest
405
+ | SystemHealthResponse;
406
+
407
+ // ============================================================================
408
+ // RPC Messages (Remote Procedure Calls)
409
+ // ============================================================================
410
+
411
+ export type RpcCommand =
412
+ | "invoke" // Call a function
413
+ | "response"; // Function result
414
+
415
+ /** RPC invoke request - call a function on a module */
416
+ export interface RpcInvoke extends CoreStreamMessage<"rpc"> {
417
+ command: "invoke";
418
+ request_id: number;
419
+ payload: {
420
+ /** Target module name */
421
+ module: string;
422
+ /** Method/function name */
423
+ method: string;
424
+ /** Arguments (JSON object) */
425
+ args?: Record<string, unknown>;
426
+ };
427
+ }
428
+
429
+ /** RPC response - function result */
430
+ export interface RpcResponse extends CoreStreamMessage<"rpc"> {
431
+ command: "response";
432
+ request_id: number;
433
+ payload: {
434
+ /** Whether the call succeeded */
435
+ success: boolean;
436
+ /** Return value (if success) */
437
+ data?: unknown;
438
+ /** Error message (if failed) */
439
+ error?: string;
440
+ /** Error code (if failed) */
441
+ error_code?: number;
442
+ };
443
+ }
444
+
445
+ export type RpcMessage = RpcInvoke | RpcResponse;
446
+
447
+ // ============================================================================
448
+ // Union Type for All Messages
449
+ // ============================================================================
450
+
451
+ export type AnyCoreSteamMessage =
452
+ | SdoMessage
453
+ | CyclicMessage
454
+ | SystemMessage
455
+ | RpcMessage;
456
+
457
+ // ============================================================================
458
+ // Helper Functions
459
+ // ============================================================================
460
+
461
+ /** Type guard for SDO messages */
462
+ export function isSdoMessage(msg: CoreStreamMessage): msg is SdoMessage {
463
+ return msg.type === "sdo";
464
+ }
465
+
466
+ /** Type guard for Cyclic messages */
467
+ export function isCyclicMessage(msg: CoreStreamMessage): msg is CyclicMessage {
468
+ return msg.type === "cyclic";
469
+ }
470
+
471
+ /** Type guard for System messages */
472
+ export function isSystemMessage(msg: CoreStreamMessage): msg is SystemMessage {
473
+ return msg.type === "system";
474
+ }
475
+
476
+ /** Type guard for RPC messages */
477
+ export function isRpcMessage(msg: CoreStreamMessage): msg is RpcMessage {
478
+ return msg.type === "rpc";
479
+ }
480
+
481
+ /** Check if an address uses symbol format */
482
+ export function isSymbolAddress(addr: SymbolAddress): addr is { symbol: string } {
483
+ return "symbol" in addr;
484
+ }
485
+
486
+ /** Check if an address uses index:subindex format */
487
+ export function isIndexAddress(addr: SymbolAddress): addr is { index: number; subindex: number } {
488
+ return "index" in addr && "subindex" in addr;
489
+ }
490
+
491
+ /** Convert address to string for display/logging */
492
+ export function addressToString(addr: SymbolAddress): string {
493
+ if (isSymbolAddress(addr)) {
494
+ return addr.symbol;
495
+ } else {
496
+ return `0x${addr.index.toString(16).padStart(4, '0')}:${addr.subindex.toString(16).padStart(2, '0')}`;
497
+ }
498
+ }
499
+
500
+ /** Parse an address string (either "vfd.frequency" or "0x2000:01") */
501
+ export function parseAddress(str: string): SymbolAddress {
502
+ // Check for hex format: 0xNNNN:NN or NNNN:NN
503
+ const hexMatch = str.match(/^(?:0x)?([0-9a-fA-F]{1,4}):([0-9a-fA-F]{1,2})$/);
504
+ if (hexMatch) {
505
+ return {
506
+ index: parseInt(hexMatch[1], 16),
507
+ subindex: parseInt(hexMatch[2], 16),
508
+ };
509
+ }
510
+ // Otherwise treat as symbol name
511
+ return { symbol: str };
512
+ }