11import React , { useEffect , useRef , useState } from 'react' ;
22import ConnManager from './ConnManager' ;
3- import { DeviceTypeAction } from '../../../src/RaftDeviceInfo' ;
3+ import { DeviceTypeAction , ActionMapEntry } from '../../../src/RaftDeviceInfo' ;
44import DispLEDGrid from './DispLedGrid' ;
55
66const 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+
818type 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 > }
0 commit comments