mrtautopilot 8__tar.gz → 9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mrtautopilot
3
- Version: 8
3
+ Version: 9
4
4
  Summary: Communicate with the MRT Autopilot Mavlink API
5
5
  Maintainer-email: Nathan Knotts <nathan@magothyrt.com>
6
6
  Project-URL: Homepage, https://www.magothyrt.com/
@@ -1,4 +1,4 @@
1
- __version__ = "8"
1
+ __version__ = "9"
2
2
 
3
3
  from .autopilot import *
4
4
  from .mission import *
@@ -3,12 +3,13 @@ import threading
3
3
  import multiprocessing
4
4
  import socket
5
5
  import asyncio
6
+ import datetime
6
7
  import logging
7
8
  import struct
8
9
  import uuid
9
10
  from dataclasses import dataclass
10
11
  from enum import Enum
11
- from typing import List, Union, Tuple
12
+ from typing import List, Union, Tuple, Callable
12
13
 
13
14
  import mrtmavlink
14
15
  from . import mission
@@ -267,6 +268,187 @@ class LowBandwidth:
267
268
  fault_response: Union[HealthResponse, None]
268
269
 
269
270
 
271
+ class GpsFixType(Enum):
272
+ NoGps = 0
273
+ NoFix = 1
274
+ Fix2d = 2
275
+ Fix3d = 3
276
+ Dgps = 4
277
+ RtkFloat = 5
278
+ RtkFixed = 6
279
+ Static = 7
280
+ Ppp = 8
281
+
282
+
283
+ @dataclass
284
+ class Gps2Raw:
285
+ def __init__(self, msg: mrtmavlink.MAVLink_gps2_raw_message):
286
+ self.timestamp = datetime.datetime.fromtimestamp(
287
+ msg.time_usec / 1e6, tz=datetime.timezone.utc
288
+ )
289
+ self.fix_type = GpsFixType(msg.fix_type)
290
+
291
+ if msg.lat != 0x7FFFFFFF:
292
+ self.latitude_deg = msg.lat / 1e7
293
+ else:
294
+ self.latitude_deg = None
295
+
296
+ if msg.lon != 0x7FFFFFFF:
297
+ self.longitude_deg = msg.lon / 1e7
298
+ else:
299
+ self.longitude_deg = None
300
+
301
+ if msg.alt != 0x7FFFFFF:
302
+ self.altitude_m = msg.alt / 1000
303
+ else:
304
+ self.altitude_m = None
305
+
306
+ if msg.eph != 0xFFFF:
307
+ self.hdop = msg.eph / 100
308
+ else:
309
+ self.hdop = None
310
+
311
+ if msg.epv != 0xFFFF:
312
+ self.vdop = msg.epv / 100
313
+ else:
314
+ self.vdop = None
315
+
316
+ if msg.vel != 0xFFFF:
317
+ self.velocity_mps = msg.vel / 100
318
+ else:
319
+ self.velocity_mps = None
320
+
321
+ if msg.cog != 0xFFFF:
322
+ self.course_deg = msg.cog / 100
323
+ else:
324
+ self.course_deg = None
325
+
326
+ if msg.satellites_visible != 0xFF:
327
+ self.num_set = msg.satellites_visible
328
+ else:
329
+ self.num_set = None
330
+
331
+ timestamp: datetime.datetime
332
+ fix_type: GpsFixType
333
+ latitude_deg: Union[float, None]
334
+ longitude_deg: Union[float, None]
335
+ altitude_m: Union[float, None]
336
+ hdop: Union[float, None]
337
+ vdop: Union[float, None]
338
+ velocity_mps: Union[float, None]
339
+ course_deg: Union[float, None]
340
+ num_set: Union[int, None]
341
+
342
+
343
+ @dataclass
344
+ class ParamUint8:
345
+ value: int
346
+
347
+
348
+ @dataclass
349
+ class ParamInt8:
350
+ value: int
351
+
352
+
353
+ @dataclass
354
+ class ParamUint16:
355
+ value: int
356
+
357
+
358
+ @dataclass
359
+ class ParamInt16:
360
+ value: int
361
+
362
+
363
+ @dataclass
364
+ class ParamUint32:
365
+ value: int
366
+
367
+
368
+ @dataclass
369
+ class ParamInt32:
370
+ value: int
371
+
372
+
373
+ @dataclass
374
+ class ParamReal32:
375
+ value: float
376
+
377
+
378
+ ParamValueType = Union[
379
+ ParamUint8,
380
+ ParamInt8,
381
+ ParamUint16,
382
+ ParamInt16,
383
+ ParamUint32,
384
+ ParamInt32,
385
+ ParamReal32,
386
+ ]
387
+
388
+
389
+ @dataclass
390
+ class ParamValue:
391
+ @staticmethod
392
+ def from_mavlink(msg: mrtmavlink.MAVLink_param_value_message):
393
+ buffer = struct.pack("<f", msg.param_value)
394
+ if msg.param_type == mrtmavlink.MAV_PARAM_TYPE_UINT8:
395
+ value = ParamUint8(int(struct.unpack("BBBB", buffer)[0]))
396
+ elif msg.param_type == mrtmavlink.MAV_PARAM_TYPE_INT8:
397
+ value = ParamInt8(int(struct.unpack("bBBB", buffer)[0]))
398
+ elif msg.param_type == mrtmavlink.MAV_PARAM_TYPE_UINT16:
399
+ value = ParamUint16(int(struct.unpack("<HBB", buffer)[0]))
400
+ elif msg.param_type == mrtmavlink.MAV_PARAM_TYPE_INT16:
401
+ value = ParamInt16(int(struct.unpack("<hBB", buffer)[0]))
402
+ elif msg.param_type == mrtmavlink.MAV_PARAM_TYPE_UINT32:
403
+ value = ParamUint32(int(struct.unpack("<I", buffer)[0]))
404
+ elif msg.param_type == mrtmavlink.MAV_PARAM_TYPE_INT32:
405
+ value = ParamInt32(int(struct.unpack("<i", buffer)[0]))
406
+ elif msg.param_type == mrtmavlink.MAV_PARAM_TYPE_REAL32:
407
+ value = ParamReal32(float(struct.unpack("<f", buffer)[0]))
408
+ else:
409
+ raise ValueError(f"Unsupported parameter type: {msg.param_type}")
410
+
411
+ return ParamValue(msg.param_id, value, msg.param_index, msg.param_count)
412
+
413
+ def to_mavlink(self) -> mrtmavlink.MAVLink_param_value_message:
414
+ if isinstance(self.value, ParamUint8):
415
+ typ = mrtmavlink.MAV_PARAM_TYPE_UINT8
416
+ value = struct.pack("BBBB", self.value.value, 0, 0, 0)
417
+ elif isinstance(self.value, ParamInt8):
418
+ typ = mrtmavlink.MAV_PARAM_TYPE_INT8
419
+ value = struct.pack("bBBB", self.value.value, 0, 0, 0)
420
+ elif isinstance(self.value, ParamUint16):
421
+ typ = mrtmavlink.MAV_PARAM_TYPE_UINT16
422
+ value = struct.pack("<HBB", self.value.value, 0, 0)
423
+ elif isinstance(self.value, ParamInt16):
424
+ typ = mrtmavlink.MAV_PARAM_TYPE_INT16
425
+ value = struct.pack("<hBB", self.value.value, 0, 0)
426
+ elif isinstance(self.value, ParamUint32):
427
+ typ = mrtmavlink.MAV_PARAM_TYPE_UINT32
428
+ value = struct.pack("<I", self.value.value)
429
+ elif isinstance(self.value, ParamInt32):
430
+ typ = mrtmavlink.MAV_PARAM_TYPE_INT32
431
+ value = struct.pack("<i", self.value.value)
432
+ elif isinstance(self.value, ParamReal32):
433
+ typ = mrtmavlink.MAV_PARAM_TYPE_REAL32
434
+ value = struct.pack("<f", self.value.value)
435
+ else:
436
+ raise ValueError(f"Unsupported parameter type: {type(self.value)}")
437
+
438
+ return mrtmavlink.MAVLink_param_value_message(
439
+ param_id=self.name.encode("utf-8"),
440
+ param_value=struct.unpack("f", value)[0],
441
+ param_type=typ,
442
+ param_index=self.param_idx,
443
+ param_count=self.num_param,
444
+ )
445
+
446
+ name: str
447
+ value: ParamValueType
448
+ param_idx: int
449
+ num_param: int
450
+
451
+
270
452
  class MavlinkThread:
