Skip to content

Commit a186973

Browse files
committed
Initial sample rate in drop-down
1 parent a7595ec commit a186973

5 files changed

Lines changed: 144 additions & 9 deletions

File tree

examples/dashboard/src/DeviceActionsForm.tsx

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
import React, { useEffect, useRef, useState } from 'react';
22
import ConnManager from './ConnManager';
3-
import { DeviceTypeAction } from '../../../src/RaftDeviceInfo';
3+
import { DeviceTypeAction, ActionMapEntry } from '../../../src/RaftDeviceInfo';
44
import DispLEDGrid from './DispLedGrid';
55

66
const connManager = ConnManager.getInstance();
77

8+
// Generic sample rate options for devices without _conf.rate
9+
const GENERIC_SAMPLE_RATES = [50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.01, 0.001];
10+
11+
// Find the closest value in an array to a target
12+
function findClosest(arr: number[], target: number): number {
13+
return arr.reduce((prev, curr) =>
14+
Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev
15+
);
16+
}
17+
818
type DeviceActionsTableProps = {
919
deviceKey: string;
1020
};
@@ -18,18 +28,28 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
1828
const [deviceActions, setDeviceActions] = useState<DeviceTypeAction[]>([]);
1929
const [inputValues, setInputValues] = useState<InputValues>({});
2030
const [actionStatus, setActionStatus] = useState<string>('');
31+
const [genericRateHz, setGenericRateHz] = useState<number>(10);
32+
const [isBusDevice, setIsBusDevice] = useState<boolean>(false);
33+
const [hasConfRate, setHasConfRate] = useState<boolean>(false);
2134

2235
useEffect(() => {
2336
if (!deviceManager) {
2437
return;
2538
}
2639
// Wait a little while inline for the device to be ready
27-
setTimeout(() => {
40+
setTimeout(async () => {
2841
const deviceState = deviceManager.getDeviceState(deviceKey);
2942
const { deviceTypeInfo } = deviceState;
3043
const actions: DeviceTypeAction[] = deviceTypeInfo?.actions || [];
3144
setDeviceActions(actions);
32-
// Initialize input values
45+
// Check if this is a bus device (has a valid busName)
46+
const busName = deviceState?.busName ?? '';
47+
const isBus = busName !== '' && busName !== '0';
48+
setIsBusDevice(isBus);
49+
// Check if device has _conf.rate action
50+
const confRateAction = actions.find(a => a.n === '_conf.rate');
51+
setHasConfRate(!!confRateAction);
52+
// Initialize input values with defaults
3353
const initialValues: InputValues = actions.reduce((acc, action) => {
3454
acc[action.n] =
3555
action.d ??
@@ -40,6 +60,47 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
4060
: 0);
4161
return acc;
4262
}, {} as InputValues);
63+
64+
// Query current poll config from firmware to initialize rate dropdowns
65+
if (isBus && deviceState?.deviceAddress) {
66+
try {
67+
const cmd = `devman/devconfig?bus=${busName}&addr=${deviceState.deviceAddress}`;
68+
const resp = await connManager.getConnector().sendRICRESTMsg(cmd, {}) as any;
69+
if (resp?.rslt === 'ok' && resp.pollIntervalUs > 0) {
70+
const currentRateHz = 1000000 / resp.pollIntervalUs;
71+
if (confRateAction?.map) {
72+
// For _conf.rate: find the map key whose interval and numSamples
73+
// best match the current config (both i and s needed to disambiguate
74+
// e.g. 52Hz and 104Hz both use i=50000 but differ in s)
75+
let bestKey = String(confRateAction.d ?? '');
76+
let bestDist = Infinity;
77+
for (const [key, entry] of Object.entries(confRateAction.map)) {
78+
const mapEntry = entry as ActionMapEntry;
79+
if (mapEntry.i !== undefined) {
80+
let dist = Math.abs(mapEntry.i - resp.pollIntervalUs);
81+
// Add penalty for numSamples mismatch to disambiguate entries with same interval
82+
if (mapEntry.s !== undefined && resp.numSamples !== undefined) {
83+
dist += Math.abs(mapEntry.s - resp.numSamples) * 1000000;
84+
}
85+
if (dist < bestDist) {
86+
bestDist = dist;
87+
bestKey = key;
88+
}
89+
}
90+
}
91+
if (bestKey) {
92+
initialValues['_conf.rate'] = parseFloat(bestKey);
93+
}
94+
} else {
95+
// For generic rate dropdown: find closest option
96+
setGenericRateHz(findClosest(GENERIC_SAMPLE_RATES, currentRateHz));
97+
}
98+
}
99+
} catch (err) {
100+
// Ignore query errors — keep defaults
101+
}
102+
}
103+
43104
setInputValues(initialValues);
44105
}, 1000);
45106
}, [deviceKey]);
@@ -71,7 +132,24 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
71132
}
72133
};
73134

