@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.
- package/dist/react-native/RaftAttributeHandler.d.ts +1 -1
- package/dist/react-native/RaftAttributeHandler.js +18 -10
- package/dist/react-native/RaftAttributeHandler.js.map +1 -1
- package/dist/react-native/RaftChannelBLE.native.d.ts +11 -11
- package/dist/react-native/RaftChannelBLE.native.js +61 -41
- package/dist/react-native/RaftChannelBLE.native.js.map +1 -1
- package/dist/react-native/RaftChannelBLE.web.d.ts +3 -4
- package/dist/react-native/RaftChannelBLE.web.js +7 -8
- package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
- package/dist/react-native/RaftChannelBLEScanner.native.d.ts +6 -6
- package/dist/react-native/RaftChannelBLEScanner.native.js +38 -46
- package/dist/react-native/RaftChannelBLEScanner.native.js.map +1 -1
- package/dist/react-native/RaftConnector.d.ts +3 -3
- package/dist/react-native/RaftConnector.js +4 -4
- package/dist/react-native/RaftConnector.js.map +1 -1
- package/dist/react-native/RaftCustomAttrHandler.d.ts +1 -1
- package/dist/react-native/RaftCustomAttrHandler.js +4 -4
- package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
- package/dist/react-native/RaftDeviceInfo.d.ts +3 -0
- package/dist/react-native/RaftDeviceManager.d.ts +16 -11
- package/dist/react-native/RaftDeviceManager.js +196 -50
- package/dist/react-native/RaftDeviceManager.js.map +1 -1
- package/dist/react-native/RaftDeviceMgrIF.d.ts +9 -6
- package/dist/react-native/RaftDeviceStates.d.ts +5 -1
- package/dist/react-native/RaftDeviceStates.js +3 -3
- package/dist/react-native/RaftDeviceStates.js.map +1 -1
- package/dist/react-native/RaftMsgHandler.d.ts +1 -1
- package/dist/react-native/RaftMsgHandler.js +1 -2
- package/dist/react-native/RaftMsgHandler.js.map +1 -1
- package/dist/react-native/RaftStruct.d.ts +3 -0
- package/dist/react-native/RaftStruct.js +208 -0
- package/dist/react-native/RaftStruct.js.map +1 -0
- package/dist/react-native/RaftSysTypeManager.d.ts +14 -0
- package/dist/react-native/RaftSysTypeManager.js +53 -0
- package/dist/react-native/RaftSysTypeManager.js.map +1 -0
- package/dist/react-native/RaftSystemType.d.ts +3 -0
- package/dist/react-native/RaftTypes.d.ts +14 -15
- package/dist/react-native/RaftTypes.js +13 -50
- package/dist/react-native/RaftTypes.js.map +1 -1
- package/dist/react-native/RaftUpdateManager.js +6 -3
- package/dist/react-native/RaftUpdateManager.js.map +1 -1
- package/dist/react-native/RaftUtils.d.ts +1 -0
- package/dist/react-native/RaftUtils.js +17 -4
- package/dist/react-native/RaftUtils.js.map +1 -1
- package/dist/react-native/main.d.ts +2 -1
- package/dist/react-native/main.js +4 -4
- package/dist/react-native/main.js.map +1 -1
- package/dist/web/RaftAttributeHandler.d.ts +1 -1
- package/dist/web/RaftAttributeHandler.js +18 -10
- package/dist/web/RaftAttributeHandler.js.map +1 -1
- package/dist/web/RaftChannelBLE.web.d.ts +3 -4
- package/dist/web/RaftChannelBLE.web.js +7 -8
- package/dist/web/RaftChannelBLE.web.js.map +1 -1
- package/dist/web/RaftConnector.d.ts +3 -3
- package/dist/web/RaftConnector.js +4 -4
- package/dist/web/RaftConnector.js.map +1 -1
- package/dist/web/RaftCustomAttrHandler.d.ts +1 -1
- package/dist/web/RaftCustomAttrHandler.js +4 -4
- package/dist/web/RaftCustomAttrHandler.js.map +1 -1
- package/dist/web/RaftDeviceInfo.d.ts +3 -0
- package/dist/web/RaftDeviceManager.d.ts +16 -11
- package/dist/web/RaftDeviceManager.js +196 -50
- package/dist/web/RaftDeviceManager.js.map +1 -1
- package/dist/web/RaftDeviceMgrIF.d.ts +9 -6
- package/dist/web/RaftDeviceStates.d.ts +5 -1
- package/dist/web/RaftDeviceStates.js +3 -3
- package/dist/web/RaftDeviceStates.js.map +1 -1
- package/dist/web/RaftMsgHandler.d.ts +1 -1
- package/dist/web/RaftMsgHandler.js +1 -2
- package/dist/web/RaftMsgHandler.js.map +1 -1
- package/dist/web/RaftStruct.d.ts +3 -0
- package/dist/web/RaftStruct.js +208 -0
- package/dist/web/RaftStruct.js.map +1 -0
- package/dist/web/RaftSysTypeManager.d.ts +14 -0
- package/dist/web/RaftSysTypeManager.js +53 -0
- package/dist/web/RaftSysTypeManager.js.map +1 -0
- package/dist/web/RaftSystemType.d.ts +3 -0
- package/dist/web/RaftTypes.d.ts +14 -15
- package/dist/web/RaftTypes.js +13 -50
- package/dist/web/RaftTypes.js.map +1 -1
- package/dist/web/RaftUpdateManager.js +6 -3
- package/dist/web/RaftUpdateManager.js.map +1 -1
- package/dist/web/RaftUtils.d.ts +1 -0
- package/dist/web/RaftUtils.js +17 -4
- package/dist/web/RaftUtils.js.map +1 -1
- package/dist/web/main.d.ts +2 -1
- package/dist/web/main.js +4 -4
- package/dist/web/main.js.map +1 -1
- package/examples/dashboard/package.json +8 -15
- package/examples/dashboard/src/ConnManager.ts +18 -14
- package/examples/dashboard/src/DeviceActionsForm.tsx +49 -49
- package/examples/dashboard/src/DeviceLineChart.tsx +44 -20
- package/examples/dashboard/src/DevicePanel.tsx +33 -2
- package/examples/dashboard/src/DevicesPanel.tsx +5 -4
- package/examples/dashboard/src/LatencyTest.ts +130 -0
- package/examples/dashboard/src/LatencyTestPanel.tsx +92 -0
- package/examples/dashboard/src/Main.tsx +191 -73
- package/examples/dashboard/src/SettingsManager.ts +67 -0
- package/examples/dashboard/src/SettingsScreen.tsx +174 -0
- package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +14 -8
- package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +12 -5
- package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +30 -0
- package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +91 -0
- package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +23 -4
- package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +1 -1
- package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +5 -2
- package/examples/dashboard/src/index.html +2 -2
- package/examples/dashboard/src/index.tsx +0 -2
- package/examples/dashboard/src/styles.css +14 -0
- package/package.json +7 -15
- package/src/RaftAttributeHandler.ts +23 -14
- package/src/RaftChannelBLE.native.ts +77 -53
- package/src/RaftChannelBLE.web.ts +8 -8
- package/src/RaftChannelBLEScanner.native.ts +38 -44
- package/src/RaftConnector.ts +5 -5
- package/src/RaftCustomAttrHandler.ts +5 -5
- package/src/RaftDeviceInfo.ts +3 -0
- package/src/RaftDeviceManager.ts +236 -65
- package/src/RaftDeviceMgrIF.ts +11 -6
- package/src/RaftDeviceStates.ts +7 -3
- package/src/RaftMsgHandler.ts +1 -2
- package/src/RaftStruct.ts +209 -0
- package/src/RaftSysTypeManager.ts +60 -0
- package/src/RaftSystemType.ts +3 -0
- package/src/RaftTypes.ts +30 -33
- package/src/RaftUpdateManager.ts +6 -3
- package/src/RaftUtils.ts +14 -4
- 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">
|
|
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
|
-
|
|
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.
|
|
31
|
+
deviceManager.addNewDeviceCallback(onNewDevice);
|
|
32
32
|
|
|
33
33
|
const onNewAttribute = (deviceKey: string, attribute: DeviceAttributeState) => {
|
|
34
34
|
setLastUpdated(Date.now());
|
|
35
35
|
}
|
|
36
|
-
deviceManager.
|
|
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.
|
|
42
|
+
deviceManager.addAttributeDataCallback(onNewAttributeData);
|
|
42
43
|
|
|
43
|
-
}, [
|
|
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 {
|
|
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>(
|
|
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
|
-
|
|
16
|
-
|
|
54
|
+
const listener = (
|
|
55
|
+
eventType: string,
|
|
17
56
|
eventEnum: RaftConnEvent | RaftUpdateEvent | RaftPublishEvent,
|
|
18
57
|
eventName: string,
|
|
19
|
-
data?: object | string | null
|
|
20
|
-
|
|
21
|
-
if (eventType ===
|
|
22
|
-
if (
|
|
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
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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;
|