271
453
  def __init__(
272
454
  self,
@@ -297,9 +479,12 @@ class MavlinkThread:
297
479
  self.remote_port = remote_port
298
480
  self.remote_address_list = [remote_address]
299
481
  if remote_address == "127.0.0.1":
300
- self.remote_address_list.extend(
301
- socket.gethostbyname_ex(socket.gethostname())[2]
302
- )
482
+ try:
483
+ self.remote_address_list.extend(
484
+ socket.gethostbyname_ex(socket.gethostname())[2]
485
+ )
486
+ except socket.gaierror:
487
+ logging.warning("Failed to get local IP address")
303
488
 
304
489
  self.is_started = False
305
490
  self.is_started_cv = threading.Condition()
@@ -324,6 +509,9 @@ class MavlinkThread:
324
509
  self.expected_cmd_uuid = 0
325
510
  self.ack_result: Union[int, None] = None
326
511
 
512
+ self.is_requesting_parameters = False
513
+ self.param_value: Union[ParamValue, None] = None
514
+
327
515
  def write(self, data: bytes):
328
516
  self.sock.sendto(data, (self.remote_address, self.remote_port))
329
517
 
@@ -363,15 +551,19 @@ class MavlinkThread:
363
551
  for msg in messages:
364
552
  self._msg_callback(msg)
365
553
 
554
+ async def _notify_param_result(self, result: ParamValue):
555
+ async with self.param_cond:
556
+ self.param_result = result
557
+ self.param_cond.notify()
558
+
366
559
  async def _notify_ack_result(self, result: int):
367
560
  async with self.ack_cond:
368
561
  self.ack_result = result
369
562
  self.ack_cond.notify()
370
563
 
371
564
  def _msg_callback(self, msg: mrtmavlink.MAVLink_message):
372
- logging.debug(f"Received: {msg.msgname}, addr")
373
-
374
565
  sys_id = msg.get_srcSystem()
566
+ logging.debug(f"Received: {msg.msgname}, sys_id={sys_id}, {msg}")
375
567
  if self.system_id != sys_id:
376
568
  self.system_id = msg.get_srcSystem()
377
569
  logging.info(f"Setting System ID to {sys_id}")
@@ -411,6 +603,10 @@ class MavlinkThread:
411
603
  ):
