test_commands.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import math
  2. import unittest
  3. from motian_bridge import Bridge
  4. from motian_bridge.dialects.v20 import VKFly
  5. from test_bridge import FakeMqtt, FakeSerial, command, config
  6. def heartbeat(system=1, component=1):
  7. mav = VKFly.MAVLink(None, srcSystem=system, srcComponent=component)
  8. return mav.heartbeat_encode(
  9. VKFly.MAV_TYPE_QUADROTOR,
  10. VKFly.MAV_AUTOPILOT_GENERIC,
  11. 0,
  12. VKFly.VKFLY_CUSTOM_MODE_POSHOLD,
  13. VKFly.MAV_STATE_ACTIVE,
  14. ).pack(mav)
  15. def decode(frame):
  16. parser = VKFly.MAVLink(None)
  17. result = None
  18. for byte in frame:
  19. message = parser.parse_char(bytes((byte,)))
  20. if message is not None:
  21. result = message
  22. return result
  23. class CommandHandlerTests(unittest.TestCase):
  24. def setUp(self):
  25. self.cfg = config()
  26. self.mqtt = FakeMqtt()
  27. self.serial = FakeSerial()
  28. self.bridge = Bridge(self.cfg, self.mqtt, lambda *args, **kwargs: self.serial)
  29. self.bridge._serial = self.serial
  30. self.bridge._on_connect(self.mqtt, None, None, 0, None)
  31. self.bridge._telemetry.feed(heartbeat(42, 1), now=1)
  32. def deliver(self, data_type, data):
  33. self.bridge._on_message(self.mqtt, None, type("Message", (), {
  34. "topic": self.cfg.obtain_topic,
  35. "retain": False,
  36. "payload": command(data_type, data),
  37. })())
  38. def last(self):
  39. return decode(self.serial.written[-1])
  40. def test_get_all_params(self):
  41. self.deliver(281, None)
  42. message = self.last()
  43. self.assertEqual(message.get_type(), "PARAM_REQUEST_LIST")
  44. self.assertEqual((message.target_system, message.target_component), (42, 1))
  45. def test_direct_parameter_mappings(self):
  46. cases = [
  47. ("battery_low_threshold", 30, "VCAP1_LOW_VAL", VKFly.MAV_PARAM_TYPE_UINT8, 30),
  48. ("battery_critical_threshold", 20, "VCAP2_LOW_VAL", VKFly.MAV_PARAM_TYPE_UINT8, 20),
  49. ("height_limit", 120, "ALT_LIM_UP1", VKFly.MAV_PARAM_TYPE_UINT16, 120),
  50. ("distance_limit", 5000, "MAX_HOR_DIST", VKFly.MAV_PARAM_TYPE_UINT32, 5000),
  51. ("return_height", 80, "RTL_ALT_M", VKFly.MAV_PARAM_TYPE_REAL32, 80),
  52. ("return_speed", 8, "MC_XY_CRUISE", VKFly.MAV_PARAM_TYPE_REAL32, 8),
  53. ("obstacle_avoidance_distance", 5, "OBAVOID_DIST",
  54. VKFly.MAV_PARAM_TYPE_REAL32, 5),
  55. ("obstacle_avoidance_enable", 1, "OBAVOID_ACT",
  56. VKFly.MAV_PARAM_TYPE_UINT8, 1),
  57. ("obstacle_avoidance_switch", 0, "OBAVOID_ACT",
  58. VKFly.MAV_PARAM_TYPE_UINT8, 0),
  59. ]
  60. for name, value, expected_id, expected_type, expected_value in cases:
  61. with self.subTest(name=name):
  62. self.serial.written.clear()
  63. self.deliver(282, {"name": name, "type": 1, "value": value})
  64. message = self.last()
  65. self.assertEqual(message.get_type(), "PARAM_SET")
  66. self.assertEqual(message.param_id, expected_id)
  67. self.assertEqual(message.param_type, expected_type)
  68. self.assertEqual(message.param_value, expected_value)
  69. def test_string_parameter_mappings(self):
  70. self.deliver(282, {"name": "return_type", "type": 2, "value": "MissionReverse"})
  71. self.assertEqual(self.last().param_id, "RTL_EXEC_MODE")
  72. self.assertEqual(self.last().param_value, 2)
  73. self.deliver(282, {"name": "precision_land_mode", "type": 2,
  74. "value": "AprilTag"})
  75. self.assertEqual(self.last().param_id, "RTL_PLD_EN")
  76. self.assertEqual(self.last().param_value, 1)
  77. def test_todo_and_third_threshold_do_not_write(self):
  78. for name in ("battery_emergency_threshold", "geofence_action", "rtk_switch",
  79. "visual_sensor_enable", "unknown"):
  80. with self.subTest(name=name):
  81. before = len(self.serial.written)
  82. self.deliver(282, {"name": name, "type": 1, "value": 1})
  83. self.assertEqual(len(self.serial.written), before)
  84. def test_low_battery_action_preserves_other_bits(self):
  85. self.bridge._telemetry.params["FS_CONF_A"] = float(0xAB00)
  86. self.deliver(282, {"name": "low_battery_action", "type": 2,
  87. "value": "ReturnHome"})
  88. message = self.last()
  89. self.assertEqual(message.param_id, "FS_CONF_A")
  90. self.assertEqual(message.param_type, VKFly.MAV_PARAM_TYPE_UINT32)
  91. self.assertEqual(message.param_value, 0xAB22)
  92. def test_low_battery_action_requires_parameter_download(self):
  93. before = len(self.serial.written)
  94. self.deliver(282, {"name": "low_battery_action", "type": 2,
  95. "value": "ReturnHome"})
  96. self.assertEqual(len(self.serial.written), before)
  97. def test_param_value_is_cached(self):
  98. mav = VKFly.MAVLink(None, srcSystem=42, srcComponent=1)
  99. frame = mav.param_value_encode(
  100. b"FS_CONF_A", 4660, VKFly.MAV_PARAM_TYPE_UINT32, 1, 0
  101. ).pack(mav)
  102. self.bridge._telemetry.feed(frame, now=2)
  103. self.assertEqual(self.bridge.mavlink_param("FS_CONF_A"), 4660)
  104. def test_2050_false_arms_and_true_is_todo(self):
  105. self.deliver(2050, False)
  106. message = self.last()
  107. self.assertEqual(message.get_type(), "COMMAND_LONG")
  108. self.assertEqual(message.command, VKFly.MAV_CMD_COMPONENT_ARM_DISARM)
  109. self.assertEqual(message.param1, 1)
  110. before = len(self.serial.written)
  111. self.deliver(2050, True)
  112. self.assertEqual(len(self.serial.written), before)
  113. def test_takeoff_and_land(self):
  114. self.deliver(2054, None)
  115. takeoff = self.last()
  116. self.assertEqual(takeoff.command, VKFly.MAV_CMD_NAV_TAKEOFF)
  117. self.assertTrue(math.isnan(takeoff.param7))
  118. self.deliver(2055, None)
  119. land = self.last()
  120. self.assertEqual(land.command, VKFly.MAV_CMD_NAV_LAND)
  121. def test_reposition_command_int(self):
  122. self.deliver(2070, {
  123. "latitude": 33.3180863,
  124. "longitude": 120.0978796,
  125. "altitude": 61.52,
  126. "speed": 5.0,
  127. "heading": 30,
  128. })
  129. message = self.last()
  130. self.assertEqual(message.get_type(), "COMMAND_INT")
  131. self.assertEqual(message.command, VKFly.MAV_CMD_DO_REPOSITION)
  132. self.assertEqual(message.frame, VKFly.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT)
  133. self.assertEqual(message.param2, VKFly.MAV_DO_REPOSITION_FLAGS_CHANGE_MODE)
  134. self.assertEqual(message.x, 333180863)
  135. self.assertEqual(message.y, 1200978796)
  136. self.assertAlmostEqual(message.z, 61.52, places=4)
  137. def test_commands_wait_for_heartbeat(self):
  138. bridge = Bridge(self.cfg, self.mqtt, lambda *args, **kwargs: self.serial)
  139. bridge._serial = self.serial
  140. bridge._on_connect(self.mqtt, None, None, 0, None)
  141. before = len(self.serial.written)
  142. bridge._on_message(self.mqtt, None, type("Message", (), {
  143. "topic": self.cfg.obtain_topic,
  144. "retain": False,
  145. "payload": command(2054, None),
  146. })())
  147. self.assertEqual(len(self.serial.written), before)
  148. if __name__ == "__main__":
  149. unittest.main()