55*/
66
77// Native imports
8- import { useEffect } from "react"
8+ import { useEffect , useMemo } from "react"
99
1010// 3rd Party Imports
11- import { Button } from "@mantine/core"
11+ import {
12+ Button ,
13+ LoadingOverlay ,
14+ NumberInput ,
15+ Select ,
16+ Tooltip ,
17+ } from "@mantine/core"
1218
1319// Styling Imports
1420import resolveConfig from "tailwindcss/resolveConfig"
1521import tailwindConfig from "../../../tailwind.config"
1622const tailwindColors = resolveConfig ( tailwindConfig ) . theme . colors
1723
24+ // Data
25+ import apmParamDefsCopter from "../../../data/gen_apm_params_def_copter.json"
26+ import apmParamDefsPlane from "../../../data/gen_apm_params_def_plane.json"
27+
1828// Redux
29+ import { useDebouncedCallback } from "@mantine/hooks"
30+ import { IconInfoCircle } from "@tabler/icons-react"
1931import { useDispatch , useSelector } from "react-redux"
20- import { emitSetGripper } from "../../redux/slices/configSlice"
32+ import {
33+ emitGetGripperConfig ,
34+ emitSetGripper ,
35+ emitSetGripperConfigParam ,
36+ selectGripperConfig ,
37+ selectRefreshingGripperConfigData ,
38+ } from "../../redux/slices/configSlice"
2139import {
2240 emitSetState ,
2341 selectConnectedToDrone ,
2442} from "../../redux/slices/droneConnectionSlice"
43+ import { selectAircraftTypeString } from "../../redux/slices/droneInfoSlice"
44+
45+ const GRIPPER_PARAMS = [
46+ "GRIP_CAN_ID" ,
47+ "GRIP_AUTOCLOSE" ,
48+ "GRIP_GRAB" ,
49+ "GRIP_NEUTRAL" ,
50+ "GRIP_REGRAB" ,
51+ "GRIP_RELEASE" ,
52+ "GRIP_TYPE" ,
53+ ]
54+
55+ function cleanFloat ( value , decimals = 3 ) {
56+ if ( typeof value === "number" ) {
57+ return Number ( value . toFixed ( decimals ) )
58+ }
59+ if ( ! isNaN ( value ) ) {
60+ return Number ( parseFloat ( value ) . toFixed ( decimals ) )
61+ }
62+ return value
63+ }
64+
65+ // Try to handle floats because mantine handles keys internally as strings
66+ // Which leads to floating point rounding errors
67+ function sanitiseInput ( value , toString = false ) {
68+ let sanitisedValue = value
69+ if ( ! isNaN ( value ) && String ( value ) . trim ( ) !== "" ) {
70+ sanitisedValue = String ( value ) . includes ( "." )
71+ ? parseFloat ( value )
72+ : parseInt ( value )
73+ }
74+
75+ return toString ? `${ sanitisedValue } ` : sanitisedValue
76+ }
77+
78+ function InputLabel ( { param } ) {
79+ return (
80+ < p className = "flex flex-row items-center gap-1" >
81+ { param . param_id } { " " }
82+ < span >
83+ < Tooltip
84+ className = "inline"
85+ label = {
86+ < >
87+ < p className = "text-wrap max-w-80" >
88+ { param . param_def ?. Description }
89+ </ p >
90+ </ >
91+ }
92+ >
93+ < IconInfoCircle size = { 16 } />
94+ </ Tooltip >
95+ </ span >
96+ </ p >
97+ )
98+ }
2599
26100export default function Gripper ( ) {
27101 const dispatch = useDispatch ( )
28102 const connected = useSelector ( selectConnectedToDrone )
103+ const aircraftTypeString = useSelector ( selectAircraftTypeString )
104+ const gripperConfig = useSelector ( selectGripperConfig )
105+ const refreshingGripperConfigData = useSelector (
106+ selectRefreshingGripperConfigData ,
107+ )
29108
30- // Set gripper config values
31- function setGripper ( action ) {
32- dispatch ( emitSetGripper ( action ) )
33- }
109+ const params = useMemo ( ( ) => {
110+ if ( ! gripperConfig ) {
111+ return [ ]
112+ }
113+
114+ const paramDefs =
115+ aircraftTypeString === "Copter" ? apmParamDefsCopter : apmParamDefsPlane
116+
117+ return GRIPPER_PARAMS . map ( ( param ) => {
118+ return {
119+ param_id : param ,
120+ param_value : gripperConfig [ param ] ?? "UNKNOWN" ,
121+ param_def : paramDefs [ param ] ,
122+ }
123+ } )
124+ } , [ gripperConfig , aircraftTypeString ] )
34125
35126 useEffect ( ( ) => {
36- if ( connected ) {
37- dispatch ( emitSetState ( "config" ) )
127+ if ( ! connected ) {
128+ return
38129 }
130+
131+ dispatch ( emitSetState ( "config" ) )
132+ dispatch ( emitGetGripperConfig ( ) )
39133 } , [ connected ] )
40134
135+ function setGripper ( action ) {
136+ dispatch ( emitSetGripper ( action ) )
137+ }
138+
139+ const debouncedUpdate = useDebouncedCallback ( ( param_id , value ) => {
140+ dispatch ( emitSetGripperConfigParam ( { param_id, value } ) )
141+ } , 500 )
142+
41143 return (
42- < div className = "m-4 w-1/2" >
144+ < div className = "flex flex-col gap-4 mx-4" >
145+ < LoadingOverlay
146+ visible = { refreshingGripperConfigData }
147+ zIndex = { 1000 }
148+ overlayProps = { { blur : 2 } }
149+ />
43150 < div className = "flex flex-row gap-2" >
44151 < Button
45152 onClick = { ( ) => setGripper ( "release" ) }
@@ -54,6 +161,56 @@ export default function Gripper() {
54161 Grab Gripper
55162 </ Button >
56163 </ div >
164+ < div className = "flex flex-col gap-2" >
165+ { params . map ( ( param ) => (
166+ < div key = { param . param_id } className = "flex flex-row justify-between" >
167+ { param . param_def ?. Values ? (
168+ < Select
169+ label = { < InputLabel param = { param } /> }
170+ className = "w-64"
171+ value = { `${ cleanFloat ( param . param_value ) } ` }
172+ onChange = { ( value ) => {
173+ dispatch (
174+ emitSetGripperConfigParam ( {
175+ param_id : param . param_id ,
176+ value : sanitiseInput ( value ) ,
177+ } ) ,
178+ )
179+ } }
180+ data = { Object . keys ( param . param_def ?. Values ) . map ( ( key ) => ( {
181+ value : `${ key } ` ,
182+ label : `${ key } : ${ param . param_def ?. Values [ key ] } ` ,
183+ } ) ) }
184+ allowDeselect = { false }
185+ />
186+ ) : (
187+ < NumberInput
188+ label = { < InputLabel param = { param } /> }
189+ description = {
190+ param . param_def ?. Range
191+ ? `${ param . param_def ?. Range . low } - ${ param . param_def ?. Range . high } `
192+ : ""
193+ }
194+ className = "w-64"
195+ value = { param . param_value }
196+ onChange = { ( value ) => {
197+ if ( value === "" || isNaN ( value ) ) {
198+ return
199+ }
200+ debouncedUpdate ( param . param_id , value )
201+ } }
202+ decimalScale = { 5 }
203+ hideControls
204+ min = { param . param_def ?. Range ? param . param_def ?. Range . low : null }
205+ max = {
206+ param . param_def ?. Range ? param . param_def ?. Range . high : null
207+ }
208+ suffix = { param . param_def ?. Units }
209+ />
210+ ) }
211+ </ div >
212+ ) ) }
213+ </ div >
57214 </ div >
58215 )
59216}
0 commit comments