@robotical/raftjs 1.3.5 → 1.4.1

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 (128) hide show
  1. package/dist/react-native/RaftAttributeHandler.d.ts +1 -1
  2. package/dist/react-native/RaftAttributeHandler.js +18 -10
  3. package/dist/react-native/RaftAttributeHandler.js.map +1 -1
  4. package/dist/react-native/RaftChannelBLE.native.d.ts +11 -11
  5. package/dist/react-native/RaftChannelBLE.native.js +61 -41
  6. package/dist/react-native/RaftChannelBLE.native.js.map +1 -1
  7. package/dist/react-native/RaftChannelBLE.web.d.ts +3 -4
  8. package/dist/react-native/RaftChannelBLE.web.js +7 -8
  9. package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
  10. package/dist/react-native/RaftChannelBLEScanner.native.d.ts +6 -6
  11. package/dist/react-native/RaftChannelBLEScanner.native.js +38 -46
  12. package/dist/react-native/RaftChannelBLEScanner.native.js.map +1 -1
  13. package/dist/react-native/RaftConnector.d.ts +3 -3
  14. package/dist/react-native/RaftConnector.js +4 -4
  15. package/dist/react-native/RaftConnector.js.map +1 -1
  16. package/dist/react-native/RaftCustomAttrHandler.d.ts +1 -1
  17. package/dist/react-native/RaftCustomAttrHandler.js +4 -4
  18. package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
  19. package/dist/react-native/RaftDeviceInfo.d.ts +3 -0
  20. package/dist/react-native/RaftDeviceManager.d.ts +16 -11
  21. package/dist/react-native/RaftDeviceManager.js +196 -50
  22. package/dist/react-native/RaftDeviceManager.js.map +1 -1
  23. package/dist/react-native/RaftDeviceMgrIF.d.ts +9 -6
  24. package/dist/react-native/RaftDeviceStates.d.ts +5 -1
  25. package/dist/react-native/RaftDeviceStates.js +3 -3
  26. package/dist/react-native/RaftDeviceStates.js.map +1 -1
  27. package/dist/react-native/RaftMsgHandler.d.ts +1 -1
  28. package/dist/react-native/RaftMsgHandler.js +1 -2
  29. package/dist/react-native/RaftMsgHandler.js.map +1 -1
  30. package/dist/react-native/RaftStruct.d.ts +3 -0
  31. package/dist/react-native/RaftStruct.js +208 -0
  32. package/dist/react-native/RaftStruct.js.map +1 -0
  33. package/dist/react-native/RaftSysTypeManager.d.ts +14 -0
  34. package/dist/react-native/RaftSysTypeManager.js +53 -0
  35. package/dist/react-native/RaftSysTypeManager.js.map +1 -0
  36. package/dist/react-native/RaftSystemType.d.ts +3 -0
  37. package/dist/react-native/RaftTypes.d.ts +14 -15
  38. package/dist/react-native/RaftTypes.js +13 -50
  39. package/dist/react-native/RaftTypes.js.map +1 -1
  40. package/dist/react-native/RaftUpdateManager.js +6 -3
  41. package/dist/react-native/RaftUpdateManager.js.map +1 -1
  42. package/dist/react-native/RaftUtils.d.ts +1 -0
  43. package/dist/react-native/RaftUtils.js +17 -4
  44. package/dist/react-native/RaftUtils.js.map +1 -1
  45. package/dist/react-native/main.d.ts +2 -1
  46. package/dist/react-native/main.js +4 -4
  47. package/dist/react-native/main.js.map +1 -1
  48. package/dist/web/RaftAttributeHandler.d.ts +1 -1
  49. package/dist/web/RaftAttributeHandler.js +18 -10
  50. package/dist/web/RaftAttributeHandler.js.map +1 -1
  51. package/dist/web/RaftChannelBLE.web.d.ts +3 -4
  52. package/dist/web/RaftChannelBLE.web.js +7 -8
  53. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  54. package/dist/web/RaftConnector.d.ts +3 -3
  55. package/dist/web/RaftConnector.js +4 -4
  56. package/dist/web/RaftConnector.js.map +1 -1
  57. package/dist/web/RaftCustomAttrHandler.d.ts +1 -1
  58. package/dist/web/RaftCustomAttrHandler.js +4 -4
  59. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  60. package/dist/web/RaftDeviceInfo.d.ts +3 -0
  61. package/dist/web/RaftDeviceManager.d.ts +16 -11
  62. package/dist/web/RaftDeviceManager.js +196 -50
  63. package/dist/web/RaftDeviceManager.js.map +1 -1
  64. package/dist/web/RaftDeviceMgrIF.d.ts +9 -6
  65. package/dist/web/RaftDeviceStates.d.ts +5 -1
  66. package/dist/web/RaftDeviceStates.js +3 -3
  67. package/dist/web/RaftDeviceStates.js.map +1 -1
  68. package/dist/web/RaftMsgHandler.d.ts +1 -1
  69. package/dist/web/RaftMsgHandler.js +1 -2
  70. package/dist/web/RaftMsgHandler.js.map +1 -1
  71. package/dist/web/RaftStruct.d.ts +3 -0
  72. package/dist/web/RaftStruct.js +208 -0
  73. package/dist/web/RaftStruct.js.map +1 -0
  74. package/dist/web/RaftSysTypeManager.d.ts +14 -0
  75. package/dist/web/RaftSysTypeManager.js +53 -0
  76. package/dist/web/RaftSysTypeManager.js.map +1 -0
  77. package/dist/web/RaftSystemType.d.ts +3 -0
  78. package/dist/web/RaftTypes.d.ts +14 -15
  79. package/dist/web/RaftTypes.js +13 -50
  80. package/dist/web/RaftTypes.js.map +1 -1
  81. package/dist/web/RaftUpdateManager.js +6 -3
  82. package/dist/web/RaftUpdateManager.js.map +1 -1
  83. package/dist/web/RaftUtils.d.ts +1 -0
  84. package/dist/web/RaftUtils.js +17 -4
  85. package/dist/web/RaftUtils.js.map +1 -1
  86. package/dist/web/main.d.ts +2 -1
  87. package/dist/web/main.js +4 -4
  88. package/dist/web/main.js.map +1 -1
  89. package/examples/dashboard/package.json +8 -15
  90. package/examples/dashboard/src/ConnManager.ts +18 -14
  91. package/examples/dashboard/src/DeviceActionsForm.tsx +49 -49
  92. package/examples/dashboard/src/DeviceLineChart.tsx +44 -20
  93. package/examples/dashboard/src/DevicePanel.tsx +33 -2
  94. package/examples/dashboard/src/DevicesPanel.tsx +5 -4
  95. package/examples/dashboard/src/LatencyTest.ts +130 -0
  96. package/examples/dashboard/src/LatencyTestPanel.tsx +92 -0
  97. package/examples/dashboard/src/Main.tsx +191 -73
  98. package/examples/dashboard/src/SettingsManager.ts +67 -0
  99. package/examples/dashboard/src/SettingsScreen.tsx +174 -0
  100. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +14 -8
  101. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +12 -5
  102. package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +30 -0
  103. package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +91 -0
  104. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +23 -4
  105. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +1 -1
  106. package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +5 -2
  107. package/examples/dashboard/src/index.html +2 -2
  108. package/examples/dashboard/src/index.tsx +0 -2
  109. package/examples/dashboard/src/styles.css +14 -0
  110. package/package.json +7 -15
  111. package/src/RaftAttributeHandler.ts +23 -14
  112. package/src/RaftChannelBLE.native.ts +77 -53
  113. package/src/RaftChannelBLE.web.ts +8 -8
  114. package/src/RaftChannelBLEScanner.native.ts +38 -44
  115. package/src/RaftConnector.ts +5 -5
  116. package/src/RaftCustomAttrHandler.ts +5 -5
  117. package/src/RaftDeviceInfo.ts +3 -0
  118. package/src/RaftDeviceManager.ts +236 -65
  119. package/src/RaftDeviceMgrIF.ts +11 -6
  120. package/src/RaftDeviceStates.ts +7 -3
  121. package/src/RaftMsgHandler.ts +1 -2
  122. package/src/RaftStruct.ts +209 -0
  123. package/src/RaftSysTypeManager.ts +60 -0
  124. package/src/RaftSystemType.ts +3 -0
  125. package/src/RaftTypes.ts +30 -33
  126. package/src/RaftUpdateManager.ts +6 -3
  127. package/src/RaftUtils.ts +14 -4
  128. package/src/main.ts +2 -2