74-
if (deviceActions.length === 0) {
135+
const handleGenericRateSend = async () => {
136+
if (!deviceManager) {
137+
return;
138+
}
139+
setActionStatus('Setting sample rate...');
140+
const result = await deviceManager.setSampleRate(deviceKey, genericRateHz);
141+
if (result.ok) {
142+
setActionStatus(`Rate: ${result.actualRateHz} Hz, poll: ${result.intervalUs} µs, buf: ${result.numSamples}`);
143+
} else {
144+
setActionStatus(`Error: ${result.error}`);
145+
}
146+
setTimeout(() => setActionStatus(''), 5000);
147+
};
148+
149+
// Show generic rate control for bus devices without _conf.rate
150+
const showGenericRate = isBusDevice && !hasConfRate;
151+
152+
if (deviceActions.length === 0 && !showGenericRate) {
75153
return <></>;
76154
}
77155

@@ -103,9 +181,11 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
103181
);
104182
} else if (action.map) {
105183
const mapKeys = Object.keys(action.map).sort((a, b) => parseFloat(a) - parseFloat(b));
184+
// Use "Rate Hz" label for _conf.rate actions
185+
const actionLabel = action.n === '_conf.rate' ? 'Rate Hz' : (action.desc ?? action.n);
106186
return (
107187
<tr key={action.n}>
108-
<td>{action.desc ?? action.n}</td>
188+
<td>{actionLabel}</td>
109189
<td>
110190
<select
111191
value={inputValues[action.n]}
@@ -173,6 +253,26 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
173253
);
174254
}
175255
})}
256+
{showGenericRate && (
257+
<tr key="__generic_rate">
258+
<td>Rate Hz</td>
259+
<td>
260+
<select
261+
value={genericRateHz}
262+
onChange={(e) => setGenericRateHz(parseFloat(e.target.value))}
263+
>
264+
{GENERIC_SAMPLE_RATES.map((rate) => (
265+
<option key={rate} value={rate}>
266+
{rate >= 1 ? `${rate} Hz` : `${rate} Hz`}
267+
</option>
268+
))}
269+
</select>
270+
</td>
271+
<td>
272+
<button onClick={handleGenericRateSend}>Send</button>
273+
</td>
274+
</tr>
275+
)}
176276
</tbody>
177277
</table>
178278
{actionStatus && <div className="action-status">{actionStatus}</div>}

