@robotical/raftjs 2.1.0 → 2.1.3

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 (172) hide show
  1. package/devdocs/devbin-backwards-compatibility.md +105 -0
  2. package/devdocs/pseudocode-to-js-transpiler.md +563 -0
  3. package/dist/react-native/PseudocodeTranspiler.d.ts +6 -0
  4. package/dist/react-native/PseudocodeTranspiler.js +115 -0
  5. package/dist/react-native/PseudocodeTranspiler.js.map +1 -0
  6. package/dist/react-native/RaftAttributeHandler.d.ts +1 -1
  7. package/dist/react-native/RaftAttributeHandler.js +108 -32
  8. package/dist/react-native/RaftAttributeHandler.js.map +1 -1
  9. package/dist/react-native/RaftChannelBLE.web.d.ts +4 -0
  10. package/dist/react-native/RaftChannelBLE.web.js +59 -21
  11. package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
  12. package/dist/react-native/RaftChannelSimulated.d.ts +1 -0
  13. package/dist/react-native/RaftChannelSimulated.js +9 -5
  14. package/dist/react-native/RaftChannelSimulated.js.map +1 -1
  15. package/dist/react-native/RaftChannelWebSocket.js +16 -1
  16. package/dist/react-native/RaftChannelWebSocket.js.map +1 -1
  17. package/dist/react-native/RaftConnector.d.ts +29 -1
  18. package/dist/react-native/RaftConnector.js +177 -11
  19. package/dist/react-native/RaftConnector.js.map +1 -1
  20. package/dist/react-native/RaftCustomAttrHandler.d.ts +2 -2
  21. package/dist/react-native/RaftCustomAttrHandler.js +32 -44
  22. package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
  23. package/dist/react-native/RaftDeviceInfo.d.ts +18 -0
  24. package/dist/react-native/RaftDeviceInfo.js +8 -0
  25. package/dist/react-native/RaftDeviceInfo.js.map +1 -1
  26. package/dist/react-native/RaftDeviceManager.d.ts +30 -3
  27. package/dist/react-native/RaftDeviceManager.js +618 -107
  28. package/dist/react-native/RaftDeviceManager.js.map +1 -1
  29. package/dist/react-native/RaftDeviceMgrIF.d.ts +11 -2
  30. package/dist/react-native/RaftDeviceStates.d.ts +27 -3
  31. package/dist/react-native/RaftDeviceStates.js +31 -6
  32. package/dist/react-native/RaftDeviceStates.js.map +1 -1
  33. package/dist/react-native/RaftFileHandler.d.ts +1 -1
  34. package/dist/react-native/RaftFileHandler.js +101 -34
  35. package/dist/react-native/RaftFileHandler.js.map +1 -1
  36. package/dist/react-native/RaftMicroPythonConsoleClient.d.ts +38 -0
  37. package/dist/react-native/RaftMicroPythonConsoleClient.js +45 -0
  38. package/dist/react-native/RaftMicroPythonConsoleClient.js.map +1 -0
  39. package/dist/react-native/RaftMsgHandler.d.ts +1 -1
  40. package/dist/react-native/RaftMsgHandler.js +6 -3
  41. package/dist/react-native/RaftMsgHandler.js.map +1 -1
  42. package/dist/react-native/RaftPublish.d.ts +2 -0
  43. package/dist/react-native/RaftPublish.js +81 -0
  44. package/dist/react-native/RaftPublish.js.map +1 -0
  45. package/dist/react-native/RaftStreamHandler.d.ts +11 -0
  46. package/dist/react-native/RaftStreamHandler.js +66 -0
  47. package/dist/react-native/RaftStreamHandler.js.map +1 -1
  48. package/dist/react-native/RaftStruct.d.ts +2 -2
  49. package/dist/react-native/RaftStruct.js +97 -26
  50. package/dist/react-native/RaftStruct.js.map +1 -1
  51. package/dist/react-native/RaftSystemType.d.ts +1 -0
  52. package/dist/react-native/RaftSystemUtils.d.ts +17 -1
  53. package/dist/react-native/RaftSystemUtils.js +51 -0
  54. package/dist/react-native/RaftSystemUtils.js.map +1 -1
  55. package/dist/react-native/RaftTimezone.d.ts +16 -0
  56. package/dist/react-native/RaftTimezone.js +153 -0
  57. package/dist/react-native/RaftTimezone.js.map +1 -0
  58. package/dist/react-native/RaftTypes.d.ts +46 -1
  59. package/dist/react-native/RaftTypes.js.map +1 -1
  60. package/dist/react-native/RaftUpdateManager.js +1 -1
  61. package/dist/react-native/RaftUpdateManager.js.map +1 -1
  62. package/dist/react-native/main.d.ts +3 -0
  63. package/dist/react-native/main.js +8 -1
  64. package/dist/react-native/main.js.map +1 -1
  65. package/dist/web/PseudocodeTranspiler.d.ts +6 -0
  66. package/dist/web/PseudocodeTranspiler.js +115 -0
  67. package/dist/web/PseudocodeTranspiler.js.map +1 -0
  68. package/dist/web/RaftAttributeHandler.d.ts +1 -1
  69. package/dist/web/RaftAttributeHandler.js +108 -32
  70. package/dist/web/RaftAttributeHandler.js.map +1 -1
  71. package/dist/web/RaftChannelBLE.web.d.ts +4 -0
  72. package/dist/web/RaftChannelBLE.web.js +59 -21
  73. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  74. package/dist/web/RaftChannelSimulated.d.ts +1 -0
  75. package/dist/web/RaftChannelSimulated.js +9 -5
  76. package/dist/web/RaftChannelSimulated.js.map +1 -1
  77. package/dist/web/RaftChannelWebSocket.js +16 -1
  78. package/dist/web/RaftChannelWebSocket.js.map +1 -1
  79. package/dist/web/RaftConnector.d.ts +29 -1
  80. package/dist/web/RaftConnector.js +177 -11
  81. package/dist/web/RaftConnector.js.map +1 -1
  82. package/dist/web/RaftCustomAttrHandler.d.ts +2 -2
  83. package/dist/web/RaftCustomAttrHandler.js +32 -44
  84. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  85. package/dist/web/RaftDeviceInfo.d.ts +18 -0
  86. package/dist/web/RaftDeviceInfo.js +8 -0
  87. package/dist/web/RaftDeviceInfo.js.map +1 -1
  88. package/dist/web/RaftDeviceManager.d.ts +30 -3
  89. package/dist/web/RaftDeviceManager.js +618 -107
  90. package/dist/web/RaftDeviceManager.js.map +1 -1
  91. package/dist/web/RaftDeviceMgrIF.d.ts +11 -2
  92. package/dist/web/RaftDeviceStates.d.ts +27 -3
  93. package/dist/web/RaftDeviceStates.js +31 -6
  94. package/dist/web/RaftDeviceStates.js.map +1 -1
  95. package/dist/web/RaftFileHandler.d.ts +1 -1
  96. package/dist/web/RaftFileHandler.js +101 -34
  97. package/dist/web/RaftFileHandler.js.map +1 -1
  98. package/dist/web/RaftMicroPythonConsoleClient.d.ts +38 -0
  99. package/dist/web/RaftMicroPythonConsoleClient.js +45 -0
  100. package/dist/web/RaftMicroPythonConsoleClient.js.map +1 -0
  101. package/dist/web/RaftMsgHandler.d.ts +1 -1
  102. package/dist/web/RaftMsgHandler.js +6 -3
  103. package/dist/web/RaftMsgHandler.js.map +1 -1
  104. package/dist/web/RaftPublish.d.ts +2 -0
  105. package/dist/web/RaftPublish.js +81 -0
  106. package/dist/web/RaftPublish.js.map +1 -0
  107. package/dist/web/RaftStreamHandler.d.ts +11 -0
  108. package/dist/web/RaftStreamHandler.js +66 -0
  109. package/dist/web/RaftStreamHandler.js.map +1 -1
  110. package/dist/web/RaftStruct.d.ts +2 -2
  111. package/dist/web/RaftStruct.js +97 -26
  112. package/dist/web/RaftStruct.js.map +1 -1
  113. package/dist/web/RaftSystemType.d.ts +1 -0
  114. package/dist/web/RaftSystemUtils.d.ts +17 -1
  115. package/dist/web/RaftSystemUtils.js +51 -0
  116. package/dist/web/RaftSystemUtils.js.map +1 -1
  117. package/dist/web/RaftTimezone.d.ts +16 -0
  118. package/dist/web/RaftTimezone.js +153 -0
  119. package/dist/web/RaftTimezone.js.map +1 -0
  120. package/dist/web/RaftTypes.d.ts +46 -1
  121. package/dist/web/RaftTypes.js.map +1 -1
  122. package/dist/web/RaftUpdateManager.js +1 -1
  123. package/dist/web/RaftUpdateManager.js.map +1 -1
  124. package/dist/web/main.d.ts +3 -0
  125. package/dist/web/main.js +8 -1
  126. package/dist/web/main.js.map +1 -1
  127. package/examples/dashboard/package.json +2 -2
  128. package/examples/dashboard/src/DeviceActionsForm.tsx +177 -17
  129. package/examples/dashboard/src/DeviceLineChart.tsx +16 -3
  130. package/examples/dashboard/src/DevicePanel.tsx +92 -11
  131. package/examples/dashboard/src/DeviceSelectDialog.tsx +224 -0
  132. package/examples/dashboard/src/DeviceStatsPanel.tsx +76 -0
  133. package/examples/dashboard/src/DevicesPanel.tsx +11 -0
  134. package/examples/dashboard/src/LogConfigPanel.tsx +357 -0
  135. package/examples/dashboard/src/LogFilesPanel.tsx +200 -0
  136. package/examples/dashboard/src/LoggingPanel.tsx +264 -0
  137. package/examples/dashboard/src/Main.tsx +12 -2
  138. package/examples/dashboard/src/SettingsScreen.tsx +9 -4
  139. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +10 -3
  140. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +37 -3
  141. package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +10 -2
  142. package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +41 -7
  143. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +34 -3
  144. package/examples/dashboard/src/styles.css +766 -1
  145. package/notes/web-ble-reconnect-retry.md +69 -0
  146. package/package.json +10 -7
  147. package/src/PseudocodeTranspiler.test.ts +372 -0
  148. package/src/PseudocodeTranspiler.ts +127 -0
  149. package/src/RaftAttributeHandler.ts +152 -76
  150. package/src/RaftChannelBLE.web.ts +62 -20
  151. package/src/RaftChannelSimulated.ts +10 -5
  152. package/src/RaftChannelWebSocket.ts +16 -2
  153. package/src/RaftConnector.ts +204 -17
  154. package/src/RaftCustomAttrHandler.ts +35 -45
  155. package/src/RaftDeviceInfo.ts +27 -0
  156. package/src/RaftDeviceManager.test.ts +164 -0
  157. package/src/RaftDeviceManager.ts +705 -127
  158. package/src/RaftDeviceMgrIF.ts +13 -2
  159. package/src/RaftDeviceStates.ts +49 -8
  160. package/src/RaftFileHandler.ts +112 -39
  161. package/src/RaftMicroPythonConsoleClient.ts +78 -0
  162. package/src/RaftMsgHandler.ts +8 -4
  163. package/src/RaftPublish.ts +92 -0
  164. package/src/RaftStreamHandler.ts +84 -1
  165. package/src/RaftStruct.test.ts +229 -0
  166. package/src/RaftStruct.ts +101 -37
  167. package/src/RaftSystemType.ts +1 -0
  168. package/src/RaftSystemUtils.ts +59 -0
  169. package/src/RaftTimezone.ts +151 -0
  170. package/src/RaftTypes.ts +57 -1
  171. package/src/RaftUpdateManager.ts +1 -1
  172. package/src/main.ts +3 -0