412
604
  logging.debug(f"Received Command Ack: {msg}")
413
605
  self.loop.create_task(self._notify_ack_result(msg.result))
606
+ elif type(msg) is mrtmavlink.MAVLink_param_value_message:
607
+ param = ParamValue.from_mavlink(msg)
608
+ logging.debug(f"Received Param Value: {param}")
609
+ self.loop.create_task(self._notify_param_result(param))
414
610
 
415
611
  def send_heartbeat(self):
416
612
  self.loop.call_soon_threadsafe(lambda: self._send_heartbeat())
@@ -421,55 +617,150 @@ class MavlinkThread:
421
617
  mrtmavlink.MAV_TYPE_GCS, mrtmavlink.MAV_AUTOPILOT_INVALID, 0, 0, 0
422
618
  )
423
619
 
620
+ def get_parameter(
621
+ self,
622
+ id: str,
623
+ callback: Callable[[Union[ParamValue, None]], None],
624
+ ):
625
+ self.loop.create_task(self._get_parameter(id, callback))
626
+
627
+ async def _get_parameter(
628
+ self,
629
+ id: str,
630
+ callback: Callable[[Union[ParamValue, None]], None],
631
+ ):
632
+ assert len(id) <= 16
633
+ if self.system_id is None:
634
+ logging.info("Failed to get parameter, system_id not set")
635
+ return
636
+
637
+ if self.is_requesting_parameters:
638
+ logging.warning("Already requesting parameters")
639
+ callback(None)
640
+ return
641
+
642
+ RETRY_PERIOD_S = 0.25
643
+ try:
644
+ self.is_requesting_parameters = True
645
+
646
+ logging.info(f"Getting Param {id}")
647
+
648
+ self.param_result = None
649
+ self.conn.param_request_read_send(
650
+ self.system_id,
651
+ mrtmavlink.MAV_COMP_ID_AUTOPILOT1,
652
+ id.encode("utf-8"),
653
+ -1,
654
+ )
655
+ try:
656
+ async with self.param_cond:
657
+ await asyncio.wait_for(self.param_cond.wait(), RETRY_PERIOD_S)
658
+ except asyncio.TimeoutError:
659
+ logging.error("Timeout waiting for parameter ack")
660
+ callback(None)
661
+ return
662
+
663
+ if self.param_result is not None:
664
+ if self.param_result.name == id:
665
+ callback(self.param_result)
666
+ else:
667
+ logging.warning(
668
+ f"Set parameter {id} but got {self.param_result.name}"
669
+ )
670
+ callback(None)
671
+ else:
672
+ logging.warning(f"Set parameter {id} but got None")
673
+ callback(None)
674
+ finally:
675
+ self.is_requesting_parameters = False
676
+
424
677
  def set_parameter(
425
678
  self,
426
679
  id: str,
427
- value: float,
428
- typ: int,
429
- component: int = mrtmavlink.MAV_COMP_ID_AUTOPILOT1,
680
+ value: ParamValueType,
681
+ callback: Callable[[Union[ParamValue, None]], None] = lambda x: None,
430
682
  ):
