@robdobsn/raftjs 1.7.8 → 1.10.7

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 (224) hide show
  1. package/.editorconfig +14 -0
  2. package/.gitattributes +11 -0
  3. package/.nvmrc +1 -0
  4. package/TODO.md +1 -0
  5. package/dist/react-native/RaftAttributeHandler.d.ts +14 -0
  6. package/dist/react-native/RaftAttributeHandler.js +375 -0
  7. package/dist/react-native/RaftAttributeHandler.js.map +1 -0
  8. package/dist/react-native/RaftChannel.d.ts +20 -0
  9. package/dist/react-native/RaftChannel.js +12 -0
  10. package/dist/react-native/RaftChannel.js.map +1 -0
  11. package/dist/react-native/RaftChannelBLE.native.d.ts +95 -0
  12. package/dist/react-native/RaftChannelBLE.native.js +483 -0
  13. package/dist/react-native/RaftChannelBLE.native.js.map +1 -0
  14. package/dist/react-native/RaftChannelBLE.web.d.ts +40 -0
  15. package/dist/react-native/RaftChannelBLE.web.js +302 -0
  16. package/dist/react-native/RaftChannelBLE.web.js.map +1 -0
  17. package/dist/react-native/RaftChannelBLEFactory.d.ts +10 -0
  18. package/dist/react-native/RaftChannelBLEFactory.js +17 -0
  19. package/dist/react-native/RaftChannelBLEFactory.js.map +1 -0
  20. package/dist/react-native/RaftChannelBLEScanner.native.d.ts +18 -0
  21. package/dist/react-native/RaftChannelBLEScanner.native.js +138 -0
  22. package/dist/react-native/RaftChannelBLEScanner.native.js.map +1 -0
  23. package/dist/react-native/RaftChannelSimulated.d.ts +42 -0
  24. package/dist/react-native/RaftChannelSimulated.js +1000 -0
  25. package/dist/react-native/RaftChannelSimulated.js.map +1 -0
  26. package/dist/react-native/RaftChannelWebSerial.d.ts +39 -0
  27. package/dist/react-native/RaftChannelWebSerial.js +329 -0
  28. package/dist/react-native/RaftChannelWebSerial.js.map +1 -0
  29. package/dist/react-native/RaftChannelWebSocket.d.ts +30 -0
  30. package/dist/react-native/RaftChannelWebSocket.js +222 -0
  31. package/dist/react-native/RaftChannelWebSocket.js.map +1 -0
  32. package/dist/react-native/RaftCommsStats.d.ts +39 -0
  33. package/dist/react-native/RaftCommsStats.js +128 -0
  34. package/dist/react-native/RaftCommsStats.js.map +1 -0
  35. package/dist/react-native/RaftConnEvents.d.ts +39 -0
  36. package/dist/react-native/RaftConnEvents.js +54 -0
  37. package/dist/react-native/RaftConnEvents.js.map +1 -0
  38. package/dist/react-native/RaftConnector.d.ts +248 -0
  39. package/dist/react-native/RaftConnector.js +658 -0
  40. package/dist/react-native/RaftConnector.js.map +1 -0
  41. package/dist/react-native/RaftCustomAttrHandler.d.ts +6 -0
  42. package/dist/react-native/RaftCustomAttrHandler.js +93 -0
  43. package/dist/react-native/RaftCustomAttrHandler.js.map +1 -0
  44. package/dist/react-native/RaftDeviceInfo.d.ts +71 -0
  45. package/dist/react-native/RaftDeviceInfo.js +50 -0
  46. package/dist/react-native/RaftDeviceInfo.js.map +1 -0
  47. package/dist/react-native/RaftDeviceManager.d.ts +61 -0
  48. package/dist/react-native/RaftDeviceManager.js +665 -0
  49. package/dist/react-native/RaftDeviceManager.js.map +1 -0
  50. package/dist/react-native/RaftDeviceMgrIF.d.ts +15 -0
  51. package/dist/react-native/RaftDeviceMgrIF.js +11 -0
  52. package/dist/react-native/RaftDeviceMgrIF.js.map +1 -0
  53. package/dist/react-native/RaftDeviceMsg.d.ts +9 -0
  54. package/dist/react-native/RaftDeviceMsg.js +11 -0
  55. package/dist/react-native/RaftDeviceMsg.js.map +1 -0
  56. package/dist/react-native/RaftDeviceStates.d.ts +37 -0
  57. package/dist/react-native/RaftDeviceStates.js +60 -0
  58. package/dist/react-native/RaftDeviceStates.js.map +1 -0
  59. package/dist/react-native/RaftFileHandler.d.ts +52 -0
  60. package/dist/react-native/RaftFileHandler.js +502 -0
  61. package/dist/react-native/RaftFileHandler.js.map +1 -0
  62. package/dist/react-native/RaftLog.d.ts +22 -0
  63. package/dist/react-native/RaftLog.js +63 -0
  64. package/dist/react-native/RaftLog.js.map +1 -0
  65. package/dist/react-native/RaftMiniHDLC.d.ts +18 -0
  66. package/dist/react-native/RaftMiniHDLC.js +383 -0
  67. package/dist/react-native/RaftMiniHDLC.js.map +1 -0
  68. package/dist/react-native/RaftMsgHandler.d.ts +62 -0
  69. package/dist/react-native/RaftMsgHandler.js +511 -0
  70. package/dist/react-native/RaftMsgHandler.js.map +1 -0
  71. package/dist/react-native/RaftMsgTrackInfo.d.ts +17 -0
  72. package/dist/react-native/RaftMsgTrackInfo.js +42 -0
  73. package/dist/react-native/RaftMsgTrackInfo.js.map +1 -0
  74. package/dist/react-native/RaftProtocolDefs.d.ts +30 -0
  75. package/dist/react-native/RaftProtocolDefs.js +48 -0
  76. package/dist/react-native/RaftProtocolDefs.js.map +1 -0
  77. package/dist/react-native/RaftStreamHandler.d.ts +38 -0
  78. package/dist/react-native/RaftStreamHandler.js +258 -0
  79. package/dist/react-native/RaftStreamHandler.js.map +1 -0
  80. package/dist/react-native/RaftStruct.d.ts +3 -0
  81. package/dist/react-native/RaftStruct.js +258 -0
  82. package/dist/react-native/RaftStruct.js.map +1 -0
  83. package/dist/react-native/RaftSysTypeManager.d.ts +16 -0
  84. package/dist/react-native/RaftSysTypeManager.js +78 -0
  85. package/dist/react-native/RaftSysTypeManager.js.map +1 -0
  86. package/dist/react-native/RaftSystemType.d.ts +30 -0
  87. package/dist/react-native/RaftSystemType.js +3 -0
  88. package/dist/react-native/RaftSystemType.js.map +1 -0
  89. package/dist/react-native/RaftSystemUtils.d.ts +136 -0
  90. package/dist/react-native/RaftSystemUtils.js +412 -0
  91. package/dist/react-native/RaftSystemUtils.js.map +1 -0
  92. package/dist/react-native/RaftTypes.d.ts +195 -0
  93. package/dist/react-native/RaftTypes.js +153 -0
  94. package/dist/react-native/RaftTypes.js.map +1 -0
  95. package/dist/react-native/RaftUpdateEvents.d.ts +33 -0
  96. package/dist/react-native/RaftUpdateEvents.js +46 -0
  97. package/dist/react-native/RaftUpdateEvents.js.map +1 -0
  98. package/dist/react-native/RaftUpdateManager.d.ts +61 -0
  99. package/dist/react-native/RaftUpdateManager.js +621 -0
  100. package/dist/react-native/RaftUpdateManager.js.map +1 -0
  101. package/dist/react-native/RaftUtils.d.ts +128 -0
  102. package/dist/react-native/RaftUtils.js +487 -0
  103. package/dist/react-native/RaftUtils.js.map +1 -0
  104. package/dist/react-native/RaftWifiTypes.d.ts +23 -0
  105. package/dist/react-native/RaftWifiTypes.js +43 -0
  106. package/dist/react-native/RaftWifiTypes.js.map +1 -0
  107. package/dist/react-native/main.d.ts +26 -0
  108. package/dist/react-native/main.js +51 -0
  109. package/dist/react-native/main.js.map +1 -0
  110. package/dist/web/RaftAttributeHandler.js +9 -6
  111. package/dist/web/RaftAttributeHandler.js.map +1 -1
  112. package/dist/web/RaftChannelBLE.web.js +8 -6
  113. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  114. package/dist/web/RaftChannelSimulated.d.ts +10 -0
  115. package/dist/web/RaftChannelSimulated.js +662 -80
  116. package/dist/web/RaftChannelSimulated.js.map +1 -1
  117. package/dist/web/RaftChannelWebSerial.js +2 -2
  118. package/dist/web/RaftChannelWebSerial.js.map +1 -1
  119. package/dist/web/RaftChannelWebSocket.js +16 -1
  120. package/dist/web/RaftChannelWebSocket.js.map +1 -1
  121. package/dist/web/RaftConnector.d.ts +2 -0
  122. package/dist/web/RaftConnector.js +38 -15
  123. package/dist/web/RaftConnector.js.map +1 -1
  124. package/dist/web/RaftCustomAttrHandler.d.ts +2 -0
  125. package/dist/web/RaftCustomAttrHandler.js +54 -26
  126. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  127. package/dist/web/RaftDeviceInfo.d.ts +3 -1
  128. package/dist/web/RaftDeviceInfo.js +17 -3
  129. package/dist/web/RaftDeviceInfo.js.map +1 -1
  130. package/dist/web/RaftDeviceManager.d.ts +22 -1
  131. package/dist/web/RaftDeviceManager.js +210 -44
  132. package/dist/web/RaftDeviceManager.js.map +1 -1
  133. package/dist/web/RaftDeviceStates.d.ts +1 -1
  134. package/dist/web/RaftDeviceStates.js +2 -2
  135. package/dist/web/RaftDeviceStates.js.map +1 -1
  136. package/dist/web/RaftMsgHandler.js.map +1 -1
  137. package/dist/web/RaftStreamHandler.js +2 -1
  138. package/dist/web/RaftStreamHandler.js.map +1 -1
  139. package/dist/web/RaftStruct.js +197 -147
  140. package/dist/web/RaftStruct.js.map +1 -1
  141. package/dist/web/RaftUpdateManager.js +1 -1
  142. package/dist/web/RaftUpdateManager.js.map +1 -1
  143. package/dist/web/RaftUtils.d.ts +2 -0
  144. package/dist/web/RaftUtils.js +20 -0
  145. package/dist/web/RaftUtils.js.map +1 -1
  146. package/dist/web/main.d.ts +1 -0
  147. package/dist/web/main.js.map +1 -1
  148. package/eslint.config.mjs +33 -0
  149. package/examples/dashboard/package.json +36 -0
  150. package/examples/dashboard/src/CommandPanel.tsx +147 -0
  151. package/examples/dashboard/src/ConnManager.ts +166 -0
  152. package/examples/dashboard/src/DeviceActionsForm.tsx +133 -0
  153. package/examples/dashboard/src/DeviceAttrsForm.tsx +49 -0
  154. package/examples/dashboard/src/DeviceLineChart.tsx +163 -0
  155. package/examples/dashboard/src/DevicePanel.tsx +171 -0
  156. package/examples/dashboard/src/DevicesPanel.tsx +58 -0
  157. package/examples/dashboard/src/DispLedGrid.tsx +110 -0
  158. package/examples/dashboard/src/DispOneLed.tsx +20 -0
  159. package/examples/dashboard/src/LatencyTest.ts +130 -0
  160. package/examples/dashboard/src/LatencyTestPanel.tsx +92 -0
  161. package/examples/dashboard/src/Main.tsx +234 -0
  162. package/examples/dashboard/src/SettingsManager.ts +67 -0
  163. package/examples/dashboard/src/SettingsScreen.tsx +174 -0
  164. package/examples/dashboard/src/StatusPanel.tsx +71 -0
  165. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +162 -0
  166. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +91 -0
  167. package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +30 -0
  168. package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +91 -0
  169. package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
  170. package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
  171. package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
  172. package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
  173. package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
  174. package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
  175. package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
  176. package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
  177. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +97 -0
  178. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
  179. package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
  180. package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +119 -0
  181. package/examples/dashboard/src/index.html +15 -0
  182. package/examples/dashboard/src/index.tsx +13 -0
  183. package/examples/dashboard/src/styles.css +408 -0
  184. package/examples/dashboard/tsconfig.json +18 -0
  185. package/jest.config.js +11 -0
  186. package/package.json +4 -7
  187. package/src/RaftAttributeHandler.ts +450 -0
  188. package/src/RaftChannel.ts +32 -0
  189. package/src/RaftChannelBLE.native.ts +617 -0
  190. package/src/RaftChannelBLE.web.ts +374 -0
  191. package/src/RaftChannelBLEFactory.ts +13 -0
  192. package/src/RaftChannelBLEScanner.native.ts +184 -0
  193. package/src/RaftChannelSimulated.ts +1176 -0
  194. package/src/RaftChannelWebSerial.ts +420 -0
  195. package/src/RaftChannelWebSocket.ts +272 -0
  196. package/src/RaftCommsStats.ts +142 -0
  197. package/src/RaftConnEvents.ts +58 -0
  198. package/src/RaftConnector.ts +785 -0
  199. package/src/RaftCustomAttrHandler.ts +117 -0
  200. package/src/RaftDeviceInfo.ts +125 -0
  201. package/src/RaftDeviceManager.ts +844 -0
  202. package/src/RaftDeviceMgrIF.ts +33 -0
  203. package/src/RaftDeviceMsg.ts +20 -0
  204. package/src/RaftDeviceStates.ts +92 -0
  205. package/src/RaftFileHandler.ts +668 -0
  206. package/src/RaftLog.ts +70 -0
  207. package/src/RaftMiniHDLC.ts +396 -0
  208. package/src/RaftMsgHandler.ts +812 -0
  209. package/src/RaftMsgTrackInfo.ts +51 -0
  210. package/src/RaftProtocolDefs.ts +46 -0
  211. package/src/RaftStreamHandler.ts +329 -0
  212. package/src/RaftStruct.ts +282 -0
  213. package/src/RaftSysTypeManager.ts +87 -0
  214. package/src/RaftSystemType.ts +34 -0
  215. package/src/RaftSystemUtils.ts +489 -0
  216. package/src/RaftTypes.ts +279 -0
  217. package/src/RaftUpdateEvents.ts +48 -0
  218. package/src/RaftUpdateManager.ts +781 -0
  219. package/src/RaftUtils.ts +514 -0
  220. package/src/RaftWifiTypes.ts +36 -0
  221. package/src/main.ts +39 -0
  222. package/testdata/TestDeviceTypeRecs.json +492 -0
  223. package/tsconfig.json +30 -0
  224. package/tsconfig.react-native.json +29 -0
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ interface LEDProps {
4
+ color: string;
5
+ onClick: () => void;
6
+ }
7
+
8
+ const DispOneLED: React.FC<LEDProps> = ({ color, onClick }) => {
9
+ return (
10
+ <div style={{
11
+ width: '20px',
12
+ height: '20px',
13
+ backgroundColor: color,
14
+ margin: '2px',
15
+ cursor: 'pointer'
16
+ }} onClick={onClick} />
17
+ );
18
+ };
19
+
20
+ export default DispOneLED;
@@ -0,0 +1,130 @@
1
+ import { mean, standardDeviation } from 'simple-statistics';
2
+ import { DeviceAttributeState } from '../../../src/RaftDeviceStates';
3
+ import SettingsManager from './SettingsManager';
4
+
5
+ class LatencyTest {
6
+ private static instance: LatencyTest;
7
+
8
+ private lastWhiteTime: number | null = null;
9
+ private lastBlackTime: number | null = null;
10
+ private lastAttrValue: number | null = null;
11
+
12
+ private latencyRecords: { type: 'white' | 'black'; latency: number }[] = [];
13
+ private latencyWindowSize: number = 10;
14
+ private sampleCounter: number = 0;
15
+
16
+ // Singleton access
17
+ static getInstance(): LatencyTest {
18
+ if (!LatencyTest.instance) {
19
+ LatencyTest.instance = new LatencyTest();
20
+ }
21
+ return LatencyTest.instance;
22
+ }
23
+
24
+ // Get abruptChangeThreshold
25
+ private get abruptChangeThreshold(): number {
26
+ const settingsManager = SettingsManager.getInstance();
27
+ return settingsManager.getSetting('latencyChangeThreshold') || 50;
28
+ }
29
+
30
+ // Record color change time
31
+ recordColorChange(color: 'white' | 'black', timestamp: number): void {
32
+ if (color === 'white') {
33
+ this.lastWhiteTime = timestamp;
34
+ this.lastBlackTime = null;
35
+ } else if (color === 'black') {
36
+ this.lastBlackTime = timestamp;
37
+ this.lastWhiteTime = null;
38
+ }
39
+ // console.log(`Color change recorded: ${color} at ${timestamp}`);
40
+ }
41
+
42
+ // Process values
43
+ processAttrValues(attribute: DeviceAttributeState, timestamp: number): void {
44
+ for (let i = 0; i < attribute.numNewValues; i++) {
45
+ const newValue = attribute.values[attribute.values.length - attribute.numNewValues + i];
46
+ // console.log(`New value: ${newValue} prev value: ${this.lastAttrValue}`);
47
+ if (this.lastAttrValue === null) {
48
+ this.lastAttrValue = newValue;
49
+ return;
50
+ }
51
+ this.sampleCounter++;
52
+
53
+ // Calculate change and update last value
54
+ const change = newValue - this.lastAttrValue;
55
+ this.lastAttrValue = newValue;
56
+
57
+ // Check size of change - needs to be above threshold
58
+ if (Math.abs(change) < this.abruptChangeThreshold) {
59
+ continue;
60
+ }
61
+
62
+ // console.log(`[${new Date().toISOString()}] new ${newValue} chg ${Math.abs(change)} >thresh ${Math.abs(change) >= this.abruptChangeThreshold} ${this.sampleCounter}`);
63
+
64
+ // Skip invalid changes (in the wrong direction) - also skips repeated values
65
+ // since lastWhiteTime and lastBlackTime are cleared after a change
66
+ if ((change >= 0 && this.lastWhiteTime === null) || (change < 0 && this.lastBlackTime === null)) {
67
+ // console.log(`Invalid change detected: ${change} lastWhite ${this.lastWhiteTime} lastBlack ${this.lastBlackTime}`);
68
+ continue;
69
+ }
70
+
71
+ // console.log(
72
+ // `[${new Date().toISOString()}] ${newValue} ${this.lastAttrValue} ${change} ${Math.abs(change)} ${this.abruptChangeThreshold} ${Math.abs(change) >= this.abruptChangeThreshold} ${this.sampleCounter} ${this.lastTriggerIndex}`
73
+ // );
74
+
75
+ // console.log(`Sample counter = ${this.sampleCounter} Last trigger index = ${this.lastTriggerIndex} Diff = ${this.sampleCounter - this.lastTriggerIndex}`);
76
+
77
+ // Detect abrupt change with threshold
78
+ if (Math.abs(change) >= this.abruptChangeThreshold) {
79
+ const eventType = change > 0 ? 'white' : 'black';
80
+ const relatedTime =
81
+ eventType === 'white' ? this.lastWhiteTime : this.lastBlackTime;
82
+
83
+ if (relatedTime !== null) {
84
+ const latency = timestamp - relatedTime;
85
+ this.latencyRecords.push({ type: eventType, latency });
86
+ if (this.latencyRecords.length > this.latencyWindowSize) {
87
+ this.latencyRecords.shift();
88
+ }
89
+
90
+ // console.log(
91
+ // `[${new Date().toISOString()}] Triggered: ${eventType} change ${change} detected. Last white time = ${this.lastWhiteTime} Last black time = ${this.lastBlackTime} Latency = ${latency} ms`
92
+ // );
93
+ }
94
+ }
95
+
96
+ // Clear timers of change
97
+ this.lastWhiteTime = null;
98
+ this.lastBlackTime = null;
99
+ }
100
+ }
101
+
102
+ // Get latency stats
103
+ getLatencyStats(): {
104
+ meanLatency: number | null;
105
+ stdDevLatency: number | null;
106
+ records: { type: 'white' | 'black'; latency: number }[];
107
+ } {
108
+ if (this.latencyRecords.length === 0) {
109
+ return {
110
+ meanLatency: null,
111
+ stdDevLatency: null,
112
+ records: [],
113
+ };
114
+ }
115
+
116
+ const latencies = this.latencyRecords.map((record) => record.latency);
117
+ const meanLatency = mean(latencies);
118
+ const stdDevLatency = standardDeviation(latencies);
119
+
120
+ // console.log(`Mean latency: ${meanLatency} ms, Std Dev: ${stdDevLatency} ms Num records: ${this.latencyRecords.length}`);
121
+
122
+ return {
123
+ meanLatency,
124
+ stdDevLatency,
125
+ records: this.latencyRecords,
126
+ };
127
+ }
128
+ }
129
+
130
+ export default LatencyTest;
@@ -0,0 +1,92 @@
1
+ // src/LatencyTestPanel.tsx
2
+ import React, { useEffect, useState } from 'react';
3
+ import './styles.css';
4
+ import ConnManager from "./ConnManager";
5
+ import LatencyTest from './LatencyTest';
6
+ import SettingsManager from './SettingsManager';
7
+ import { DeviceAttributeState, DeviceState } from '../../../src/RaftDeviceStates';
8
+
9
+ const connManager = ConnManager.getInstance();
10
+ const settingsManager = SettingsManager.getInstance();
11
+
12
+ const LatencyTestPanel = () => {
13
+
14
+ const latencyTest = LatencyTest.getInstance();
15
+ const [stats, setStats] = useState(latencyTest.getLatencyStats());
16
+ const [isWhite, setIsWhite] = useState<boolean>(true);
17
+
18
+ const attributeName = settingsManager.getSetting('latencyAttributeName');
19
+
20
+ useEffect(() => {
21
+ const deviceManager = connManager.getConnector().getSystemType()?.deviceMgrIF;
22
+ if (!deviceManager) {
23
+ return;
24
+ }
25
+
26
+ const onNewDevice = (deviceKey: string, newDeviceState: DeviceState) => {
27
+ console.log(`New device: ${deviceKey}`);
28
+ };
29
+
30
+ const onNewAttribute = (deviceKey: string, attribute: DeviceAttributeState) => {
31
+ console.log(`New attribute: ${deviceKey}`);
32
+ };
33
+
34
+ const onNewAttributeData = (deviceKey: string, attribute: DeviceAttributeState) => {
35
+ // console.log(`New attribute data: ${deviceKey}`);
36
+ if (attribute.name === attributeName) {
37
+ latencyTest.processAttrValues(attribute, Date.now());
38
+ setStats(latencyTest.getLatencyStats());
39
+ }
40
+ };
41
+
42
+ deviceManager.addNewDeviceCallback(onNewDevice);
43
+ deviceManager.addNewAttributeCallback(onNewAttribute);
44
+ deviceManager.addAttributeDataCallback(onNewAttributeData);
45
+
46
+ // Cleanup callbacks when the component unmounts
47
+ return () => {
48
+ deviceManager.removeNewDeviceCallback(onNewDevice);
49
+ deviceManager.removeNewAttributeCallback(onNewAttribute);
50
+ deviceManager.removeAttributeDataCallback(onNewAttributeData);
51
+ };
52
+ }, [latencyTest, attributeName]);
53
+
54
+ useEffect(() => {
55
+ const interval = setInterval(() => {
56
+ // Toggle `isWhite` and record the color change
57
+ setIsWhite((prevIsWhite) => {
58
+ const newColor = !prevIsWhite ? 'white' : 'black';
59
+ latencyTest.recordColorChange(newColor, new Date().getTime());
60
+ return !prevIsWhite;
61
+ });
62
+ }, 2500);
63
+ return () => clearInterval(interval);
64
+ }, []);
65
+
66
+ return (
67
+ <div className="info-boxes">
68
+ <div className="info-box">
69
+ <h3>Latency Stats</h3>
70
+ <div style={{ display: 'flex', alignItems: 'center' }}>
71
+ <div
72
+ className="latency-test-panel"
73
+ style={{
74
+ width: '200px',
75
+ height: '150px',
76
+ backgroundColor: isWhite ? '#fff' : '#000',
77
+ border: '1px solid #666',
78
+ borderRadius: '8px',
79
+ marginRight: '20px',
80
+ }}
81
+ />
82
+ <div>
83
+ <div>Mean Latency: {stats.meanLatency ? `${stats.meanLatency.toFixed(1)} ms` : 'N/A'}</div>
84
+ <div>Std Dev: {stats.stdDevLatency ? `${stats.stdDevLatency.toFixed(1)} ms` : 'N/A'}</div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ );
90
+ };
91
+
92
+ export default LatencyTestPanel;
@@ -0,0 +1,234 @@
1
+ import React, { useEffect, useState, useRef } from 'react';
2
+ import './styles.css';
3
+ import SettingsScreen from './SettingsScreen';
4
+ import ConnManager from './ConnManager';
5
+ import {
6
+ RaftConnEvent,
7
+ RaftUpdateEvent,
8
+ RaftPublishEvent,
9
+ RaftSysTypeManager,
10
+ } from '../../../src/main';
11
+ import StatusPanel from './StatusPanel';
12
+ import DevicesPanel from './DevicesPanel';
13
+ import CommandPanel from './CommandPanel';
14
+ import LatencyTestPanel from './LatencyTestPanel';
15
+ import SettingsManager from './SettingsManager';
16
+
17
+ const sysTypeManager = RaftSysTypeManager.getInstance();
18
+ const connManager = ConnManager.getInstance();
19
+
20
+ export default function Main() {
21
+ const [connectionStatus, setConnectionStatus] = useState<RaftConnEvent>(
22
+ RaftConnEvent.CONN_DISCONNECTED
23
+ );
24
+ const [connectionTime, setConnectionTime] = useState<Date | null>(null);
25
+ const [elapsedTime, setElapsedTime] = useState<string | null>(null);
26
+ const [menuOpen, setMenuOpen] = useState(false);
27
+ const [showSettings, setShowSettings] = useState(false);
28
+ const menuRef = useRef<HTMLDivElement>(null);
29
+ const settingsManager = SettingsManager.getInstance();
30
+ const [latencyTestEnabled, setLatencyTestEnabled] = useState(
31
+ settingsManager.getSetting('latencyTest')
32
+ );
33
+
34
+ const [ipAddress, setIpAddress] = useState<string>(
35
+ localStorage.getItem('lastIpAddress') || ''
36
+ );
37
+
38
+ const [serialNo, setSerialNo] = useState<string>('');
39
+
40
+ const handleConnect = () => {
41
+ if (ipAddress.trim() === '') {
42
+ console.warn('No IP address entered');
43
+ return;
44
+ }
45
+ connManager.connect('WebSocket', ipAddress, [], null);
46
+ localStorage.setItem('lastIpAddress', ipAddress);
47
+ };
48
+
49
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
50
+ if (event.key === 'Enter') {
51
+ handleConnect();
52
+ }
53
+ };
54
+
55
+ useEffect(() => {
56
+ const listener = (
57
+ eventType: string,
58
+ eventEnum: RaftConnEvent | RaftUpdateEvent | RaftPublishEvent,
59
+ eventName: string,
60
+ data?: object | string | null
61
+ ) => {
62
+ if (eventType === 'conn') {
63
+ if (
64
+ eventEnum === RaftConnEvent.CONN_CONNECTED ||
65
+ eventEnum === RaftConnEvent.CONN_DISCONNECTED
66
+ ) {
67
+ setConnectionStatus(eventEnum);
68
+ setConnectionTime(new Date());
69
+ }
70
+ }
71
+ };
72
+
73
+ connManager.setConnectionEventListener(listener);
74
+
75
+ return () => {
76
+ connManager.setConnectionEventListener(() => { });
77
+ };
78
+ }, []);
79
+
80
+ useEffect(() => {
81
+ const handleClickOutside = (event: MouseEvent) => {
82
+ if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
83
+ setMenuOpen(false);
84
+ }
85
+ };
86
+ document.addEventListener('mousedown', handleClickOutside);
87
+ return () => {
88
+ document.removeEventListener('mousedown', handleClickOutside);
89
+ };
90
+ }, []);
91
+
92
+ useEffect(() => {
93
+ const interval = setInterval(() => {
94
+ setLatencyTestEnabled(settingsManager.getSetting('latencyTest'));
95
+ }, 100);
96
+ return () => clearInterval(interval);
97
+ }, []);
98
+
99
+ useEffect(() => {
100
+ if (connectionStatus === RaftConnEvent.CONN_CONNECTED && connectionTime) {
101
+ const interval = setInterval(() => {
102
+ const now = new Date();
103
+ const elapsed = now.getTime() - connectionTime.getTime();
104
+
105
+ const hours = Math.floor(elapsed / 3600000).toString().padStart(2, '0');
106
+ const minutes = Math.floor((elapsed % 3600000) / 60000).toString().padStart(2, '0');
107
+ const seconds = Math.floor((elapsed % 60000) / 1000).toString().padStart(2, '0');
108
+ const milliseconds = (elapsed % 1000).toString().padStart(3, '0');
109
+
110
+ setElapsedTime(`${hours}:${minutes}:${seconds}:${milliseconds}`);
111
+ }, 50);
112
+
113
+ return () => clearInterval(interval);
114
+ } else {
115
+ setElapsedTime(null);
116
+ }
117
+ }, [connectionStatus, connectionTime]);
118
+
119
+ return (
120
+ <div className="content-outer">
121
+ {showSettings ? (
122
+ <SettingsScreen onBack={() => setShowSettings(false)} />
123
+ ) : (
124
+ <>
125
+ <div className="header">
126
+ <h1>RaftJS Dashboard</h1>
127
+ <div
128
+ className="menu-icon header-menu-icon"
129
+ onClick={() => setMenuOpen(!menuOpen)}
130
+ >
131
+
132
+ </div>
133
+ {menuOpen && (
134
+ <div className="dropdown-menu" ref={menuRef}>
135
+ <div
136
+ className="menu-item"
137
+ onClick={() => {
138
+ setMenuOpen(false);
139
+ setShowSettings(true);
140
+ }}
141
+ >
142
+ Settings
143
+ </div>
144
+ </div>
145
+ )}
146
+ </div>
147
+ <div className="content-body">
148
+ {connectionStatus === RaftConnEvent.CONN_CONNECTED ? (
149
+ <>
150
+ <div className="connected-panel">
151
+ <div className="info-boxes">
152
+ <div className="info-box">
153
+ <div className="conn-indication">
154
+ <h3>Connected</h3>
155
+ </div>
156
+ <div>
157
+ <button
158
+ className="action-button"
159
+ onClick={() => connManager.disconnect()}
160
+ >
161
+ Disconnect
162
+ </button>
163
+ </div>
164
+ <div>
165
+ {elapsedTime && <p>{elapsedTime}</p>}
166
+ </div>
167
+ </div>
168
+ </div>
169
+ <StatusPanel />
170
+ {latencyTestEnabled && <LatencyTestPanel />}
171
+ <CommandPanel />
172
+ </div>
173
+ <DevicesPanel />
174
+ </>
175
+ ) : (
176
+ <>
177
+ <div className="info-boxes">
178
+ <div className="info-box">
179
+ <h3>WebSocket</h3>
180
+ <input
181
+ className="ip-addr-input"
182
+ id="ip-addr"
183
+ type="text"
184
+ placeholder="IP Address"
185
+ value={ipAddress}
186
+ onChange={(e) => setIpAddress(e.target.value)}
187
+ onKeyDown={handleKeyDown}
188
+ />
189
+ <button
190
+ className="action-button"
191
+ onClick={handleConnect}
192
+ >
193
+ Connect
194
+ </button>
195
+ </div>
196
+ <div className="info-box">
197
+ <h3>WebBLE</h3>
198
+ <input
199
+ className="serial-no-input"
200
+ id="serial-no"
201
+ type="text"
202
+ placeholder="Serial No (ignored if empty)"
203
+ value={serialNo}
204
+ onChange={(e) => setSerialNo(e.target.value)}
205
+ />
206
+ <button
207
+ className="action-button"
208
+ onClick={() => {
209
+ connManager.connect('WebBLE', '', sysTypeManager.getAllServiceUUIDs(), serialNo);
210
+ }}
211
+ >
212
+ Connect
213
+ </button>
214
+ </div>
215
+ <div className="info-box">
216
+ <h3>WebSerial</h3>
217
+ <button
218
+ className="action-button"
219
+ onClick={() => {
220
+ connManager.connect('WebSerial', '', [], null);
221
+ }}
222
+ >
223
+ Connect
224
+ </button>
225
+ </div>
226
+ </div>
227
+ </>
228
+ )}
229
+ </div>
230
+ </>
231
+ )}
232
+ </div>
233
+ );
234
+ }
@@ -0,0 +1,67 @@
1
+ export type Settings = {
2
+ showCharts: boolean;
3
+ maxChartDataPoints: number;
4
+ maxDatapointsToStore: number;
5
+ latencyTest: boolean;
6
+ latencyAttributeName: string;
7
+ latencyChangeThreshold: number;
8
+ };
9
+
10
+ class SettingsManager {
11
+ private static instance: SettingsManager;
12
+ private settings: Settings;
13
+ private storageKey = "RaftJS_Settings";
14
+ private maxChartDataPoints_default = 50;
15
+ private maxDatapointsToStore_default = 1000;
16
+
17
+ private constructor() {
18
+ // Load settings from localStorage or use default values
19
+ const savedSettings = localStorage.getItem(this.storageKey);
20
+ this.settings = savedSettings
21
+ ? JSON.parse(savedSettings)
22
+ : {
23
+ latencyTest: false,
24
+ showCharts: true,
25
+ maxChartDataPoints: this.maxChartDataPoints_default,
26
+ maxDatapointsToStore: this.maxDatapointsToStore_default,
27
+ };
28
+ }
29
+
30
+ static getInstance(): SettingsManager {
31
+ if (!SettingsManager.instance) {
32
+ SettingsManager.instance = new SettingsManager();
33
+ }
34
+ return SettingsManager.instance;
35
+ }
36
+
37
+ getSetting<K extends keyof Settings>(key: K): Settings[K] {
38
+ return this.settings[key];
39
+ }
40
+
41
+ setSetting<K extends keyof Settings>(key: K, value: Settings[K]): void {
42
+ this.settings[key] = value;
43
+ this.saveSettings();
44
+ }
45
+
46
+ getAllSettings(): Settings {
47
+ return this.settings;
48
+ }
49
+
50
+ // Save settings to localStorage
51
+ private saveSettings(): void {
52
+ localStorage.setItem(this.storageKey, JSON.stringify(this.settings));
53
+ }
54
+
55
+ // Reset to default settings
56
+ resetSettings(): void {
57
+ this.settings = {
58
+ latencyTest: false,
59
+ showCharts: true,
60
+ maxChartDataPoints: this.maxChartDataPoints_default,
61
+ maxDatapointsToStore: this.maxDatapointsToStore_default,
62
+ };
63
+ this.saveSettings();
64
+ }
65
+ }
66
+
67
+ export default SettingsManager;