@@ -0,0 +1,264 @@
1
+ import React, { useEffect, useState, useRef } from 'react';
2
+ import ConnManager from './ConnManager';
3
+ import { LogConfig } from './LogConfigPanel';
4
+ import { getHostPosixTZ } from '../../../src/RaftTimezone';
5
+ import './styles.css';
6
+
7
+ // Minimal query-value encoder: only encode characters that break query string parsing.
8
+ // Unlike encodeURIComponent (which encodes ~40 chars), this keeps JSON, colons, commas
9
+ // etc. as-is, significantly reducing message size for BLE transport.
10
+ function encodeQueryValue(s: string): string {
11
+ return s.replace(/%/g, '%25').replace(/&/g, '%26').replace(/=/g, '%3D');
12
+ }
13
+
14
+ const connManager = ConnManager.getInstance();
15
+
16
+ interface LogStatus {
17
+ isLogging: boolean;
18
+ fileName: string;
19
+ elapsedSecs: number;
20
+ bytesWritten: number;
21
+ samples: number;
22
+ flushCount: number;
23
+ bufferOverflows: number;
24
+ avgWriteMs: number;
25
+ maxWriteMs: number;
26
+ bytesPerSec: number;
27
+ }
28
+
29
+ const emptyStatus: LogStatus = {
30
+ isLogging: false,
31
+ fileName: '',
32
+ elapsedSecs: 0,
33
+ bytesWritten: 0,
34
+ samples: 0,
35
+ flushCount: 0,
36
+ bufferOverflows: 0,
37
+ avgWriteMs: 0,
38
+ maxWriteMs: 0,
39
+ bytesPerSec: 0,
40
+ };
41
+
42
+ interface LoggingPanelProps {
43
+ onLogStopped?: () => void;
44
+ pausePolling?: boolean;
45
+ logConfig?: LogConfig | null;
46
+ }
47
+
48
+ export default function LoggingPanel({ onLogStopped, pausePolling, logConfig }: LoggingPanelProps) {
49
+ const [status, setStatus] = useState<LogStatus>(emptyStatus);
50
+ const [label, setLabel] = useState('');
51
+ const [isBusy, setIsBusy] = useState(false);
52
+ const [lastError, setLastError] = useState('');
53
+ const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
54
+ const wasLoggingRef = useRef(false);
55
+
56
+ const fetchStatus = async () => {
57
+ if (!connManager.getConnector().isConnected()) return;
58
+ try {
59
+ const resp = await connManager.getConnector().sendRICRESTMsg(
60
+ 'datalog?action=status', {}
61
+ );
62
+ if (resp && typeof resp === 'object') {
63
+ const r = resp as any;
64
+ const flushLatency = r.flushLatency ?? {};
65
+ const nowLogging = r.active ?? false;
66
+ setStatus({
67
+ isLogging: nowLogging,
68
+ fileName: r.fileName ?? '',
69
+ elapsedSecs: (r.durationMs ?? 0) / 1000,
70
+ bytesWritten: r.totalBytesWritten ?? 0,
71
+ samples: r.samples ?? 0,
72
+ flushCount: r.flushCount ?? 0,
73
+ bufferOverflows: r.bufferOverflows ?? 0,
74
+ avgWriteMs: (flushLatency.avgUs ?? 0) / 1000,
75
+ maxWriteMs: (flushLatency.maxUs ?? 0) / 1000,
76
+ bytesPerSec: r.bytesPerSec ?? 0,
77
+ });
78
+ // Detect timed logging session that finished on its own
79
+ if (wasLoggingRef.current && !nowLogging) {
80
+ onLogStopped?.();
81
+ }
82
+ wasLoggingRef.current = nowLogging;
83
+ }
84
+ } catch (e) {
85
+ console.warn('Failed to fetch logging status', e);
86
+ }
87
+ };
88
+
89
+ // Poll status every 2 seconds (paused during file downloads)
90
+ useEffect(() => {
91
+ if (pausePolling) {
92
+ if (pollTimerRef.current) clearInterval(pollTimerRef.current);
93
+ pollTimerRef.current = null;
94
+ return;
95
+ }
96
+ fetchStatus();
97
+ pollTimerRef.current = setInterval(fetchStatus, 2000);
98
+ return () => {
99
+ if (pollTimerRef.current) clearInterval(pollTimerRef.current);
100
+ };
101
+ }, [pausePolling]);
102
+
103
+ const handleStart = async () => {
104
+ setIsBusy(true);
105
+ setLastError('');
106
+ try {
107
+ const labelParam = label.trim() ? `&label=${encodeQueryValue(label.trim())}` : '';
108
+ let configParam = '';
109
+ if (logConfig && logConfig.devices.length > 0) {
110
+ configParam = `&config=${encodeQueryValue(JSON.stringify(logConfig))}`;
111
+ }
112
+ // Include current UTC time so firmware can timestamp the log even without NTP
113
+ const utcParam = `&UTC=${encodeQueryValue(new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'))}`;
114
+ const posixTZ = getHostPosixTZ();
115
+ const tzParam = posixTZ ? `&tz=${encodeQueryValue(posixTZ)}` : '';
116
+ const resp = await connManager.getConnector().sendRICRESTMsg(
117
+ `datalog?action=start${labelParam}${configParam}${utcParam}${tzParam}`, {}
118
+ );
119
+ const r = resp as any;
120
+ if (r?.rslt !== 'ok') {
121
+ setLastError(r?.error || 'Start failed');
122
+ }
123
+ await fetchStatus();
124
+ } catch (e) {
125
+ setLastError('Failed to send start command');
126
+ }
127
+ setIsBusy(false);
128
+ };
129
+
130
+ const handleStop = async () => {
131
+ setIsBusy(true);
132
+ setLastError('');
133
+ try {
134
+ const resp = await connManager.getConnector().sendRICRESTMsg(
135
+ 'datalog?action=stop', {}
136
+ );
137
+ const r = resp as any;
138
+ if (r?.rslt !== 'ok') {
139
+ setLastError(r?.error || 'Stop failed');
140
+ }
141
+ await fetchStatus();
142
+ onLogStopped?.();
143
+ } catch (e) {
144
+ setLastError('Failed to send stop command');
145
+ }
146
+ setIsBusy(false);
147
+ };
148
+
149
+ const handleSimulate = async () => {
150
+ setIsBusy(true);
151
+ setLastError('');
152
+ try {
153
+ const resp = await connManager.getConnector().sendRICRESTMsg(
154
+ 'datalog?action=simulate', {}
155
+ );
156
+ const r = resp as any;
157
+ if (r?.rslt !== 'ok') {
158
+ setLastError(r?.error || 'Simulate failed');
159
+ }
160
+ await fetchStatus();
161
+ } catch (e) {
162
+ setLastError('Failed to send simulate command');
163
+ }
164
+ setIsBusy(false);
165
+ };
166
+
167
+ const formatBytes = (bytes: number): string => {
168
+ if (bytes < 1024) return `${bytes} B`;
169
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
170
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
171
+ };
172
+
173
+ const formatDuration = (secs: number): string => {
174
+ const h = Math.floor(secs / 3600);
175
+ const m = Math.floor((secs % 3600) / 60);
176
+ const s = Math.floor(secs % 60);
177
+ if (h > 0) return `${h}h ${m}m ${s}s`;
178
+ if (m > 0) return `${m}m ${s}s`;
179
+ return `${s}s`;
180
+ };
181
+
182
+ return (
183
+ <div className="info-box logging-panel">
184
+ <h3>Data Logging</h3>
185
+
186
+ {status.isLogging ? (
187
+ <>
188
+ <div className="logging-status-active">
189
+ <div className="logging-indicator" />
190
+ <span>Logging Active</span>
191
+ </div>
192
+ <div className="info">
193
+ <div className="info-line">
194
+ <div className="info-label">File:</div>
195
+ <div className="info-value">{status.fileName}</div>
196
+ </div>
197
+ <div className="info-line">
198
+ <div className="info-label">Duration:</div>
199
+ <div className="info-value">{formatDuration(status.elapsedSecs)}</div>
200
+ </div>
201
+ <div className="info-line">
202
+ <div className="info-label">Written:</div>
203
+ <div className="info-value">{formatBytes(status.bytesWritten)}</div>
204
+ </div>
205
+ <div className="info-line">
206
+ <div className="info-label">Writes:</div>
207
+ <div className="info-value">{status.flushCount} flushes, {status.samples} samples (overflows: {status.bufferOverflows})</div>
208
+ </div>
209
+ <div className="info-line">
210
+ <div className="info-label">Write time:</div>
211
+ <div className="info-value">avg {status.avgWriteMs.toFixed(1)}ms, max {status.maxWriteMs.toFixed(1)}ms</div>
212
+ </div>
213
+ {status.bytesPerSec > 0 && (
214
+ <div className="info-line">
215
+ <div className="info-label">Rate:</div>
216
+ <div className="info-value">{formatBytes(status.bytesPerSec)}/s</div>
217
+ </div>
218
+ )}
219
+ </div>
220
+ <button
221
+ className="action-button logging-stop-button"
222
+ onClick={handleStop}
223
+ disabled={isBusy}
224
+ >
225
+ Stop Logging
226
+ </button>
227
+ </>
228
+ ) : (
229
+ <>
230
+ <div className="logging-start-controls">
231
+ <input
232
+ type="text"
233
+ className="logging-label-input"
234
+ placeholder="Session label (optional)"
235
+ value={label}
236
+ onChange={(e) => setLabel(e.target.value)}
237
+ onKeyDown={(e) => { if (e.key === 'Enter') handleStart(); }}
238
+ />
239
+ <div className="logging-button-row">
240
+ <button
241
+ className="action-button"
242
+ onClick={handleStart}
243
+ disabled={isBusy || (logConfig !== undefined && (!logConfig || logConfig.devices.length === 0))}
244
+ >
245
+ Start Logging
246
+ </button>
247
+ <button
248
+ className="action-button logging-simulate-button"
249
+ onClick={handleSimulate}
250
+ disabled={isBusy}
251
+ >
252
+ Simulate
253
+ </button>
254
+ </div>
255
+ </div>
256
+ </>
257
+ )}
258
+
259
+ {lastError && (
260
+ <div className="logging-error">{lastError}</div>
261
+ )}
262
+ </div>
263
+ );
264
+ }
@@ -11,6 +11,10 @@ import {
11
11
  import StatusPanel from './StatusPanel';
12
12
  import DevicesPanel from './DevicesPanel';
13
13
  import CommandPanel from './CommandPanel';
14
+ import LoggingPanel from './LoggingPanel';
15
+ import LogFilesPanel from './LogFilesPanel';
16
+ import LogConfigPanel, { LogConfig } from './LogConfigPanel';
17
+
14
18
  import LatencyTestPanel from './LatencyTestPanel';
15
19
  import SettingsManager from './SettingsManager';
16
20
 
@@ -34,6 +38,9 @@ export default function Main() {
34
38
  const [ipAddress, setIpAddress] = useState<string>(
35
39
  localStorage.getItem('lastIpAddress') || ''
36
40
  );
41
+ const [fileRefreshTrigger, setFileRefreshTrigger] = useState(0);
42
+ const [downloadActive, setDownloadActive] = useState(false);
43
+ const [logConfig, setLogConfig] = useState<LogConfig | null>(null);
37
44
 
38
45
  const [serialNo, setSerialNo] = useState<string>('');
39
46
 
@@ -148,10 +155,10 @@ export default function Main() {
148
155
  {connectionStatus === RaftConnEvent.CONN_CONNECTED ? (
149
156
  <>
150
157
  <div className="connected-panel">
151
- <div className="info-boxes">
158
+ <div className="info-boxes connection-info">
152
159
  <div className="info-box">
153
160
  <div className="conn-indication">
154
- <h3>Connected</h3>
161
+ <h3>Connected via {connManager.getConnector().getConnMethod() || 'Unknown'}</h3>
155
162
  </div>
156
163
  <div>
157
164
  <button
@@ -169,6 +176,9 @@ export default function Main() {
169
176
  <StatusPanel />
170
177
  {latencyTestEnabled && <LatencyTestPanel />}
171
178
  <CommandPanel />
179
+ <LogConfigPanel onConfigChanged={setLogConfig} disabled={false} />
180
+ <LoggingPanel onLogStopped={() => setFileRefreshTrigger(n => n + 1)} pausePolling={downloadActive} logConfig={logConfig} />
181
+ <LogFilesPanel refreshTrigger={fileRefreshTrigger} onDownloadActiveChange={setDownloadActive} />
172
182
  </div>
173
183
  <DevicesPanel />
174
184
  </>
@@ -24,9 +24,14 @@ const SettingsScreen = ({ onBack }: { onBack: () => void }) => {
24
24
  settingsManager.getSetting('latencyAttributeName') || 'amb0'
25
25
  );
26
26
  const [latencyChangeThreshold, setLatencyChangeThreshold] = useState<number>(
27
- settingsManager.getSetting('latencyChangeThreshold') || 100
27
+ settingsManager.getSetting('latencyChangeThreshold') ?? 100
28
28
  );
29
29
 
30
+ const parseIntOrDefault = (value: string, fallback: number): number => {
31
+ const parsed = Number.parseInt(value, 10);
32
+ return Number.isNaN(parsed) ? fallback : parsed;
33
+ };
34
+
30
35
  const handleSaveAndReturn = () => {
31
36
  // Save settings to SettingsManager
32
37
  settingsManager.setSetting('latencyTest', latencyTest);
@@ -83,7 +88,7 @@ const SettingsScreen = ({ onBack }: { onBack: () => void }) => {
83
88
  value={maxChartDataPoints}
84
89
  onChange={(e) =>
85
90
  setMaxChartDataPoints(
86
- Math.min(parseInt(e.target.value, 10) || 1, 500)
91
+ Math.min(parseIntOrDefault(e.target.value, 1), 500)
87
92
  )
88
93
  }
89
94
  style={{ width: '50px', marginLeft: '10px' }}
@@ -101,7 +106,7 @@ const SettingsScreen = ({ onBack }: { onBack: () => void }) => {
101
106
  value={maxDatapointsToStore}
102
107
  onChange={(e) =>
103
108
  setMaxDatapointsToStore(
104
- Math.min(parseInt(e.target.value, 10) || 1, 100000)
109
+ Math.min(parseIntOrDefault(e.target.value, 1), 100000)
105
110
  )
106
111
  }
107
112
  style={{ width: '50px', marginLeft: '10px' }}
@@ -141,7 +146,7 @@ const SettingsScreen = ({ onBack }: { onBack: () => void }) => {
141
146
  min="1"
142
147
  value={latencyChangeThreshold}
143
148
  onChange={(e) =>
144
- setLatencyChangeThreshold(parseInt(e.target.value, 10) || 1)
149
+ setLatencyChangeThreshold(parseIntOrDefault(e.target.value, 1))
145
150
  }
146
151
  style={{ width: '60px', marginLeft: '10px' }}
147
152
  />
@@ -2,7 +2,6 @@ import { time } from "console";
2
2
  import RaftLog from "../../../../src/RaftLog";
3
3
  import { DeviceManager } from "../../../../src/RaftDeviceManager";
4
4
  import RaftUtils from "../../../../src/RaftUtils";
5
- import { TextDecoder } from 'text-encoding';
6
5
 
7
6
  // export interface IMUStateInfo {
8
7
  // gx: number;
@@ -47,6 +46,14 @@ export class CogStateInfo {
47
46
  return this._deviceManager;
48
47
  }
49
48
 
49
+ async handleBinaryPayload(rxMsg: Uint8Array): Promise<void> {
50
+ await this._deviceManager.handleClientMsgBinary(rxMsg);
51
+ }
52
+
53
+ async handleJsonPayload(jsonString: string): Promise<void> {
54
+ await this._deviceManager.handleClientMsgJson(jsonString);
55
+ }
56
+
50
57
  updateFromMsg(rxMsg: Uint8Array, frameTimeMs: number, isBinary: boolean): Array<string> {
51
58
 
52
59
  // Debug
@@ -54,14 +61,14 @@ export class CogStateInfo {
54
61
 
55
62
  if (isBinary) {
56
63
  // console.log(`CogStateInfo: updateFromMsg: ${RaftUtils.bufferToHex(rxMsg)}`);
57
- this._deviceManager.handleClientMsgBinary(rxMsg);
64
+ this.handleBinaryPayload(rxMsg);
58
65
  } else {
59
66
  // Convert Uint8Array to string
60
67
  const decoder = new TextDecoder('utf-8');
61
68
  const jsonString = decoder.decode(rxMsg.slice(2));
62
69
 
63
70
  // Handle using device manager
64
- this._deviceManager.handleClientMsgJson(jsonString);
71
+ this.handleJsonPayload(jsonString);
65
72
  }
66
73
 
67
74
  // // Debug
@@ -1,5 +1,5 @@
1
1
  import { RaftSubscribeForUpdatesCBType, RaftSystemType } from "../../../../src/RaftSystemType";
2
- import { RaftEventFn, RaftLog, RaftOKFail, RaftPublishEvent, RaftPublishEventNames, RaftSystemUtils } from "../../../../src/main";
2
+ import { inspectPublishFrame, RaftEventFn, RaftLog, RaftPublishEvent, RaftPublishEventNames, RaftSubscriptionUpdateResponse, RaftSystemUtils } from "../../../../src/main";
3
3
  import { CogStateInfo } from "./CogStateInfo";
4
4
  import { DeviceManager } from "../../../../src/RaftDeviceManager";
5
5
 
@@ -53,10 +53,16 @@ export default class SystemTypeCog implements RaftSystemType {
53
53
  ']}';
54
54
 
55
55
  const msgHandler = systemUtils.getMsgHandler();
56
- const ricResp = await msgHandler.sendRICRESTCmdFrame<RaftOKFail>(
56
+ const ricResp = await msgHandler.sendRICRESTCmdFrame<RaftSubscriptionUpdateResponse>(
57
57
  enable ? subscribeEnable : subscribeDisable
58
58
  );
59
59
 
60
+ // Cache topic index->name map from response, then refresh from pubtopics endpoint when enabling
61
+ systemUtils.updatePublishTopicMapFromSubscriptionResponse(ricResp);
62
+ if (enable) {
63
+ await systemUtils.refreshPublishTopicMap();
64
+ }
65
+
60
66
  // Debug
61
67
  RaftLog.debug(`subscribe enable/disable returned ${JSON.stringify(ricResp)}`);
62
68
  } catch (error: unknown) {
@@ -72,13 +78,41 @@ export default class SystemTypeCog implements RaftSystemType {
72
78
 
73
79
  // RICLog.debug(`rxOtherMsgType payload ${RaftUtils.bufferToHex(payload)}`);
74
80
  RaftLog.verbose(`rxOtherMsgType payloadLen ${payload.length}`);
75
- const topicIDs = this._stateInfo.updateFromMsg(payload, frameTimeMs, SUBSCRIBE_BINARY_MSGS);
81
+
82
+ const frameMeta = inspectPublishFrame(payload, (idx) => this._systemUtils?.getPublishTopicName(idx));
83
+ let handledByDeviceManager = false;
84
+
85
+ if (frameMeta.frameType === "binary") {
86
+ if (frameMeta.binaryHasEnvelope) {
87
+ if (frameMeta.topicName === "devbin") {
88
+ this._stateInfo.handleBinaryPayload(payload);
89
+ handledByDeviceManager = true;
90
+ }
91
+ } else if (SUBSCRIBE_BINARY_MSGS) {
92
+ this._stateInfo.handleBinaryPayload(payload);
93
+ handledByDeviceManager = true;
94
+ }
95
+ } else if (frameMeta.frameType === "json") {
96
+ if (frameMeta.topicName === "devjson" || frameMeta.topicName === undefined) {
97
+ if (frameMeta.jsonString !== undefined) {
98
+ this._stateInfo.handleJsonPayload(frameMeta.jsonString);
99
+ handledByDeviceManager = true;
100
+ }
101
+ }
102
+ }
103
+
104
+ const topicIDs = frameMeta.topicIndex !== undefined ? [frameMeta.topicIndex.toString()] : [];
76
105
 
77
106
  // Call event handler if registered
78
107
  if (this._onEvent) {
79
108
  this._onEvent("pub", RaftPublishEvent.PUBLISH_EVENT_DATA, RaftPublishEventNames[RaftPublishEvent.PUBLISH_EVENT_DATA],
80
109
  {
81
110
  topicIDs: topicIDs,
111
+ topicName: frameMeta.topicName,
112
+ topicIndex: frameMeta.topicIndex,
113
+ topicVersion: frameMeta.version,
114
+ frameType: frameMeta.frameType,
115
+ handledByDeviceManager,
82
116
  payload: payload,
83
117
  frameTimeMs: frameTimeMs,
84
118
  isBinary: SUBSCRIBE_BINARY_MSGS
@@ -7,6 +7,14 @@ export class StateInfoGeneric {
7
7
  public constructor(private _deviceManager: DeviceManager) {
8
8
  }
9
9
 
10
+ async handleBinaryPayload(rxMsg: Uint8Array): Promise<void> {
11
+ await this._deviceManager.handleClientMsgBinary(rxMsg);
12
+ }
13
+
14
+ async handleJsonPayload(jsonString: string): Promise<void> {
15
+ await this._deviceManager.handleClientMsgJson(jsonString);
16
+ }
17
+
10
18
  updateFromMsg(rxMsg: Uint8Array, frameTimeMs: number, isBinary: boolean): Array<string> {
11
19
 
12
20
  // Debug
@@ -15,14 +23,14 @@ export class StateInfoGeneric {
15
23
  // Handle binary or JSON
16
24
  if (isBinary) {
17
25
  // Handle using device manager
18
- this._deviceManager.handleClientMsgBinary(rxMsg);
26
+ this.handleBinaryPayload(rxMsg);
19
27
  } else {
20
28
  // Convert Uint8Array to string
21
29
  const decoder = new TextDecoder('utf-8');
22
30
  const jsonString = decoder.decode(rxMsg.slice(2));
23
31
 
24
32
  // Handle using device manager
25
- this._deviceManager.handleClientMsgJson(jsonString);
33
+ this.handleJsonPayload(jsonString);
26
34
  }
27
35
  return [];
28
36
 
@@ -1,9 +1,9 @@
1
1
  import { RaftSubscribeForUpdatesCBType, RaftSystemType } from "../../../../src/RaftSystemType";
2
- import { RaftEventFn, RaftLog, RaftOKFail, RaftPublishEvent, RaftPublishEventNames, RaftSystemUtils } from "../../../../src/main";
2
+ import { inspectPublishFrame, RaftEventFn, RaftLog, RaftPublishEvent, RaftPublishEventNames, RaftSubscriptionUpdateResponse, RaftSystemUtils } from "../../../../src/main";
3
3
  import { StateInfoGeneric } from "./StateInfoGeneric";
4
4
  import { DeviceManager } from "../../../../src/RaftDeviceManager";
5
5
 
6
- const SUBSCRIBE_BINARY_MSGS = false;
6
+ const SUBSCRIBE_BINARY_MSGS = true;
7
7
 
8
8
  export default class SystemTypeGeneric implements RaftSystemType {
9
9
  nameForDialogs = "Generic System";
@@ -39,24 +39,30 @@ export default class SystemTypeGeneric implements RaftSystemType {
39
39
 
40
40
  // Subscribe for updates
41
41
  subscribeForUpdates: RaftSubscribeForUpdatesCBType | null = async (systemUtils: RaftSystemUtils, enable: boolean) => {
42
- // Subscription rate
42
+ // Subscription rate — must be high enough to match max polling rate
43
43
  const subscribeRateHz = 0.1;
44
44
  try {
45
45
  const topic = SUBSCRIBE_BINARY_MSGS ? "devbin" : "devjson";
46
46
  const subscribeDisable = '{"cmdName":"subscription","action":"update",' +
47
47
  '"pubRecs":[' +
48
- `{"name":"${topic}","rateHz":0,}` +
48
+ `{"name":"${topic}","rateHz":0}` +
49
49
  ']}';
50
50
  const subscribeEnable = '{"cmdName":"subscription","action":"update",' +
51
51
  '"pubRecs":[' +
52
- `{"name":"${topic}","trigger":"timeorchange","rateHz":${subscribeRateHz.toString()}}` +
52
+ `{"name":"${topic}","trigger":"timeorchange","rateHz":${subscribeRateHz.toString()},"minMs":10}` +
53
53
  ']}';
54
54
 
55
55
  const msgHandler = systemUtils.getMsgHandler();
56
- const ricResp = await msgHandler.sendRICRESTCmdFrame<RaftOKFail>(
56
+ const ricResp = await msgHandler.sendRICRESTCmdFrame<RaftSubscriptionUpdateResponse>(
57
57
  enable ? subscribeEnable : subscribeDisable
58
58
  );
59
59
 
60
+ // Cache topic index->name map from response, then refresh from pubtopics endpoint when enabling
61
+ systemUtils.updatePublishTopicMapFromSubscriptionResponse(ricResp);
62
+ if (enable) {
63
+ await systemUtils.refreshPublishTopicMap();
64
+ }
65
+
60
66
  // Debug
61
67
  RaftLog.debug(`subscribe enable/disable returned ${JSON.stringify(ricResp)}`);
62
68
  } catch (error: unknown) {
@@ -72,13 +78,41 @@ export default class SystemTypeGeneric implements RaftSystemType {
72
78
 
73
79
  // RICLog.debug(`rxOtherMsgType payload ${RaftUtils.bufferToHex(payload)}`);
74
80
  RaftLog.verbose(`rxOtherMsgType payloadLen ${payload.length}`);
75
- const topicIDs = this._stateInfo.updateFromMsg(payload, frameTimeMs, SUBSCRIBE_BINARY_MSGS);
81
+
82
+ const frameMeta = inspectPublishFrame(payload, (idx) => this._systemUtils?.getPublishTopicName(idx));
83
+ let handledByDeviceManager = false;
84
+
85
+ if (frameMeta.frameType === "binary") {
86
+ if (frameMeta.binaryHasEnvelope) {
87
+ if (frameMeta.topicName === "devbin") {
88
+ this._stateInfo.handleBinaryPayload(payload);
89
+ handledByDeviceManager = true;
90
+ }
91
+ } else if (SUBSCRIBE_BINARY_MSGS) {
92
+ this._stateInfo.handleBinaryPayload(payload);
93
+ handledByDeviceManager = true;
94
+ }
95
+ } else if (frameMeta.frameType === "json") {
96
+ if (frameMeta.topicName === "devjson" || frameMeta.topicName === undefined) {
97
+ if (frameMeta.jsonString !== undefined) {
98
+ this._stateInfo.handleJsonPayload(frameMeta.jsonString);
99
+ handledByDeviceManager = true;
100
+ }
101
+ }
102
+ }
103
+
104
+ const topicIDs = frameMeta.topicIndex !== undefined ? [frameMeta.topicIndex.toString()] : [];
76
105
 
77
106
  // Call event handler if registered
78
107
  if (this._onEvent) {
79
108
  this._onEvent("pub", RaftPublishEvent.PUBLISH_EVENT_DATA, RaftPublishEventNames[RaftPublishEvent.PUBLISH_EVENT_DATA],
80
109
  {
81
110
  topicIDs: topicIDs,
111
+ topicName: frameMeta.topicName,
112
+ topicIndex: frameMeta.topicIndex,
113
+ topicVersion: frameMeta.version,
114
+ frameType: frameMeta.frameType,
115
+ handledByDeviceManager,
82
116
  payload: payload,
83
117
  frameTimeMs: frameTimeMs,
84
118
  isBinary: SUBSCRIBE_BINARY_MSGS
@@ -1,5 +1,6 @@
1
1
  import RaftDeviceMgrIF from "../../../../src/RaftDeviceMgrIF";
2
- import { DeviceAttributeState, DevicesState, DeviceState } from "../../../../src/RaftDeviceStates";
2
+ import { SampleRateResult } from "../../../../src/RaftDeviceInfo";
3
+ import { DeviceAttributeState, DevicesState, DeviceState, DeviceOnlineState } from '../../../../src/RaftDeviceStates';
3
4
  import { RICSERIAL_PAYLOAD_POS } from "../../../../src/RaftProtocolDefs";
4
5
  import RICAddOnManager from "./RICAddOnManager";
5
6
  import RICCommsStats from "./RICCommsStats";
@@ -45,12 +46,18 @@ export class RICStateInfo implements RaftDeviceMgrIF {
45
46
  deviceTimeline: {
46
47
  timestampsUs: [],
47
48
  lastReportTimestampUs: 0,
48
- reportTimestampOffsetUs: 0
49
+ reportTimestampOffsetUs: 0,
50
+ totalSamplesAdded: 0,
51
+ emaLastSampleTimeUs: 0,
52
+ emaIntervalUs: 0,
53
+ emaPrevPollTimeUs: 0,
54
+ emaCalibrated: false,
55
+ emaCalibrationPolls: 0
49
56
  },
50
57
  deviceAttributes: {},
51
58
  deviceIsNew: false,
52
59
  stateChanged: false,
53
- isOnline: false,
60
+ onlineState: DeviceOnlineState.Offline,
54
61
  deviceAddress: "",
55
62
  deviceType: "",
56
63
  busName: ""
@@ -85,6 +92,14 @@ export class RICStateInfo implements RaftDeviceMgrIF {
85
92
  // TODO - implement if RICStateInfo is to be used as a DeviceMgr
86
93
  }
87
94
 
95
+ addDeviceRemovedCallback(callback: (deviceKey: string, state: DeviceState) => void): void {
96
+ // TODO - implement if RICStateInfo is to be used as a DeviceMgr
97
+ }
98
+
99
+ removeDeviceRemovedCallback(callback: (deviceKey: string, state: DeviceState) => void): void {
100
+ // TODO - implement if RICStateInfo is to be used as a DeviceMgr
101
+ }
102
+
88
103
  sendAction(deviceKey: string, action: any, data: any): void {
89
104
  // TODO - implement if RICStateInfo is to be used as a DeviceMgr
90
105
  }
@@ -93,5 +108,21 @@ export class RICStateInfo implements RaftDeviceMgrIF {
93
108
  // TODO - implement if RICStateInfo is to be used as a DeviceMgr
94
109
  }
95
110
 
111
+ async setSampleRate(deviceKey: string, sampleRateHz: number, options?: {
112
+ numSamples?: number; intervalUs?: number; maxNumSamples?: number;
113
+ }): Promise<SampleRateResult> {
114
+ // TODO - implement if RICStateInfo is to be used as a DeviceMgr
115
+ return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: 0, intervalUs: 0, numSamples: 0, error: 'Not implemented' };
116
+ }
117
+
118
+ getDeviceStats(deviceKey: string): any {
119
+ // TODO - implement if RICStateInfo is to be used as a DeviceMgr
120
+ return {};
121
+ }
122
+
123
+ resetDeviceStats(deviceKey: string): void {
124
+ // TODO - implement if RICStateInfo is to be used as a DeviceMgr
125
+ }
126
+
96
127
  }
97
128