431
- self.loop.call_soon_threadsafe(
432
- lambda: self._set_parameter(id, value, typ, component)
433
- )
683
+ self.loop.create_task(self._set_parameter(id, value, callback))
434
684
 
435
- def _set_parameter(self, id: str, value: float, typ: int, component: int):
685
+ async def _set_parameter(
686
+ self,
687
+ id: str,
688
+ value: ParamValueType,
689
+ callback: Callable[[Union[ParamValue, None]], None],
690
+ ):
436
691
  assert len(id) <= 16
437
- if not self.system_id:
692
+ if self.system_id is None:
438
693
  logging.info("Failed to set parameter, system_id not set")
439
694
  return
440
695
 
441
- logging.info(f"Setting Param {id} to {value}")
442
-
443
- if typ == mrtmavlink.MAV_PARAM_TYPE_UINT8:
444
- val_bytes = struct.pack("BBBB", int(value), 0, 0, 0)
445
- elif typ == mrtmavlink.MAV_PARAM_TYPE_INT8:
446
- val_bytes = struct.pack("bBBB", int(value), 0, 0, 0)
447
- elif typ == mrtmavlink.MAV_PARAM_TYPE_UINT16:
448
- val_bytes = struct.pack("<HBB", int(value), 0, 0)
449
- elif typ == mrtmavlink.MAV_PARAM_TYPE_INT16:
450
- val_bytes = struct.pack("<hBB", int(value), 0, 0)
451
- elif typ == mrtmavlink.MAV_PARAM_TYPE_UINT32:
452
- val_bytes = struct.pack("<I", int(value))
453
- elif typ == mrtmavlink.MAV_PARAM_TYPE_INT32:
454
- val_bytes = struct.pack("<i", int(value))
455
- elif typ == mrtmavlink.MAV_PARAM_TYPE_REAL32:
456
- val_bytes = struct.pack("<f", value)
457
- else:
458
- assert False, "unsupported type"
459
-
460
- self.conn.param_set_send(
461
- self.system_id,
462
- component,
463
- id.encode("utf-8"),
464
- struct.unpack("f", val_bytes)[0],
465
- typ,
466
- )
696
+ if self.is_requesting_parameters:
697
+ logging.warning("Already requesting parameters")
698
+ callback(None)
699
+ return
700
+
701
+ RETRY_PERIOD_S = 0.25
702
+ try:
703
+ self.is_requesting_parameters = True
704
+
705
+ logging.info(f"Setting Param {id} to {value}")
706
+
707
+ if isinstance(value, ParamUint8):
708
+ val_bytes = struct.pack("BBBB", int(value.value), 0, 0, 0)
709
+ typ = mrtmavlink.MAV_PARAM_TYPE_UINT8
710
+ elif isinstance(value, ParamInt8):
711
+ val_bytes = struct.pack("bBBB", int(value.value), 0, 0, 0)
712
+ typ = mrtmavlink.MAV_PARAM_TYPE_INT8
713
+ elif isinstance(value, ParamUint16):
714
+ val_bytes = struct.pack("<HBB", int(value.value), 0, 0)
715
+ typ = mrtmavlink.MAV_PARAM_TYPE_UINT16
716
+ elif isinstance(value, ParamInt16):
717
+ val_bytes = struct.pack("<hBB", int(value.value), 0, 0)
718
+ typ = mrtmavlink.MAV_PARAM_TYPE_INT16
719
+ elif isinstance(value, ParamUint32):
720
+ val_bytes = struct.pack("<I", int(value.value))
721
+ typ = mrtmavlink.MAV_PARAM_TYPE_UINT32
722
+ elif isinstance(value, ParamInt32):
723
+ val_bytes = struct.pack("<i", int(value.value))
724
+ typ = mrtmavlink.MAV_PARAM_TYPE_INT32
725
+ elif isinstance(value, ParamReal32):
726
+ val_bytes = struct.pack("<f", value.value)
727
+ typ = mrtmavlink.MAV_PARAM_TYPE_REAL32
728
+ else:
729
+ assert False, "unsupported type"
730
+
731
+ self.param_result = None
732
+ self.conn.param_set_send(
733
+ self.system_id,
734
+ mrtmavlink.MAV_COMP_ID_AUTOPILOT1,
735
+ id.encode("utf-8"),
736
+ struct.unpack("f", val_bytes)[0],
737
+ typ,
738
+ )
739
+ try:
740
+ async with self.param_cond:
741
+ await asyncio.wait_for(self.param_cond.wait(), RETRY_PERIOD_S)
742
+ except asyncio.TimeoutError:
743
+ logging.error("Timeout waiting for parameter ack")
744
+ callback(None)
745
+ return
746
+
747
+ if self.param_result is not None:
748
+ if self.param_result.name == id:
749
+ callback(self.param_result)
750
+ else:
751
+ logging.warning(
752
+ f"Set parameter {id} but got {self.param_result.name}"
753
+ )
754
+ callback(None)
755
+ else:
756
+ logging.warning(f"Set parameter {id} but got None")
757
+ callback(None)
758
+ finally:
759
+ self.is_requesting_parameters = False
467
760
 
