@@ -649,7 +649,6 @@ import Loading from './Loading.vue'
649649import { VideoChannelValue , type BaseParameterSetting , type VideoParameterSettings , type VideoResolutionValue , BaseAutoWhiteBalanceModeValue , BaseAutoWhiteBalanceSceneValue , type AdvancedParameterSetting , type CameraControl } from ' @/bindings/radcam'
650650import axios from ' axios'
651651import type { ActuatorsConfig , ActuatorsControl , ActuatorsParametersConfig , ActuatorsState , CameraID , MountType , ScriptFunction , ServoChannel } from ' @/bindings/autopilot'
652- import { applyNonNull } from ' @/utils/jsonUtils'
653652import ErrorDialog from ' ./ErrorDialog.vue'
654653import WelcomeDialog from ' ./WelcomeDialog.vue'
655654import { OneMoreTime } from ' @/utils/oneMoreTime'
@@ -744,6 +743,30 @@ const actuatorsState = ref<ActuatorsState>({
744743 zoom: 0 ,
745744 tilt: 0 ,
746745})
746+ type ActuatorKey = keyof ActuatorsState
747+
748+ const approxEqual = (a : number , b : number ): boolean => Math .abs (a - b ) <= 1e-3
749+
750+ // Tracks the last user-requested value per actuator. While a key is pending, we do not let
751+ // periodic polling overwrite the UI with intermediate/stale values.
752+ const desiredActuatorsState = ref <Record <ActuatorKey , number | null >>({
753+ focus: null ,
754+ zoom: null ,
755+ tilt: null ,
756+ })
757+
758+ // Coalesce actuator set requests so only one request per actuator can be in-flight, always
759+ // sending the most recent value (prevents request pile-up).
760+ const actuatorsSetInFlight = ref <Record <ActuatorKey , boolean >>({
761+ focus: false ,
762+ zoom: false ,
763+ tilt: false ,
764+ })
765+ const actuatorsSetQueued = ref <Record <ActuatorKey , number | null >>({
766+ focus: null ,
767+ zoom: null ,
768+ tilt: null ,
769+ })
747770const isConfigured = ref <boolean >(true )
748771const showWelcomeDialog = ref <boolean >(true )
749772const isLoading = ref <boolean >(false )
@@ -1103,7 +1126,23 @@ const getActuatorsState = () => {
11031126 .then ((response ) => {
11041127 const state = response .data as ActuatorsState
11051128
1106- applyNonNull (actuatorsState .value , state )
1129+ ;([' focus' , ' zoom' , ' tilt' ] as const ).forEach ((key ) => {
1130+ const desired = desiredActuatorsState .value [key ]
1131+ const received = state [key ]
1132+
1133+ // If we have a desired value in flight, only accept feedback once it converges.
1134+ if (desired !== null ) {
1135+ if (received !== null && received !== undefined && approxEqual (received , desired )) {
1136+ desiredActuatorsState .value [key ] = null
1137+ actuatorsState .value [key ] = received
1138+ }
1139+ return
1140+ }
1141+
1142+ if (received !== null && received !== undefined ) {
1143+ actuatorsState .value [key ] = received
1144+ }
1145+ })
11071146 console .log (state )
11081147 isConfigured .value = true
11091148 })
@@ -1114,28 +1153,54 @@ const getActuatorsState = () => {
11141153 })
11151154}
11161155
1117- const updateActuatorsState = (param : keyof ActuatorsState , value : number ) => {
1156+ const sendQueuedActuatorState = (param : ActuatorKey ) : void => {
11181157 if (! props .selectedCameraUuid || isLoading .value ) return
1158+ if (actuatorsSetInFlight .value [param ]) return
1159+
1160+ const value = actuatorsSetQueued .value [param ]
1161+ if (value === null ) return
1162+
1163+ actuatorsSetQueued .value [param ] = null
1164+ actuatorsSetInFlight .value [param ] = true
11191165
11201166 const payload: ActuatorsControl = {
11211167 camera_uuid: props .selectedCameraUuid ,
11221168 action: " setActuatorsState" ,
1123- json: { [param ]: value } as ActuatorsState
1169+ json: { [param ]: value } as ActuatorsState ,
11241170 }
11251171
11261172 axios
11271173 .post (` ${props .backendApi }/autopilot/control ` , payload )
1128- .then ((response ) => {
1129- const state = response .data as ActuatorsState ;
1130-
1131- applyNonNull (actuatorsState .value , state )
1132- console .log (state )
1133- })
11341174 .catch ((error ) => {
11351175 const message = ` Error updating ${param } `
11361176 console .log (message , error .message )
11371177 showWarningToast (message , error )
11381178 })
1179+ .finally (() => {
1180+ actuatorsSetInFlight .value [param ] = false
1181+
1182+ // If a newer value was queued while this request was in-flight, send it now.
1183+ if (actuatorsSetQueued .value [param ] !== null ) {
1184+ sendQueuedActuatorState (param )
1185+ }
1186+ })
1187+ }
1188+
1189+ const updateActuatorsState = (param : keyof ActuatorsState , value : number ) => {
1190+ if (! props .selectedCameraUuid || isLoading .value ) return
1191+
1192+ const key = param as ActuatorKey
1193+
1194+ // Optimistic UI update: do not wait for feedback to reflect user input.
1195+ actuatorsState .value [key ] = value
1196+ desiredActuatorsState .value [key ] = value
1197+
1198+ // Coalesce requests per actuator to prevent backlog.
1199+ const existingQueued = actuatorsSetQueued .value [key ]
1200+ if (existingQueued === null || ! approxEqual (existingQueued , value )) {
1201+ actuatorsSetQueued .value [key ] = value
1202+ }
1203+ sendQueuedActuatorState (key )
11391204}
11401205
11411206// eslint-disable-next-line @typescript-eslint/no-explicit-any
0 commit comments