2828 # py3
2929 from io import BytesIO as SIO
3030
31+ try :
32+ import queue as Queue
33+ from queue import Empty
34+ except ImportError :
35+ import Queue
36+ from Queue import Empty
37+
3138
3239class ParamState :
3340 '''this class is separated to make it possible to use the parameter
@@ -56,6 +63,141 @@ def __init__(self, mav_param, logdir, vehicle_name, parm_file, mpstate, sysid):
5663 self .default_params = None
5764 self .watch_patterns = set ()
5865
66+ # dictionary of ParamSet objects we are processing:
67+ self .parameters_to_set = {}
68+ # a Queue which onto which ParamSet objects can be pushed in a
69+ # thread-safe manner:
70+ self .parameters_to_set_input_queue = Queue .Queue ()
71+
72+ class ParamSet ():
73+ '''class to hold information about a parameter set being attempted'''
74+ def __init__ (self , master , name , value , param_type = None , attempts = None ):
75+ self .master = master
76+ self .name = name
77+ self .value = value
78+ self .param_type = param_type
79+ self .attempts_remaining = attempts
80+ self .retry_interval = 1 # seconds
81+ self .last_value_received = None
82+
83+ if self .attempts_remaining is None :
84+ self .attempts_remaining = 3
85+
86+ self .request_sent = 0 # this is a timestamp
87+
88+ def normalize_parameter_for_param_set_send (self , name , value , param_type ):
89+ '''uses param_type to convert value into a value suitable for passing
90+ into the mavlink param_set_send binding. Note that this
91+ is a copy of a method in pymavlink, in case the user has
92+ an older version of that library.
93+ '''
94+ if param_type is not None and param_type != mavutil .mavlink .MAV_PARAM_TYPE_REAL32 :
95+ # need to encode as a float for sending
96+ if param_type == mavutil .mavlink .MAV_PARAM_TYPE_UINT8 :
97+ vstr = struct .pack (">xxxB" , int (value ))
98+ elif param_type == mavutil .mavlink .MAV_PARAM_TYPE_INT8 :
99+ vstr = struct .pack (">xxxb" , int (value ))
100+ elif param_type == mavutil .mavlink .MAV_PARAM_TYPE_UINT16 :
101+ vstr = struct .pack (">xxH" , int (value ))
102+ elif param_type == mavutil .mavlink .MAV_PARAM_TYPE_INT16 :
103+ vstr = struct .pack (">xxh" , int (value ))
104+ elif param_type == mavutil .mavlink .MAV_PARAM_TYPE_UINT32 :
105+ vstr = struct .pack (">I" , int (value ))
106+ elif param_type == mavutil .mavlink .MAV_PARAM_TYPE_INT32 :
107+ vstr = struct .pack (">i" , int (value ))
108+ else :
109+ print ("can't send %s of type %u" % (name , param_type ))
110+ return None
111+ numeric_value , = struct .unpack (">f" , vstr )
112+ else :
113+ if isinstance (value , str ) and value .lower ().startswith ('0x' ):
114+ numeric_value = int (value [2 :], 16 )
115+ else :
116+ try :
117+ numeric_value = float (value )
118+ except ValueError :
119+ print (f"can't convert { name } ({ value } , { type (value )} ) to float" )
120+ return None
121+
122+ return numeric_value
123+
124+ def send_set (self ):
125+ numeric_value = self .normalize_parameter_for_param_set_send (self .name , self .value , self .param_type )
126+ if numeric_value is None :
127+ print (f"can't send { self .name } of type { self .param_type } " )
128+ self .attempts_remaining = 0
129+ return
130+ # print(f"Sending set attempts-remaining={self.attempts_remaining}")
131+ self .master .param_set_send (
132+ self .name .upper (),
133+ numeric_value ,
134+ parm_type = self .param_type ,
135+ )
136+ self .request_sent = time .time ()
137+ self .attempts_remaining -= 1
138+
139+ def expired (self ):
140+ if self .attempts_remaining > 0 :
141+ return False
142+ return time .time () - self .request_sent > self .retry_interval
143+
144+ def due_for_retry (self ):
145+ if self .attempts_remaining <= 0 :
146+ return False
147+ return time .time () - self .request_sent > self .retry_interval
148+
149+ def handle_PARAM_VALUE (self , m , value ):
150+ '''handle PARAM_VALUE packet m which has already been checked for a
151+ match against self.name. Returns true if this Set is now
152+ satisfied. value is the value extracted and potentially
153+ manipulated from the packet
154+ '''
155+ self .last_value_received = value
156+ if abs (value - float (self .value )) > 0.00001 :
157+ return False
158+
159+ return True
160+
161+ def print_expired_message (self ):
162+ reason = ""
163+ if self .last_value_received is None :
164+ reason = " (no PARAM_VALUE received)"
165+ else :
166+ reason = f" (invalid returned value { self .last_value_received } )"
167+ print (f"Failed to set { self .name } to { self .value } { reason } " )
168+
169+ def run_parameter_set_queue (self ):
170+ # firstly move anything from the input queue into our
171+ # collection of things to send:
172+ try :
173+ while True :
174+ new_parameter_to_set = self .parameters_to_set_input_queue .get (block = False )
175+ self .parameters_to_set [new_parameter_to_set .name ] = new_parameter_to_set
176+ except Empty :
177+ pass
178+
179+ # now send any parameter-sets which are due to be sent out,
180+ # either because they are new or because we need to retry:
181+ count = 0
182+ keys_to_remove = [] # remove entries after iterating the dict
183+ for (key , parameter_to_set ) in self .parameters_to_set .items ():
184+ if parameter_to_set .expired ():
185+ parameter_to_set .print_expired_message ()
186+ keys_to_remove .append (key )
187+ continue
188+ if not parameter_to_set .due_for_retry ():
189+ continue
190+ # send parameter set:
191+ parameter_to_set .send_set ()
192+ # rate-limit to 10 items per call:
193+ count += 1
194+ if count > 10 :
195+ break
196+
197+ # complete purging of expired parameter-sets:
198+ for key in keys_to_remove :
199+ del self .parameters_to_set [key ]
200+
59201 def use_ftp (self ):
60202 '''return true if we should try ftp for download'''
61203 if self .ftp_failed :
@@ -132,6 +274,16 @@ def handle_mavlink_packet(self, master, m):
132274 self .fetch_set = None
133275 if self .fetch_set is not None and len (self .fetch_set ) == 0 :
134276 self .fetch_check (master , force = True )
277+
278+ # if we were setting this parameter then check it's the
279+ # value we want and, if so, stop setting the parameter
280+ try :
281+ if self .parameters_to_set [param_id ].handle_PARAM_VALUE (m , value ):
282+ # print(f"removing set of param_id ({self.parameters_to_set[param_id].value} vs {value})")
283+ del self .parameters_to_set [param_id ]
284+ except KeyError :
285+ pass
286+
135287 elif m .get_type () == 'HEARTBEAT' :
136288 if m .get_srcComponent () == 1 :
137289 # remember autopilot types so we can handle PX4 parameters
@@ -493,7 +645,21 @@ def param_bitmask_modify(self, master, args):
493645 return
494646
495647 # Update the parameter
496- self .mav_param .mavset (master , uname , value , retries = 3 , parm_type = ptype )
648+ self .set_parameter (master , uname , value , attempts = 3 , parm_type = ptype )
649+
650+ def set_parameter (self , master , name , value , attempts = None , param_type = None ):
651+ '''convenient intermediate method which determines parameter type for
652+ lazy callers'''
653+ if param_type is None :
654+ param_type = self .param_types .get (name , None )
655+
656+ self .parameters_to_set_input_queue .put (ParamState .ParamSet (
657+ master ,
658+ name ,
659+ value ,
660+ attempts = attempts ,
661+ param_type = param_type ,
662+ ))
497663
498664 def param_revert (self , master , args ):
499665 '''handle param revert'''
@@ -519,10 +685,7 @@ def param_revert(self, master, args):
519685 if s1 == s2 :
520686 continue
521687 print ("Reverting %-16.16s %s -> %s" % (p , s1 , s2 ))
522- ptype = None
523- if p in self .param_types :
524- ptype = self .param_types [p ]
525- self .mav_param .mavset (master , p , defaults [p ], retries = 3 , parm_type = ptype )
688+ self .set_parameter (master , p , defaults [p ], attempts = 3 )
526689 count += 1
527690 print ("Reverted %u parameters" % count )
528691
@@ -596,10 +759,7 @@ def handle_command(self, master, mpstate, args):
596759 print ("Unable to find parameter '%s'" % param )
597760 return
598761 uname = param .upper ()
599- ptype = None
600- if uname in self .param_types :
601- ptype = self .param_types [uname ]
602- self .mav_param .mavset (master , uname , value , retries = 3 , parm_type = ptype )
762+ self .set_parameter (master , uname , value , attempts = 3 )
603763
604764 if (param .upper () == "WP_LOITER_RAD" or param .upper () == "LAND_BREAK_PATH" ):
605765 # need to redraw rally points
@@ -881,6 +1041,12 @@ def idle_task(self):
8811041 else :
8821042 self .menu_added_console = False
8831043
1044+ self .run_parameter_set_queues ()
1045+
1046+ def run_parameter_set_queues (self ):
1047+ for pstate in self .pstate .values ():
1048+ pstate .run_parameter_set_queue ()
1049+
8841050 def cmd_param (self , args ):
8851051 '''control parameters'''
8861052 self .check_new_target_system ()
0 commit comments