468
761
  def set_motor_enablement(self, enable: bool):
469
762
  logging.info(f"Setting Motor Enablement to {enable}")
470
- self.set_parameter(
471
- "MOTOR_ENABLE", 1 if enable else 0, mrtmavlink.MAV_PARAM_TYPE_UINT8
472
- )
763
+ self.set_parameter("MOTOR_ENABLE", ParamUint8(1 if enable else 0))
473
764
 
474
765
  async def _run(self):
475
766
  with self.is_started_cv:
@@ -478,6 +769,7 @@ class MavlinkThread:
478
769
 
479
770
  self.loop = asyncio.get_running_loop()
480
771
  self.ack_cond = asyncio.Condition()
772
+ self.param_cond = asyncio.Condition()
481
773
  await self.loop.create_datagram_endpoint(lambda: self, sock=self.sock) # type: ignore
482
774
 
483
775
  while not self.is_done:
@@ -491,11 +783,57 @@ class MavlinkThread:
491
783
  def _send_system_time(self):
492
784
  self.conn.system_time_send(int(time.time() * 1e6), 0)
493
785
 
786
+ def _send_param_request_list(self):
787
+ self.conn.param_request_list_send(
788
+ self.system_id, # target_system
789
+ mrtmavlink.MAV_COMP_ID_AUTOPILOT1, # target_component
790
+ )
791
+
792
+ def request_parameters(self, callback: Callable[[List[ParamValue]], None]):
793
+ self.loop.create_task(self._request_parameters(callback))
794
+
795
+ async def _request_parameters(self, callback: Callable[[List[ParamValue]], None]):
796
+ if self.system_id is None:
797
+ logging.warning("System ID not set, not requesting parameters")
798
+ return
799
+
800
+ if self.is_requesting_parameters:
801
+ logging.warning("Already requesting parameters")
802
+ return
803
+
804
+ RETRY_PERIOD_S = 0.25
805
+ params: List[ParamValue] = []
806
+ try:
807
+ self.is_requesting_parameters = True
808
+ self._send_param_request_list()
809
+ while True:
810
+ try:
811
+ self.param_result = None
812
+ async with self.param_cond:
813
+ await asyncio.wait_for(self.param_cond.wait(), RETRY_PERIOD_S)
814
+
815
+ if self.param_result is not None:
816
+ params.append(self.param_result)
817
+ if (
818
+ self.param_result.param_idx
819
+ == self.param_result.num_param - 1
820
+ ):
821
+ break
822
+ except asyncio.TimeoutError:
823
+ logging.error("Timeout waiting for parameter")
824
+ break
825
+
826
+ finally:
827
+ self.is_requesting_parameters = False
828
+ self.param_result = None
829
+
830
+ callback(params)
831
+
494
832
  def send_autopilot_stop(self):
