ソースを参照

Add emergency motor stop command forwarding

LiuYang 1 週間 前
親
コミット
04256529a8
3 ファイル変更、53 行追加、0 行削除
  1. 4 0
      README.md
  2. 26 0
      motian_bridge/commands.py
  3. 23 0
      tests/test_commands.py

+ 4 - 0
README.md

@@ -48,6 +48,10 @@ Bridge 在收到飞控 HEARTBEAT 并锁定目标 system/component 后,支持
 | 2054 | 无人机起飞 | `COMMAND_LONG / MAV_CMD_NAV_TAKEOFF`,高度 `NaN` 表示使用飞控默认值 |
 | 2055 | 无人机降落 | `COMMAND_LONG / MAV_CMD_NAV_LAND` |
 | 2070 | 指点飞行 | `COMMAND_INT / MAV_CMD_DO_REPOSITION`,相对高度坐标系 |
+| 2073 | 高空紧急停桨(停桨后开伞) | `COMMAND_LONG / MAV_CMD_DO_PARACHUTE`,`param1=PARACHUTE_RELEASE`("Release parachute and kill motors") |
+| 2074 | 地面紧急停桨(停桨后不开伞) | `COMMAND_LONG / MAV_CMD_COMPONENT_ARM_DISARM`,`param1=0`(上锁)、`param2=21196`(强制),不触发开伞 |
+
+`#2073` 的映射采用飞控通用的 `MAV_CMD_DO_PARACHUTE`/`PARACHUTE_RELEASE`(开伞并停桨);`#2074` 采用强制上锁 `MAV_CMD_COMPONENT_ARM_DISARM`(`param2=21196`)停桨而不开伞,避免 `MAV_CMD_DO_FLIGHTTERMINATION` 在多旋翼上可能触发的开伞。若 VKFly 有专用时序命令,需与飞控方确认后调整。
 
 #282 已确认的参数映射:
 

+ 26 - 0
motian_bridge/commands.py

@@ -93,6 +93,8 @@ class FlightCommandHandlers:
         self.bridge.register_handler(2054, self.takeoff)
         self.bridge.register_handler(2055, self.land)
         self.bridge.register_handler(2070, self.reposition)
+        self.bridge.register_handler(2073, self.emergency_stop_with_parachute)
+        self.bridge.register_handler(2074, self.emergency_stop_motors)
 
     def _target(self) -> tuple[int, int]:
         source = self.bridge.mavlink_target
@@ -177,6 +179,18 @@ class FlightCommandHandlers:
             1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
         ))
 
+    def emergency_stop_with_parachute(self, command: BaseMessage, bridge: Bridge) -> None:
+        """DataType 2073: high-altitude emergency motor stop, then parachute release."""
+        if command.data is not None:
+            raise ValueError("DataType 2073 Data must be null")
+        system, component = self._target()
+        # VKFly MAV_CMD_DO_PARACHUTE with PARACHUTE_RELEASE means
+        # "Release parachute and kill motors", i.e. stop propellers and deploy.
+        self._send(self.encoder.command_long_encode(
+            system, component, VKFly.MAV_CMD_DO_PARACHUTE, 0,
+            float(VKFly.PARACHUTE_RELEASE), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+        ))
+
     def takeoff(self, command: BaseMessage, bridge: Bridge) -> None:
         if command.data is not None:
             raise ValueError("DataType 2054 Data must be null")
@@ -187,6 +201,18 @@ class FlightCommandHandlers:
             raise ValueError("DataType 2055 Data must be null")
         self._send_command_long(VKFly.MAV_CMD_NAV_LAND)
 
+    def emergency_stop_motors(self, command: BaseMessage, bridge: Bridge) -> None:
+        """DataType 2074: ground emergency motor stop without parachute."""
+        if command.data is not None:
+            raise ValueError("DataType 2074 Data must be null")
+        system, component = self._target()
+        # Forced disarm (MAV_CMD_COMPONENT_ARM_DISARM param2=21196) kills the
+        # motors without triggering the parachute that DataType 2073 uses.
+        self._send(self.encoder.command_long_encode(
+            system, component, VKFly.MAV_CMD_COMPONENT_ARM_DISARM, 0,
+            0.0, 21196.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+        ))
+
     def _send_command_long(self, mav_command: int, *, param7: float = math.nan) -> None:
         system, component = self._target()
         nan = math.nan

+ 23 - 0
tests/test_commands.py

@@ -138,6 +138,29 @@ class CommandHandlerTests(unittest.TestCase):
         land = self.last()
         self.assertEqual(land.command, VKFly.MAV_CMD_NAV_LAND)
 
+    def test_emergency_stop_with_parachute(self):
+        self.deliver(2073, None)
+        message = self.last()
+        self.assertEqual(message.get_type(), "COMMAND_LONG")
+        self.assertEqual(message.command, VKFly.MAV_CMD_DO_PARACHUTE)
+        self.assertEqual(message.param1, VKFly.PARACHUTE_RELEASE)
+        self.assertEqual((message.target_system, message.target_component), (42, 1))
+        before = len(self.serial.written)
+        self.deliver(2073, {})   # Data must be null
+        self.assertEqual(len(self.serial.written), before)
+
+    def test_emergency_stop_motors_ground(self):
+        self.deliver(2074, None)
+        message = self.last()
+        self.assertEqual(message.get_type(), "COMMAND_LONG")
+        self.assertEqual(message.command, VKFly.MAV_CMD_COMPONENT_ARM_DISARM)
+        self.assertEqual(message.param1, 0)          # disarm
+        self.assertEqual(message.param2, 21196)      # force
+        self.assertEqual((message.target_system, message.target_component), (42, 1))
+        before = len(self.serial.written)
+        self.deliver(2074, {})   # Data must be null
+        self.assertEqual(len(self.serial.written), before)
+
     def test_reposition_command_int(self):
         self.deliver(2070, {
             "latitude": 33.3180863,