From 43d1d1469dce392969d2c8b80ebd4db5b62630ae Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Tue, 14 Jan 2025 23:39:43 -0300 Subject: [PATCH 1/4] autopilot manager: add Unmanaged board for ethernet connections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Mário Lago <58235456+joaomariolago@users.noreply.github.com> --- .../api/v1/routers/endpoints.py | 10 ++++++ .../ardupilot_manager/autopilot_manager.py | 34 +++++++++++++++++-- .../flight_controller_detector/Detector.py | 5 ++- core/services/ardupilot_manager/typedefs.py | 3 ++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/core/services/ardupilot_manager/api/v1/routers/endpoints.py b/core/services/ardupilot_manager/api/v1/routers/endpoints.py index 7b5d6f6635..4115d06de1 100644 --- a/core/services/ardupilot_manager/api/v1/routers/endpoints.py +++ b/core/services/ardupilot_manager/api/v1/routers/endpoints.py @@ -34,3 +34,13 @@ async def remove_endpoints(endpoints: Set[Endpoint] = Body(...)) -> Any: @endpoints_router_v1.put("/", status_code=status.HTTP_200_OK) async def update_endpoints(endpoints: Set[Endpoint] = Body(...)) -> Any: await autopilot.update_endpoints(endpoints) + + +@endpoints_router_v1.post("/manual_board_master_endpoint", summary="Set the master endpoint for an manual board.") +async def set_manual_board_master_endpoint(endpoint: Endpoint) -> bool: + return await autopilot.set_manual_board_master_endpoint(endpoint) + + +@endpoints_router_v1.get("/manual_board_master_endpoint", summary="Get the master endpoint for an manual board.") +def get_manual_board_master_endpoint() -> Any: + return autopilot.get_manual_board_master_endpoint() diff --git a/core/services/ardupilot_manager/autopilot_manager.py b/core/services/ardupilot_manager/autopilot_manager.py index 34bc254889..edb5805185 100644 --- a/core/services/ardupilot_manager/autopilot_manager.py +++ b/core/services/ardupilot_manager/autopilot_manager.py @@ -390,6 +390,12 @@ def load_preferred_router(self) -> Optional[str]: def get_available_routers(self) -> List[str]: return [router.name() for router in self.mavlink_manager.available_interfaces()] + async def start_manual_board(self, board: FlightController) -> None: + self._current_board = board + self.master_endpoint = self.get_manual_board_master_endpoint() + self.ardupilot_subprocess = None + await self.start_mavlink_manager(self.master_endpoint) + async def start_sitl(self) -> None: self._current_board = BoardDetector.detect_sitl() if not self.firmware_manager.is_firmware_installed(self._current_board): @@ -486,9 +492,9 @@ def get_board_to_be_used(self, boards: List[FlightController]) -> FlightControll # SITL should only be used if explicitly set by user, in which case it's a preferred board and the # previous return logic will get it. We do this to prevent the user thinking that it's physical board # is correctly running when in fact it was SITL automatically starting. - real_boards = [board for board in boards if board.type != PlatformType.SITL] + real_boards = [board for board in boards if board.type not in [PlatformType.SITL, PlatformType.Manual]] if not real_boards: - raise RuntimeError("Only available board is SITL, and it wasn't explicitly chosen.") + raise RuntimeError("No physical board detected and SITL/Manual board aren't explicitly chosen.") real_boards.sort(key=lambda board: board.platform) return real_boards[0] @@ -584,6 +590,8 @@ async def start_ardupilot(self) -> None: await self.start_serial(flight_controller) elif flight_controller.platform == Platform.SITL: await self.start_sitl() + elif flight_controller.platform == Platform.Manual: + await self.start_manual_board(flight_controller) else: raise RuntimeError(f"Invalid board type: {flight_controller}") finally: @@ -662,3 +670,25 @@ async def install_firmware_from_url( async def restore_default_firmware(self, board: FlightController) -> None: await self.firmware_manager.restore_default_firmware(board) + + async def set_manual_board_master_endpoint(self, endpoint: Endpoint) -> bool: + self.configuration["manual_board_master_endpoint"] = endpoint.as_dict() + self.settings.save(self.configuration) + self._save_current_endpoints() + await self.mavlink_manager.restart() + return True + + def get_manual_board_master_endpoint(self) -> Endpoint: + default_master_endpoint = Endpoint( + name="Manual Board Master Endpoint", + owner=self.settings.app_name, + connection_type=EndpointType.UDPServer, + place="0.0.0.0", + argument=14551, + persistent=True, + enabled=True, + ) + endpoint = self.configuration.get("manual_board_master_endpoint", None) + if endpoint is None: + return default_master_endpoint + return Endpoint(**endpoint) diff --git a/core/services/ardupilot_manager/flight_controller_detector/Detector.py b/core/services/ardupilot_manager/flight_controller_detector/Detector.py index a1753a14b8..6eba0fdc1b 100644 --- a/core/services/ardupilot_manager/flight_controller_detector/Detector.py +++ b/core/services/ardupilot_manager/flight_controller_detector/Detector.py @@ -77,7 +77,7 @@ def detect_sitl() -> FlightController: return FlightController(name="SITL", manufacturer="ArduPilot Team", platform=Platform.SITL) @classmethod - async def detect(cls, include_sitl: bool = True) -> List[FlightController]: + async def detect(cls, include_sitl: bool = True, include_manual: bool = True) -> List[FlightController]: """Return a list of available flight controllers Arguments: @@ -99,4 +99,7 @@ async def detect(cls, include_sitl: bool = True) -> List[FlightController]: if include_sitl: available.append(Detector.detect_sitl()) + if include_manual: + available.append(FlightController(name="Manual", manufacturer="Manual", platform=Platform.Manual)) + return available diff --git a/core/services/ardupilot_manager/typedefs.py b/core/services/ardupilot_manager/typedefs.py index 94bcf03348..e5f7efd68c 100644 --- a/core/services/ardupilot_manager/typedefs.py +++ b/core/services/ardupilot_manager/typedefs.py @@ -97,6 +97,7 @@ class PlatformType(LowerStringEnum): Linux = auto() SITL = auto() Unknown = auto() + Manual = auto() class Platform(str, Enum): @@ -113,6 +114,7 @@ class Platform(str, Enum): Navigator64 = "navigator64" Argonot = "argonot" SITL = get_sitl_platform_name(machine()) + Manual = "Manual" @property def type(self) -> PlatformType: @@ -127,6 +129,7 @@ def type(self) -> PlatformType: Platform.Navigator64: PlatformType.Linux, Platform.Argonot: PlatformType.Linux, Platform.SITL: PlatformType.SITL, + Platform.Manual: PlatformType.Manual, } return platform_types.get(self, PlatformType.Unknown) From 268a54d678cb509c7d24d1e0dff314fe9831edbe Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Wed, 15 Jan 2025 12:08:17 -0300 Subject: [PATCH 2/4] frontend: add master endpoint management interface --- .../autopilot/MasterEndpointManager.vue | 217 ++++++++++++++++++ core/frontend/src/views/Autopilot.vue | 17 +- 2 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 core/frontend/src/components/autopilot/MasterEndpointManager.vue diff --git a/core/frontend/src/components/autopilot/MasterEndpointManager.vue b/core/frontend/src/components/autopilot/MasterEndpointManager.vue new file mode 100644 index 0000000000..4c4d79f3f6 --- /dev/null +++ b/core/frontend/src/components/autopilot/MasterEndpointManager.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/core/frontend/src/views/Autopilot.vue b/core/frontend/src/views/Autopilot.vue index 0239c09f22..141879286d 100644 --- a/core/frontend/src/views/Autopilot.vue +++ b/core/frontend/src/views/Autopilot.vue @@ -44,7 +44,17 @@
- + + + + Master endpoint + + + + + + + Firmware update @@ -127,6 +137,7 @@ import { import AutopilotSerialConfiguration from '@/components/autopilot/AutopilotSerialConfiguration.vue' import BoardChangeDialog from '@/components/autopilot/BoardChangeDialog.vue' import FirmwareManager from '@/components/autopilot/FirmwareManager.vue' +import MasterEndpointManager from '@/components/autopilot/MasterEndpointManager.vue' import NotSafeOverlay from '@/components/common/NotSafeOverlay.vue' import { MavAutopilot } from '@/libs/MAVLink2Rest/mavlink2rest-ts/messages/mavlink2rest-enum' import Notifier from '@/libs/notifier' @@ -148,6 +159,7 @@ export default Vue.extend({ FirmwareManager, AutopilotSerialConfiguration, NotSafeOverlay, + MasterEndpointManager, }, data() { return { @@ -201,6 +213,9 @@ export default Vue.extend({ } return ['Navigator', 'Navigator64', 'SITL'].includes(boardname) }, + is_external_board(): boolean { + return autopilot.current_board?.name === 'Manual' + }, current_board(): FlightController | null { return autopilot.current_board }, From 9992570604643773a98c3859f6c2e4e365fdc0f5 Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Wed, 15 Jan 2025 21:47:27 -0300 Subject: [PATCH 3/4] frontend: autopilot manager: only show buttons when it makes sense to do so --- core/frontend/src/views/Autopilot.vue | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/frontend/src/views/Autopilot.vue b/core/frontend/src/views/Autopilot.vue index 141879286d..d209a52be7 100644 --- a/core/frontend/src/views/Autopilot.vue +++ b/core/frontend/src/views/Autopilot.vue @@ -88,7 +88,7 @@ Change board { let version = 'Unknown' if (this.firmware_info) { From f1b6ed592909a82e1d4c55aa348fab940f0a779e Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Wed, 28 May 2025 17:01:21 -0300 Subject: [PATCH 4/4] ardupilot_manager: allow updating master endpoint --- core/services/ardupilot_manager/autopilot_manager.py | 2 +- .../ardupilot_manager/mavlink_proxy/AbstractRouter.py | 4 ++++ core/services/ardupilot_manager/mavlink_proxy/Manager.py | 8 +++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/services/ardupilot_manager/autopilot_manager.py b/core/services/ardupilot_manager/autopilot_manager.py index edb5805185..1ee761a36c 100644 --- a/core/services/ardupilot_manager/autopilot_manager.py +++ b/core/services/ardupilot_manager/autopilot_manager.py @@ -674,7 +674,7 @@ async def restore_default_firmware(self, board: FlightController) -> None: async def set_manual_board_master_endpoint(self, endpoint: Endpoint) -> bool: self.configuration["manual_board_master_endpoint"] = endpoint.as_dict() self.settings.save(self.configuration) - self._save_current_endpoints() + self.mavlink_manager.master_endpoint = endpoint await self.mavlink_manager.restart() return True diff --git a/core/services/ardupilot_manager/mavlink_proxy/AbstractRouter.py b/core/services/ardupilot_manager/mavlink_proxy/AbstractRouter.py index 0e5f554342..598bbfeac9 100644 --- a/core/services/ardupilot_manager/mavlink_proxy/AbstractRouter.py +++ b/core/services/ardupilot_manager/mavlink_proxy/AbstractRouter.py @@ -94,6 +94,10 @@ def version(self) -> Optional[str]: def master_endpoint(self) -> Optional[Endpoint]: return self._master_endpoint + @master_endpoint.setter + def master_endpoint(self, master_endpoint: Endpoint) -> None: + self._master_endpoint = master_endpoint + async def start(self, master_endpoint: Endpoint) -> None: self._master_endpoint = master_endpoint command = self.assemble_command(self._master_endpoint) diff --git a/core/services/ardupilot_manager/mavlink_proxy/Manager.py b/core/services/ardupilot_manager/mavlink_proxy/Manager.py index 3a0de47336..95317f3e4d 100644 --- a/core/services/ardupilot_manager/mavlink_proxy/Manager.py +++ b/core/services/ardupilot_manager/mavlink_proxy/Manager.py @@ -114,6 +114,10 @@ def _reset_endpoints(self) -> None: def master_endpoint(self) -> Optional[Endpoint]: return self.tool.master_endpoint + @master_endpoint.setter + def master_endpoint(self, master_endpoint: Endpoint) -> None: + self.tool.master_endpoint = master_endpoint + async def start(self, master_endpoint: Endpoint) -> None: # If the tool is already running, don't start it again to avoid port conflicts if self.should_be_running and await self.is_running(): @@ -151,8 +155,10 @@ async def stop(self) -> None: self.should_be_running = False await self.tool.exit() - async def restart(self) -> None: + async def restart(self, master_endpoint: Optional[Endpoint] = None) -> None: self.should_be_running = False + if master_endpoint: + self.tool.master_endpoint = master_endpoint await self.tool.restart() self._last_valid_endpoints = self.endpoints() self.should_be_running = True