495
833
  self.loop.call_soon_threadsafe(lambda: self._send_autopilot_stop())
496
834
 
497
835
  def _send_autopilot_stop(self):
498
- if not self.system_id:
836
+ if self.system_id is None:
499
837
  logging.warning("System ID not set, not sending STOP")
500
838
  return
501
839
  logging.info("Sending STOP")
@@ -532,7 +870,7 @@ class MavlinkThread:
532
870
  )
533
871
 
534
872
  def _send_waypoint(self, lat_deg: float, lon_deg: float, speed_mps: float):
535
- if not self.system_id:
873
+ if self.system_id is None:
536
874
  logging.warning("System ID not set, not sending waypoint")
537
875
  return
538
876
  logging.info(
@@ -559,7 +897,7 @@ class MavlinkThread:
559
897
  )
560
898
 
561
899
  def _send_loiter(self, speed_mps: float, radius_m: float, duration_s: float):
562
- if not self.system_id:
900
+ if self.system_id is None:
563
901
  logging.warning("System ID not set, not sending loiter")
564
902
  return
565
903
  logging.info(
@@ -707,7 +1045,7 @@ class MavlinkThread:
707
1045
  )
708
1046
 
709
1047
  for retry_num in range(max_retries):
710
- logging.debug(f"Set mission index: ({retry_num+1}/{max_retries})")
1048
+ logging.debug(f"Set mission index: ({retry_num + 1}/{max_retries})")
711
1049
 
712
1050
  self.ack_result = None
713
1051
  self.conn.send(cmd)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mrtautopilot
3
- Version: 8
3
+ Version: 9
4
4
  Summary: Communicate with the MRT Autopilot Mavlink API
5
5
  Maintainer-email: Nathan Knotts <nathan@magothyrt.com>
6
6
  Project-URL: Homepage, https://www.magothyrt.com/
File without changes
File without changes
File without changes
File without changes