examples/dashboard/src/LoggingPanel.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default function LoggingPanel({ onLogStopped, pausePolling, logConfig }:
4343
const [isBusy, setIsBusy] = useState(false);
4444
const [lastError, setLastError] = useState('');
4545
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
46+
const wasLoggingRef = useRef(false);
4647

4748
const fetchStatus = async () => {
4849
if (!connManager.getConnector().isConnected()) return;
@@ -53,8 +54,9 @@ export default function LoggingPanel({ onLogStopped, pausePolling, logConfig }:
5354
if (resp && typeof resp === 'object') {
5455
const r = resp as any;
5556
const flushLatency = r.flushLatency ?? {};
57+
const nowLogging = r.active ?? false;
5658
setStatus({
57-
isLogging: r.active ?? false,
59+
isLogging: nowLogging,
5860
fileName: r.fileName ?? '',
5961
elapsedSecs: (r.durationMs ?? 0) / 1000,
6062
bytesWritten: r.totalBytesWritten ?? 0,
@@ -65,6 +67,11 @@ export default function LoggingPanel({ onLogStopped, pausePolling, logConfig }:
6567
maxWriteMs: (flushLatency.maxUs ?? 0) / 1000,
6668
bytesPerSec: r.bytesPerSec ?? 0,
6769
});
70+
// Detect timed logging session that finished on its own
71+
if (wasLoggingRef.current && !nowLogging) {
72+
onLogStopped?.();
73+
}
74+
wasLoggingRef.current = nowLogging;
6875
}
6976
} catch (e) {
7077
console.warn('Failed to fetch logging status', e);

examples/dashboard/src/Main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export default function Main() {
155155
{connectionStatus === RaftConnEvent.CONN_CONNECTED ? (
156156
<>
157157
<div className="connected-panel">
158-
<div className="info-boxes">
158+
<div className="info-boxes connection-info">
159159
<div className="info-box">
160160
<div className="conn-indication">
161161
<h3>Connected via {connManager.getConnector().getConnMethod() || 'Unknown'}</h3>

examples/dashboard/src/styles.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,19 @@ h1 {
9494
gap: 10px;
9595
}
9696

97+
.connection-info {
98+
flex: 0 0 auto;
99+
max-width: 140px;
100+
}
101+
102+
.connection-info .info-box {
103+
padding: 8px 12px;
104+
}
105+
97106
.connected-panel {
98107
display: flex;
99108
justify-content: flex-start;
100-
gap: 20px;
109+
gap: 10px;
101110
align-items: stretch;
102111
flex-direction: row;
103112
width: 100%;

src/RaftDeviceManager.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,26 @@ export class DeviceManager implements RaftDeviceMgrIF{
931931
// Find the _conf.rate action
932932
const confRateAction = typeInfo.actions?.find(a => a.n === '_conf.rate');
933933
if (!confRateAction?.map) {
934-
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: 0, intervalUs: 0, numSamples: 0, error: 'Device does not have a _conf.rate action with a rate map' };
934+
// No _conf.rate action — use generic sample rate setting
935+
// Non-FIFO devices: poll once per sample period, 1 sample per read
936+
const samplePeriodUs = Math.round(1000000 / sampleRateHz);
937+
const numSamples = options?.numSamples ?? 1;
938+
const intervalUs = options?.intervalUs ?? Math.max(5000, samplePeriodUs);
939+
940+
const { bus: devBus, addr: devAddr } = parseDeviceKey(deviceKey);
941+
const cmd = `devman/devconfig?bus=${devBus}&addr=${devAddr}&intervalUs=${intervalUs}&numSamples=${numSamples}`;
942+
943+
try {
944+
const msgHandler = this._systemUtils?.getMsgHandler();
945+
if (!msgHandler) {
946+
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: sampleRateHz, intervalUs, numSamples, error: 'No message handler available' };
947+
}
948+
const msgRslt = await msgHandler.sendRICRESTURL<RaftOKFail>(cmd);
949+
const ok = msgRslt.rslt === 'ok';
950+
return { ok, requestedRateHz: sampleRateHz, actualRateHz: sampleRateHz, intervalUs, numSamples, error: ok ? undefined : `Firmware returned: ${msgRslt.rslt}` };
951+
} catch (error) {
952+
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: sampleRateHz, intervalUs, numSamples, error: `${error}` };
953+
}
935954
}
936955

937956
// Find the closest supported rate from the map keys

0 commit comments

Comments
 (0)