@@ -5,6 +5,7 @@ import DeviceAttrsForm from './DeviceAttrsForm';
5
5
  import DeviceActionsForm from './DeviceActionsForm';
6
6
  import DeviceLineChart from './DeviceLineChart';
7
7
  import ConnManager from './ConnManager';
8
+ import SettingsManager from './SettingsManager';
8
9
 
9
10
  const connManager = ConnManager.getInstance();
10
11
 
@@ -24,6 +25,11 @@ const DevicePanel = ({ deviceKey, lastUpdated }: DevicePanelProps) => {
24
25
  const [menuOpen, setMenuOpen] = useState<boolean>(false);
25
26
  const menuRef = useRef<HTMLDivElement>(null);
26
27
 
28
+ const settingsManager = SettingsManager.getInstance();
29
+ const [showCharts, setShowCharts] = useState(
30
+ settingsManager.getSetting('showCharts')
31
+ );
32
+
27
33
  useEffect(() => {
28
34
  const startTime = Date.now();
29
35
  const updateChart = () => {
@@ -110,10 +116,33 @@ const DevicePanel = ({ deviceKey, lastUpdated }: DevicePanelProps) => {
110
116
  document.body.removeChild(textArea);
111
117
  };
112
118
 
119
+ let headerText = `Device ${deviceState?.deviceTypeInfo?.name}`;
120
+ if ((deviceState?.deviceAddress !== undefined) && (deviceState?.deviceAddress !== "") && (deviceState?.deviceAddress !== "0")) {
121
+ // See if we can identify I2C addresses - should start with two bytes of 0s and then have a byte which is slot and a byte which is address
122
+ const addrInt = parseInt(deviceState?.deviceAddress, 10);
123
+ if (addrInt < 65536) {
124
+ const slot = addrInt >> 8;
125
+ const address = ("00" + (addrInt & 0xFF).toString(16)).slice(-2);
126
+ headerText += ` I2C Address 0x${address}`;
127
+ if (slot === 0)
128
+ headerText += ` (Main Bus)`;
129
+ else
130
+ headerText += ` (Slot ${slot})`;
131
+ } else {
132
+ headerText += ` Address ${deviceState?.deviceAddress}`;
133
+ }
134
+ }
135
+ if ((deviceState?.busName !== undefined) && (deviceState?.busName !== "") && (deviceState?.busName !== "0")) {
136
+ headerText += ` Bus ${deviceState?.busName}`;
137
+ }
138
+ if (!deviceState?.isOnline) {
139
+ headerText += " (Offline)";
140
+ }
141
+
113
142
  return (
114
143
  <div className={`device-panel ${offlineClass}`}>
115
144
  <div className="device-block-heading">
116
- <div className="device-block-heading-text">Device {deviceState?.deviceTypeInfo?.name} Address {deviceKey}{!deviceState?.isOnline ? " (Offline)" : ""}</div>
145
+ <div className="device-block-heading-text">{headerText}</div>
117
146
  <div className="menu-icon always-enabled" onClick={() => setMenuOpen(!menuOpen)}>☰</div>
118
147
  {menuOpen && (
119
148
  <div className="dropdown-menu" ref={menuRef}>
@@ -126,7 +155,9 @@ const DevicePanel = ({ deviceKey, lastUpdated }: DevicePanelProps) => {
126
155
  <DeviceAttrsForm deviceKey={deviceKey} lastUpdated={lastUpdated} />
127
156
  <DeviceActionsForm deviceKey={deviceKey} />
128
157
  </div>
129
- <DeviceLineChart deviceKey={deviceKey} lastUpdated={timedChartUpdate} />
158
+ {showCharts &&
159
+ <DeviceLineChart deviceKey={deviceKey} lastUpdated={timedChartUpdate} />
160
+ }
130
161
  </div>
131
162
  </div>
132
163
  );
@@ -28,19 +28,20 @@ export default function DevicesPanel(props: DevicesPanelProps) {
28
28
  const onNewDevice = (deviceKey: string, newDeviceState: DeviceState) => {
29
29
  setLastUpdated(Date.now());
30
30
  };
31
- deviceManager.onNewDevice(onNewDevice);
31
+ deviceManager.addNewDeviceCallback(onNewDevice);
32
32
 
33
33
  const onNewAttribute = (deviceKey: string, attribute: DeviceAttributeState) => {
34
34
  setLastUpdated(Date.now());
35
35
  }
36
- deviceManager.onNewDeviceAttribute(onNewAttribute);
36
+ deviceManager.addNewAttributeCallback(onNewAttribute);
37
37
 
38
38
  const onNewAttributeData = (deviceKey: string, attribute: DeviceAttributeState) => {
39
39
  setLastUpdated(Date.now());
40
+ // console.log(`New attribute data: ${deviceKey} ${attribute.name} ${attribute.values.length}`);
40
41
  }
41
- deviceManager.onNewAttributeData(onNewAttributeData);
42
+ deviceManager.addAttributeDataCallback(onNewAttributeData);
42
43
 
43
- }, [lastUpdated]);
44
+ }, []);
44
45
 
45
46
  const deviceManager = connManager.getConnector().getSystemType()?.deviceMgrIF;
46
47
  let devicesState: DevicesState = {};
@@ -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;
@@ -1,106 +1,224 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useState, useRef } from 'react';
2
2
  import './styles.css';
3
+ import SettingsScreen from './SettingsScreen';
3
4
  import ConnManager from './ConnManager';
4
- import { RaftConnEvent, RaftUpdateEvent, RaftPublishEvent } from "../../../src/main";
5
+ import {
6
+ RaftConnEvent,
7
+ RaftUpdateEvent,
8
+ RaftPublishEvent,
9
+ RaftSysTypeManager,
10
+ } from '../../../src/main';
5
11
  import StatusPanel from './StatusPanel';
6
12
  import DevicesPanel from './DevicesPanel';
7
13
  import CommandPanel from './CommandPanel';
14
+ import LatencyTestPanel from './LatencyTestPanel';
15
+ import SettingsManager from './SettingsManager';
8
16
 
17
+ const sysTypeManager = RaftSysTypeManager.getInstance();
9
18
  const connManager = ConnManager.getInstance();
10
19
 
11
20
  export default function Main() {
12
- const [connectionStatus, setConnectionStatus] = useState<RaftConnEvent>(RaftConnEvent.CONN_DISCONNECTED);
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 handleConnect = () => {
39
+ if (ipAddress.trim() === '') {
40
+ console.error('No IP address entered');
41
+ return;
42
+ }
43
+ connManager.connect('WebSocket', ipAddress, []);
44
+ localStorage.setItem('lastIpAddress', ipAddress);
45
+ };
46
+
47
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
48
+ if (event.key === 'Enter') {
49
+ handleConnect();
50
+ }
51
+ };
13
52
 
14
53
  useEffect(() => {
15
- // Define the listener for raft events
16
- const listener = (eventType: string,
54
+ const listener = (
55
+ eventType: string,
17
56
  eventEnum: RaftConnEvent | RaftUpdateEvent | RaftPublishEvent,
18
57
  eventName: string,
19
- data?: object | string | null) => {
20
- console.log(`Connection event: ${eventName}`);
21
- if (eventType === "conn") {
22
- if ((eventEnum === RaftConnEvent.CONN_CONNECTED) || (eventEnum === RaftConnEvent.CONN_DISCONNECTED)) {
58
+ data?: object | string | null
59
+ ) => {
60
+ if (eventType === 'conn') {
61
+ if (
62
+ eventEnum === RaftConnEvent.CONN_CONNECTED ||
63
+ eventEnum === RaftConnEvent.CONN_DISCONNECTED
64
+ ) {
23
65
  setConnectionStatus(eventEnum);
66
+ setConnectionTime(new Date());
24
67
  }
25
68
  }
26
69
  };
27
70
 
28
- // Set the listener function
29
71
  connManager.setConnectionEventListener(listener);
30
72
 
31
- // Clean up the listener when the component unmounts
32
73
  return () => {
33
74
  connManager.setConnectionEventListener(() => { });
34
75
  };
35
76
  }, []);
36
77
 
78
+ useEffect(() => {
79
+ const handleClickOutside = (event: MouseEvent) => {
80
+ if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
81
+ setMenuOpen(false);
82
+ }
83
+ };
84
+ document.addEventListener('mousedown', handleClickOutside);
85
+ return () => {
86
+ document.removeEventListener('mousedown', handleClickOutside);
87
+ };
88
+ }, []);
89
+
90
+ useEffect(() => {
91
+ const interval = setInterval(() => {
92
+ setLatencyTestEnabled(settingsManager.getSetting('latencyTest'));
93
+ }, 100);
94
+ return () => clearInterval(interval);
95
+ }, []);
96
+
97
+ useEffect(() => {
98
+ if (connectionStatus === RaftConnEvent.CONN_CONNECTED && connectionTime) {
99
+ const interval = setInterval(() => {
100
+ const now = new Date();
101
+ const elapsed = now.getTime() - connectionTime.getTime();
102
+
103
+ const hours = Math.floor(elapsed / 3600000).toString().padStart(2, '0');
104
+ const minutes = Math.floor((elapsed % 3600000) / 60000).toString().padStart(2, '0');
105
+ const seconds = Math.floor((elapsed % 60000) / 1000).toString().padStart(2, '0');
106
+ const milliseconds = (elapsed % 1000).toString().padStart(3, '0');
107
+
108
+ setElapsedTime(`${hours}:${minutes}:${seconds}:${milliseconds}`);
109
+ }, 50);
110
+
111
+ return () => clearInterval(interval);
112
+ } else {
113
+ setElapsedTime(null);
114
+ }
115
+ }, [connectionStatus, connectionTime]);
116
+
37
117
  return (
38
118
  <div className="content-outer">
39
- <div className="header">
40
- <h1>RaftJS Dashboard</h1>
41
- </div>
42
- <div className="content-body">
43
- {/* Div optionally shown if connected */}
44
- {connectionStatus === RaftConnEvent.CONN_CONNECTED ?
45
- <>
46
- <div className="connected-panel">
47
- <div className="info-boxes">
48
- <div className="info-box">
49
- <div className="conn-indication">
50
- <h3>Connected</h3>
119
+ {showSettings ? (
120
+ <SettingsScreen onBack={() => setShowSettings(false)} />
121
+ ) : (
122
+ <>
123
+ <div className="header">
124
+ <h1>RaftJS Dashboard</h1>
125
+ <div
126
+ className="menu-icon header-menu-icon"
127
+ onClick={() => setMenuOpen(!menuOpen)}
128
+ >
129
+
130
+ </div>
131
+ {menuOpen && (
132
+ <div className="dropdown-menu" ref={menuRef}>
133
+ <div
134
+ className="menu-item"
135
+ onClick={() => {
136
+ setMenuOpen(false);
137
+ setShowSettings(true);
138
+ }}
139
+ >
140
+ Settings
141
+ </div>
142
+ </div>
143
+ )}
144
+ </div>
145
+ <div className="content-body">
146
+ {connectionStatus === RaftConnEvent.CONN_CONNECTED ? (
147
+ <>
148
+ <div className="connected-panel">
149
+ <div className="info-boxes">
150
+ <div className="info-box">
151
+ <div className="conn-indication">
152
+ <h3>Connected</h3>
153
+ </div>
154
+ <div>
155
+ <button
156
+ className="action-button"
157
+ onClick={() => connManager.disconnect()}
158
+ >
159
+ Disconnect
160
+ </button>
161
+ </div>
162
+ <div>
163
+ {elapsedTime && <p>{elapsedTime}</p>}
164
+ </div>
165
+ </div>
166
+ </div>
167
+ <StatusPanel />
168
+ {latencyTestEnabled && <LatencyTestPanel />}
169
+ <CommandPanel />
170
+ </div>
171
+ <DevicesPanel />
172
+ </>
173
+ ) : (
174
+ <>
175
+ <div className="info-boxes">
176
+ <div className="info-box">
177
+ <h3>WebSocket</h3>
178
+ <input
179
+ className="ip-addr-input"
180
+ id="ip-addr"
181
+ type="text"
182
+ placeholder="IP Address"
183
+ value={ipAddress}
184
+ onChange={(e) => setIpAddress(e.target.value)}
185
+ onKeyDown={handleKeyDown}
186
+ />
187
+ <button
188
+ className="action-button"
189
+ onClick={handleConnect}
190
+ >
191
+ Connect
192
+ </button>
51
193
  </div>
52
- <div>
53
- <button className="action-button" onClick={() => connManager.disconnect()}>Disconnect</button>
194
+ <div className="info-box">
195
+ <h3>WebBLE</h3>
196
+ <button
197
+ className="action-button"
198
+ onClick={() => {
199
+ connManager.connect('WebBLE', '', sysTypeManager.getAllServiceUUIDs());
200
+ }}
201
+ >
202
+ Connect
203
+ </button>
204
+ </div>
205
+ <div className="info-box">
206
+ <h3>WebSerial</h3>
207
+ <button
208
+ className="action-button"
209
+ onClick={() => {
210
+ connManager.connect('WebSerial', '', []);
211
+ }}
212
+ >
213
+ Connect
214
+ </button>
54
215
  </div>
55
216
  </div>
56
- </div>
57
- <StatusPanel />
58
- <CommandPanel />
59
- </div>
60
- <DevicesPanel />
61
- </>
62
- :
63
- <>
64
- <div className="info-boxes">
65
- <div className="info-box">
66
- <h3>WebSocket</h3>
67
- <input className="ip-addr-input" id="ip-addr" type="text" placeholder="IP Address" />
68
- <button className="action-button" onClick={() => {
69
- // Get IP address
70
- const ipAddrElem = document.getElementById("ip-addr") as HTMLInputElement;
71
- if (ipAddrElem) {
72
- const ipAddr = ipAddrElem.value;
73
- connManager.connect("WebSocket", ipAddr, "");
74
- } else {
75
- console.error("No IP address entered");
76
- }
77
- }
78
- }>
79
- Connect
80
- </button>
81
- </div>
82
- <div className="info-box">
83
- <h3>WebBLE</h3>
84
- <button className="action-button" onClick={() => {
85
- connManager.connect("WebBLE", "", "");
86
- }
87
- }>
88
- Connect
89
- </button>
90
- </div>
91
- <div className="info-box">
92
- <h3>WebSerial</h3>
93
- <button className="action-button" onClick={() => {
94
- connManager.connect("WebSerial", "", "");
95
- }
96
- }>
97
- Connect
98
- </button>
99
- </div>
100
- </div>
101
- </>
102
- }
103
- </div>
217
+ </>
218
+ )}
219
+ </div>
220
+ </>
221
+ )}
104
222
  </div>
105
223
  );
106
224
  }
@@ -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;