├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bugs.yml │ └── ui-corrections.yml └── workflows │ ├── sync-submodules.yml │ └── test-and-deploy.yml ├── .gitignore ├── .gitmodules ├── .hooks └── pre-push ├── CoC.md ├── LICENSE-BlueOS-Custom.md ├── LICENSE-agplv3.md ├── LICENSE.md ├── README.md ├── bootstrap ├── Dockerfile ├── README.md ├── bootstrap │ ├── __init__.py │ ├── bootstrap.py │ └── setup.py ├── main.py ├── pip.conf ├── startup.json.default └── test_bootstrap.py ├── core ├── .dockerignore ├── .python-version ├── Dockerfile ├── compose │ ├── compose.yml │ └── workspace │ │ ├── blueos │ │ └── .gitignore │ │ ├── config │ │ └── .gitignore │ │ ├── logs │ │ └── .gitignore │ │ └── userdata │ │ └── .gitignore ├── configuration │ ├── motd │ └── tmux.conf ├── frontend │ ├── .eslintrc.js │ ├── .gitignore │ ├── bun.lockb │ ├── index.html │ ├── package.json │ ├── public │ │ ├── assets │ │ │ └── vehicles │ │ │ │ ├── images │ │ │ │ ├── bb120.png │ │ │ │ └── bluerov2.png │ │ │ │ └── models │ │ │ │ ├── boat │ │ │ │ ├── UNDEFINED.glb │ │ │ │ └── UNDEFINED.json │ │ │ │ ├── rover │ │ │ │ └── unknown.glb │ │ │ │ └── sub │ │ │ │ ├── BLUEROV1.glb │ │ │ │ ├── BLUEROV1.json │ │ │ │ ├── SIMPLEROV_3.glb │ │ │ │ ├── SIMPLEROV_3.json │ │ │ │ ├── SIMPLEROV_4.glb │ │ │ │ ├── SIMPLEROV_4.json │ │ │ │ ├── SIMPLEROV_5.glb │ │ │ │ ├── VECTORED.glb │ │ │ │ ├── VECTORED.json │ │ │ │ ├── VECTORED_6DOF.glb │ │ │ │ ├── VECTORED_6DOF.json │ │ │ │ └── bluerov.glb │ │ ├── favicon.ico │ │ └── img │ │ │ └── icons │ │ │ ├── apple-touch-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ └── motordetection.svg │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ ├── img │ │ │ │ ├── banners │ │ │ │ │ ├── ArduPilot.svg │ │ │ │ │ ├── OpenPilot.svg │ │ │ │ │ └── PX4.svg │ │ │ │ ├── blue-robotics-logo.svg │ │ │ │ ├── blueos-logo-blue.svg │ │ │ │ ├── blueos-logo-white.svg │ │ │ │ ├── configuration │ │ │ │ │ ├── camera │ │ │ │ │ │ └── gimbal-pitch.svg │ │ │ │ │ └── failsafes │ │ │ │ │ │ ├── battery.svg │ │ │ │ │ │ ├── crash.svg │ │ │ │ │ │ ├── ekf.svg │ │ │ │ │ │ ├── heartbeat.svg │ │ │ │ │ │ ├── leak.svg │ │ │ │ │ │ ├── pilot-input.svg │ │ │ │ │ │ ├── pressure.svg │ │ │ │ │ │ └── temperature.svg │ │ │ │ ├── devicePathHelper │ │ │ │ │ ├── navigator.svg │ │ │ │ │ ├── rpi3b.svg │ │ │ │ │ ├── rpi4b.svg │ │ │ │ │ └── rpi5.svg │ │ │ │ ├── icons │ │ │ │ │ ├── RX.svg │ │ │ │ │ ├── TX.svg │ │ │ │ │ └── pi.svg │ │ │ │ ├── ping │ │ │ │ │ ├── Ping1D.gif │ │ │ │ │ └── Ping360.gif │ │ │ │ └── wip-video.svg │ │ │ └── vehicles │ │ │ │ └── images │ │ │ │ └── unknown.svg │ │ ├── components │ │ │ ├── app │ │ │ │ ├── Alerter.vue │ │ │ │ ├── BackendStatusChecker.vue │ │ │ │ ├── DnsConfigurationMenu.vue │ │ │ │ ├── ImagePicker.vue │ │ │ │ ├── InternetTrayMenu.vue │ │ │ │ ├── NetworkInterfaceMenu.vue │ │ │ │ ├── NetworkInterfacePriorityMenu.vue │ │ │ │ ├── NewVersionNotificator.vue │ │ │ │ ├── PirateModeMenu.vue │ │ │ │ ├── PirateModeTrayMenu.vue │ │ │ │ ├── PowerMenu.vue │ │ │ │ ├── ReportMenu.vue │ │ │ │ ├── SettingsMenu.vue │ │ │ │ ├── SystemCheckerTrayMenu.vue │ │ │ │ ├── ThemeTrayMenu.vue │ │ │ │ ├── VehicleBanner.vue │ │ │ │ ├── VehicleRebootMenu.vue │ │ │ │ └── VehicleRebootRequiredTrayMenu.vue │ │ │ ├── autopilot │ │ │ │ ├── AutopilotManagerUpdater.ts │ │ │ │ ├── AutopilotSerialConfiguration.vue │ │ │ │ ├── BoardChangeDialog.vue │ │ │ │ ├── EndpointCard.vue │ │ │ │ ├── EndpointCreationDialog.vue │ │ │ │ ├── EndpointManager.vue │ │ │ │ ├── FirmwareManager.vue │ │ │ │ └── MasterEndpointManager.vue │ │ │ ├── beacon │ │ │ │ └── BeaconTrayMenu.vue │ │ │ ├── bridges │ │ │ │ ├── BridgeCard.vue │ │ │ │ ├── BridgeCreationDialog.vue │ │ │ │ ├── Bridget.vue │ │ │ │ └── BridgetUpdater.vue │ │ │ ├── cloud │ │ │ │ └── CloudTrayMenu.vue │ │ │ ├── common │ │ │ │ ├── DefaultTooltip.vue │ │ │ │ ├── DevicePathHelper.vue │ │ │ │ ├── JsonEditor.vue │ │ │ │ ├── NotSafeOverlay.vue │ │ │ │ ├── ParameterSwitch.vue │ │ │ │ ├── PasswordInput.vue │ │ │ │ ├── SpinningLogo.vue │ │ │ │ ├── StatusTextWatcher.vue │ │ │ │ └── rebootRequiredOverlay.vue │ │ │ ├── ethernet │ │ │ │ ├── AddressCreationDialog.vue │ │ │ │ ├── AddressDeletionDialog.vue │ │ │ │ ├── DHCPServerDialog.vue │ │ │ │ ├── EthernetManager.vue │ │ │ │ ├── EthernetTrayMenu.vue │ │ │ │ ├── EthernetUpdater.vue │ │ │ │ └── InterfaceCard.vue │ │ │ ├── health │ │ │ │ ├── GpsTrayMenu.vue │ │ │ │ ├── HealthTrayMenu.vue │ │ │ │ └── SelfHealthTest.vue │ │ │ ├── kraken │ │ │ │ ├── BackAlleyTab.vue │ │ │ │ ├── BazaarTab.vue │ │ │ │ ├── KrakenManager.ts │ │ │ │ ├── Utils.ts │ │ │ │ ├── cards │ │ │ │ │ ├── InstalledExtensionCard.vue │ │ │ │ │ └── StoreExtensionCard.vue │ │ │ │ └── modals │ │ │ │ │ ├── ExtensionCreationModal.vue │ │ │ │ │ ├── ExtensionDetailsModal.vue │ │ │ │ │ └── ExtensionSettingsModal.vue │ │ │ ├── logs │ │ │ │ └── LogManager.vue │ │ │ ├── mavlink │ │ │ │ └── MavlinkUpdater.vue │ │ │ ├── nmea-injector │ │ │ │ ├── NMEAInjector.vue │ │ │ │ ├── NMEASocketCard.vue │ │ │ │ └── NMEASocketCreationDialog.vue │ │ │ ├── notifications │ │ │ │ ├── ConfigMenu.vue │ │ │ │ ├── NotificationCard.vue │ │ │ │ ├── NotificationManager.vue │ │ │ │ └── TrayButton.vue │ │ │ ├── parameter-editor │ │ │ │ ├── InlineParameterEditor.vue │ │ │ │ ├── ParameterEditor.vue │ │ │ │ ├── ParameterEditorDialog.vue │ │ │ │ ├── ParameterLabel.vue │ │ │ │ ├── ParameterLoader.vue │ │ │ │ └── ServoFunctionEditorDialog.vue │ │ │ ├── ping │ │ │ │ ├── ping1d.vue │ │ │ │ └── ping360.vue │ │ │ ├── scanner │ │ │ │ └── availableServicesTable.vue │ │ │ ├── speedtest │ │ │ │ ├── Graph.vue │ │ │ │ ├── InternetSpeedTest.vue │ │ │ │ └── NetworkSpeedTest.vue │ │ │ ├── system-information │ │ │ │ ├── AboutThisSystem.vue │ │ │ │ ├── Firmware.vue │ │ │ │ ├── Kernel.vue │ │ │ │ ├── Network.vue │ │ │ │ ├── NetworkCard.vue │ │ │ │ ├── Processes.vue │ │ │ │ ├── SystemCondition.vue │ │ │ │ └── SystemConditionCard.vue │ │ │ ├── utils │ │ │ │ ├── BrIframe.vue │ │ │ │ ├── ParameterLoadingSpinner.vue │ │ │ │ ├── PullProgress.vue │ │ │ │ ├── RebootButton.vue │ │ │ │ └── themedSVG.vue │ │ │ ├── vehiclesetup │ │ │ │ ├── Configure.vue │ │ │ │ ├── MotorDetection.vue │ │ │ │ ├── PwmSetup.vue │ │ │ │ ├── SetupOverview.vue │ │ │ │ ├── calibration.ts │ │ │ │ ├── configuration │ │ │ │ │ ├── accelerometer │ │ │ │ │ │ ├── ArdupilotAccelerometerSetup.vue │ │ │ │ │ │ ├── FullAccelerometerCalibration.vue │ │ │ │ │ │ └── QuickAccelerometerCalibration.vue │ │ │ │ │ ├── camera.vue │ │ │ │ │ ├── common.ts │ │ │ │ │ ├── compass │ │ │ │ │ │ ├── ArdupilotMavlinkCompassSetup.vue │ │ │ │ │ │ ├── AutoCoordinateDetector.vue │ │ │ │ │ │ ├── CalibrationQualityIndicator.vue │ │ │ │ │ │ ├── CompassDisplay.vue │ │ │ │ │ │ ├── CompassLearn.vue │ │ │ │ │ │ ├── CompassMaskPicker.vue │ │ │ │ │ │ ├── CompassParams.vue │ │ │ │ │ │ ├── FullCompassCalibrator.vue │ │ │ │ │ │ └── LargeVehicleCompassCalibrator.vue │ │ │ │ │ ├── failsafes │ │ │ │ │ │ ├── FailsafeCard.vue │ │ │ │ │ │ ├── Failsafes.vue │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── lights.vue │ │ │ │ ├── overview │ │ │ │ │ ├── BaroCalib.vue │ │ │ │ │ ├── GyroCalib.vue │ │ │ │ │ ├── LeakInfo.vue │ │ │ │ │ ├── LightsInfo.vue │ │ │ │ │ ├── OnboardSensors.vue │ │ │ │ │ ├── ParamSets.vue │ │ │ │ │ ├── PingInfo.vue │ │ │ │ │ ├── PowerInfo.vue │ │ │ │ │ ├── VehicleInfo.vue │ │ │ │ │ ├── VideoOverview.vue │ │ │ │ │ ├── common.ts │ │ │ │ │ └── gripper.vue │ │ │ │ └── viewers │ │ │ │ │ ├── GenericViewer.vue │ │ │ │ │ └── VehicleViewer.vue │ │ │ ├── version-chooser │ │ │ │ ├── DockerLogin.vue │ │ │ │ ├── VersionCard.vue │ │ │ │ └── VersionChooser.vue │ │ │ ├── video-manager │ │ │ │ ├── VideoControlsDialog.vue │ │ │ │ ├── VideoDevice.vue │ │ │ │ ├── VideoDiagnosticHelper.vue │ │ │ │ ├── VideoManager.vue │ │ │ │ ├── VideoStream.vue │ │ │ │ ├── VideoStreamCreationDialog.vue │ │ │ │ ├── VideoThumbnail.vue │ │ │ │ └── VideoUpdater.vue │ │ │ ├── wifi │ │ │ │ ├── ConnectionDialog.vue │ │ │ │ ├── DisconnectionDialog.vue │ │ │ │ ├── WifiManager.vue │ │ │ │ ├── WifiNetworkCard.vue │ │ │ │ ├── WifiSettingsDialog.vue │ │ │ │ ├── WifiTrayMenu.vue │ │ │ │ └── WifiUpdater.vue │ │ │ ├── wizard │ │ │ │ ├── ActionStepper.vue │ │ │ │ ├── DefaultParamLoader.vue │ │ │ │ ├── RequireInternet.vue │ │ │ │ ├── ScriptLoader.vue │ │ │ │ └── Wizard.vue │ │ │ └── zenoh-inspector │ │ │ │ └── ZenohInspector.vue │ │ ├── cosmos.ts │ │ ├── libs │ │ │ ├── MAVLink2Rest │ │ │ │ ├── Endpoint.ts │ │ │ │ ├── Listener.ts │ │ │ │ ├── MessageID.ts │ │ │ │ └── index.ts │ │ │ ├── filebrowser.ts │ │ │ ├── firmware │ │ │ │ └── ardupilot │ │ │ │ │ └── ardusub.ts │ │ │ ├── message-manager.ts │ │ │ ├── notifier.ts │ │ │ └── settings.ts │ │ ├── main.ts │ │ ├── menus.ts │ │ ├── one-more-time.ts │ │ ├── plugins │ │ │ └── vuetify.ts │ │ ├── router │ │ │ └── index.ts │ │ ├── store │ │ │ ├── autopilot.ts │ │ │ ├── autopilot_manager.ts │ │ │ ├── bag.ts │ │ │ ├── beacon.ts │ │ │ ├── bridget.ts │ │ │ ├── commander.ts │ │ │ ├── ethernet.ts │ │ │ ├── frontend.ts │ │ │ ├── helper.ts │ │ │ ├── index.ts │ │ │ ├── mavlink.ts │ │ │ ├── nmea-injector.ts │ │ │ ├── notifications.ts │ │ │ ├── ping.ts │ │ │ ├── settings.ts │ │ │ ├── system-information.ts │ │ │ ├── video.ts │ │ │ └── wifi.ts │ │ ├── style │ │ │ ├── colors │ │ │ │ ├── blue_robotics.js │ │ │ │ ├── default.js │ │ │ │ └── vuetify.js │ │ │ └── css │ │ │ │ ├── animations.css │ │ │ │ └── vuetify-global.css │ │ ├── types │ │ │ ├── autopilot.ts │ │ │ ├── autopilot │ │ │ │ ├── parameter-fetcher.ts │ │ │ │ ├── parameter-rover-enums.ts │ │ │ │ ├── parameter-sub-enums.ts │ │ │ │ ├── parameter-table.ts │ │ │ │ ├── parameter.ts │ │ │ │ └── px4 │ │ │ │ │ └── metadata-fetcher.ts │ │ │ ├── beacon.ts │ │ │ ├── bridges.ts │ │ │ ├── commander.ts │ │ │ ├── common.ts │ │ │ ├── ethernet.ts │ │ │ ├── filebrowser.ts │ │ │ ├── frontend_services.ts │ │ │ ├── helper.ts │ │ │ ├── json-viewer.d.ts │ │ │ ├── kraken.ts │ │ │ ├── mavlink.ts │ │ │ ├── nmea-injector.ts │ │ │ ├── notifications.ts │ │ │ ├── parameter_repository.d.ts │ │ │ ├── ping.ts │ │ │ ├── shims-general.ts │ │ │ ├── shims-tsx.d.ts │ │ │ ├── shims-vue.d.ts │ │ │ ├── shims-vuetify.d.ts │ │ │ ├── system-information │ │ │ │ ├── kernel.ts │ │ │ │ ├── model.ts │ │ │ │ ├── netstat.ts │ │ │ │ ├── platform.ts │ │ │ │ ├── serial.ts │ │ │ │ └── system.ts │ │ │ ├── version-chooser.ts │ │ │ ├── video.ts │ │ │ ├── vuetify.ts │ │ │ └── wifi.ts │ │ ├── utils │ │ │ ├── api.ts │ │ │ ├── ardupilot_mavlink.ts │ │ │ ├── deviceid_decoder.ts │ │ │ ├── helper_functions.ts │ │ │ ├── mavlink.ts │ │ │ ├── mavlink_math.ts │ │ │ ├── mavlink_prettifier.ts │ │ │ ├── networking.ts │ │ │ ├── pattern_validators.ts │ │ │ ├── pull_tracker.ts │ │ │ ├── shaders.ts │ │ │ ├── streaming.ts │ │ │ ├── update_time.ts │ │ │ ├── version_chooser.ts │ │ │ ├── video.ts │ │ │ └── wifi.ts │ │ ├── views │ │ │ ├── Autopilot.vue │ │ │ ├── AvailableServicesView.vue │ │ │ ├── BagEditorView.vue │ │ │ ├── BridgesView.vue │ │ │ ├── EndpointView.vue │ │ │ ├── ExtensionManagerView.vue │ │ │ ├── ExtensionView.vue │ │ │ ├── FileBrowserView.vue │ │ │ ├── LogView.vue │ │ │ ├── MainView.vue │ │ │ ├── MavlinkInspectorView.vue │ │ │ ├── NMEAInjectorView.vue │ │ │ ├── NetworkTestView.vue │ │ │ ├── PageNotFound.vue │ │ │ ├── ParameterEditorView.vue │ │ │ ├── Pings.vue │ │ │ ├── SystemInformationView.vue │ │ │ ├── TerminalView.vue │ │ │ ├── VehicleSetupView.vue │ │ │ ├── VersionChooser.vue │ │ │ ├── VideoManagerView.vue │ │ │ └── ZenohInspectorView.vue │ │ └── widgets │ │ │ ├── Cpu.vue │ │ │ ├── CpuPie.vue │ │ │ ├── Disk.vue │ │ │ └── Networking.vue │ ├── tsconfig.json │ ├── vite.config.js │ └── yarn.lock ├── libs │ ├── bridges │ │ ├── README.md │ │ ├── pyproject.toml │ │ └── src │ │ │ └── bridges │ │ │ ├── __init__.py │ │ │ ├── bridges.py │ │ │ ├── py.typed │ │ │ └── serialhelper.py │ └── commonwealth │ │ ├── README.md │ │ ├── pyproject.toml │ │ └── src │ │ └── commonwealth │ │ ├── __init__.py │ │ ├── mavlink_comm │ │ ├── MavlinkComm.py │ │ ├── VehicleManager.py │ │ ├── __init__.py │ │ ├── exceptions.py │ │ └── typedefs.py │ │ ├── py.typed │ │ ├── settings │ │ ├── __init__.py │ │ ├── bases │ │ │ ├── __init__.py │ │ │ ├── pydantic_base.py │ │ │ └── pykson_base.py │ │ ├── exceptions.py │ │ ├── manager.py │ │ ├── managers │ │ │ ├── __init__.py │ │ │ ├── pydantic_manager.py │ │ │ └── pykson_manager.py │ │ ├── settings.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ ├── test_manager.py │ │ │ ├── test_manager_pydantic.py │ │ │ ├── test_settings.py │ │ │ └── test_settings_pydantic.py │ │ └── utils │ │ ├── DHCPDiscovery.py │ │ ├── DHCPServerManager.py │ │ ├── Singleton.py │ │ ├── __init__.py │ │ ├── apis.py │ │ ├── commands.py │ │ ├── decorators.py │ │ ├── general.py │ │ ├── logs.py │ │ ├── sentry_config.py │ │ ├── streaming.py │ │ └── tests │ │ ├── __init__.py │ │ └── test_decorators.py ├── pyproject.toml ├── run-service.sh ├── services │ ├── ardupilot_manager │ │ ├── .gitignore │ │ ├── README.md │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── app.py │ │ │ ├── static │ │ │ │ └── .gitkeep │ │ │ ├── v1 │ │ │ │ └── routers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── endpoints.py │ │ │ │ │ └── index.py │ │ │ └── v2 │ │ │ │ └── routers │ │ │ │ ├── __init__.py │ │ │ │ └── index.py │ │ ├── args.py │ │ ├── autopilot_manager.py │ │ ├── exceptions.py │ │ ├── firmware │ │ │ ├── FirmwareDownload.py │ │ │ ├── FirmwareInstall.py │ │ │ ├── FirmwareManagement.py │ │ │ ├── FirmwareUpload.py │ │ │ ├── __init__.py │ │ │ ├── test_FirmwareDownload.py │ │ │ └── test_FirmwareInstall.py │ │ ├── flight_controller_detector │ │ │ ├── Detector.py │ │ │ ├── __init__.py │ │ │ ├── board_identification.py │ │ │ └── linux │ │ │ │ ├── __init__.py │ │ │ │ ├── argonot.py │ │ │ │ ├── detector.py │ │ │ │ ├── linux_boards.py │ │ │ │ └── navigator.py │ │ ├── main.py │ │ ├── mavlink_proxy │ │ │ ├── AbstractRouter.py │ │ │ ├── Endpoint.py │ │ │ ├── MAVLinkRouter.py │ │ │ ├── MAVLinkServer.py │ │ │ ├── MAVP2P.py │ │ │ ├── MAVProxy.py │ │ │ ├── Manager.py │ │ │ ├── __init__.py │ │ │ ├── exceptions.py │ │ │ ├── main.py │ │ │ └── test_all.py │ │ ├── pyproject.toml │ │ ├── settings.py │ │ └── typedefs.py │ ├── bag_of_holding │ │ ├── main.py │ │ └── pyproject.toml │ ├── beacon │ │ ├── default-settings.json │ │ ├── main.py │ │ ├── pyproject.toml │ │ ├── settings.py │ │ └── typedefs.py │ ├── bridget │ │ ├── bridget.py │ │ ├── main.py │ │ ├── pyproject.toml │ │ └── settings.py │ ├── cable_guy │ │ ├── .gitignore │ │ ├── README.md │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── dns.py │ │ │ ├── manager.py │ │ │ └── settings.py │ │ ├── config.py │ │ ├── main.py │ │ ├── networksetup.py │ │ ├── pyproject.toml │ │ ├── typedefs.py │ │ └── typedefs_pydantic_network_shin.py │ ├── commander │ │ ├── main.py │ │ └── pyproject.toml │ ├── helper │ │ ├── .gitignore │ │ ├── README.md │ │ ├── main.py │ │ ├── nginx_parser.py │ │ └── pyproject.toml │ ├── kraken │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── app.py │ │ │ ├── static │ │ │ │ ├── assets │ │ │ │ │ └── logo.png │ │ │ │ └── pages │ │ │ │ │ └── root.html │ │ │ ├── v1 │ │ │ │ └── routers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── extension.py │ │ │ │ │ └── index.py │ │ │ └── v2 │ │ │ │ └── routers │ │ │ │ ├── __init__.py │ │ │ │ ├── container.py │ │ │ │ ├── extension.py │ │ │ │ ├── index.py │ │ │ │ ├── jobs.py │ │ │ │ └── manifest.py │ │ ├── args.py │ │ ├── config.py │ │ ├── extension │ │ │ ├── __init__.py │ │ │ ├── exceptions.py │ │ │ ├── extension.py │ │ │ └── models.py │ │ ├── harbor │ │ │ ├── __init__.py │ │ │ ├── container.py │ │ │ ├── contexts.py │ │ │ ├── exceptions.py │ │ │ └── models.py │ │ ├── jobs │ │ │ ├── __init__.py │ │ │ ├── exceptions.py │ │ │ ├── jobs.py │ │ │ └── models.py │ │ ├── kraken.py │ │ ├── main.py │ │ ├── manifest │ │ │ ├── __init__.py │ │ │ ├── exceptions.py │ │ │ ├── manifest.py │ │ │ └── models.py │ │ ├── pyproject.toml │ │ ├── settings.py │ │ └── utils.py │ ├── log_zipper │ │ ├── main.py │ │ └── pyproject.toml │ ├── nmea_injector │ │ ├── README.md │ │ ├── main.py │ │ ├── nmea_injector │ │ │ ├── MavlinkNMEA.py │ │ │ ├── TrafficController.py │ │ │ ├── __init__.py │ │ │ ├── exceptions.py │ │ │ ├── settings.py │ │ │ └── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_MavlinkNMEA.py │ │ │ │ └── test_TrafficController.py │ │ └── pyproject.toml │ ├── pardal │ │ ├── README.md │ │ ├── main.py │ │ └── pyproject.toml │ ├── ping │ │ ├── exceptions.py │ │ ├── main.py │ │ ├── ping1d_driver.py │ │ ├── ping1d_mavlink.py │ │ ├── ping360_driver.py │ │ ├── ping360_ethernet_driver.py │ │ ├── ping360_ethernet_prober.py │ │ ├── pingdriver.py │ │ ├── pingmanager.py │ │ ├── pingprober.py │ │ ├── pingutils.py │ │ ├── portwatcher.py │ │ ├── pyproject.toml │ │ ├── settings.py │ │ └── typedefs.py │ ├── versionchooser │ │ ├── .gitignore │ │ ├── docker_login.py │ │ ├── frontend │ │ │ ├── index.html │ │ │ └── static │ │ │ │ └── css │ │ │ │ └── style.css │ │ ├── main.py │ │ ├── openapi │ │ │ └── versionchooser.yaml │ │ ├── pyproject.toml │ │ ├── test_versionchooser.py │ │ └── utils │ │ │ ├── __init__.py │ │ │ ├── chooser.py │ │ │ └── dockerhub.py │ └── wifi │ │ ├── .gitignore │ │ ├── exceptions.py │ │ ├── frontend │ │ └── index.html │ │ ├── main.py │ │ ├── pyproject.toml │ │ ├── settings.py │ │ ├── typedefs.py │ │ └── wifi_handlers │ │ ├── AbstractWifiHandler.py │ │ ├── __init__.py │ │ ├── networkmanager │ │ └── networkmanager.py │ │ └── wpa_supplicant │ │ ├── Hotspot.py │ │ ├── WifiManager.py │ │ ├── __init__.py │ │ └── wpa_supplicant.py ├── start-blueos-core ├── tools │ ├── ardupilot_tools │ │ ├── bootstrap.sh │ │ └── setup-python-libs.sh │ ├── blueos_startup_update │ │ ├── blueos_startup_update.py │ │ └── bootstrap.sh │ ├── bridges │ │ └── bootstrap.sh │ ├── filebrowser │ │ └── bootstrap.sh │ ├── install-python-libs.sh │ ├── install-static-binaries.sh │ ├── install-system-tools.sh │ ├── linux2rest │ │ └── bootstrap.sh │ ├── logviewer │ │ └── bootstrap.sh │ ├── machineid │ │ └── bootstrap.sh │ ├── mavlink2rest │ │ └── bootstrap.sh │ ├── mavlink_camera_manager │ │ └── bootstrap.sh │ ├── mavlink_server │ │ └── bootstrap.sh │ ├── nginx │ │ ├── cors.conf │ │ └── nginx.conf │ ├── scripts │ │ ├── bootstrap.sh │ │ └── red-pill │ ├── ttyd │ │ └── bootstrap.sh │ ├── wifi │ │ └── bootstrap.sh │ └── zenoh │ │ ├── blueos-zenoh.json5 │ │ └── bootstrap.sh └── uv.lock ├── deploy ├── README.md ├── deploy.sh ├── expand_fs.sh ├── github-runner │ ├── README.md │ └── blueos-runner.sh ├── pimod │ ├── README.md │ └── blueos.Pifile └── wpa_supplicant.conf ├── doc ├── blueboat.png ├── bluerov.png └── dashboard.png └── install ├── README.md ├── boards ├── bcm_2712.sh ├── bcm_27xx.sh ├── bcm_28xx.sh ├── config.toml └── configure_board.sh ├── configs ├── blueos.service └── journald.conf ├── install.sh ├── kraken └── set_default_extensions.sh ├── network ├── avahi.sh └── blueos.service ├── overlays └── spi0-led.dts └── udev └── 100.autopilot.rules /.github/ISSUE_TEMPLATE/ui-corrections.yml: -------------------------------------------------------------------------------- 1 | name: Improve User Interface or Experience 2 | description: Suggestion to improve UI/UX. 3 | title: "core: frontend: " 4 | labels: ["core", "enhancement", "triage", "ui"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to suggest improvements to our interface! Please fill out the following details so we can respond and act efficiently. 10 | 11 | - type: textarea 12 | attributes: 13 | label: Current behaviour 14 | description: Describe what you see, why that is bad, and any issues it is causing. 15 | placeholder: | 16 | Example: "The small margins between the buttons make it harder to distinguish between them, and looks unusual" 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: Expected or desired behaviour 23 | description: Explain what you expect or would like to see instead, and why. 24 | placeholder: | 25 | Example: "An extra margin or space between the buttons may help" 26 | validations: 27 | required: true 28 | 29 | - type: checkboxes 30 | id: terms 31 | attributes: 32 | label: Prerequisites 33 | description: Please confirm the following (and check the boxes) before submitting. 34 | options: 35 | - label: I have checked to make sure that a similar request has not already been filed or fixed. 36 | required: true -------------------------------------------------------------------------------- /.github/workflows/sync-submodules.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | # Run at 11:11 (UTC) every Monday 4 | - cron: '11 11 * * 1' 5 | # Allow running manually, from the Actions tab or through the GitHub HTTP API 6 | workflow_dispatch: 7 | 8 | name: Sync Submodules 9 | jobs: 10 | sync: 11 | runs-on: ubuntu-latest 12 | 13 | permissions: 14 | contents: write 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | - name: Git submodule update 23 | run: | 24 | git submodule update --init --recursive --remote 25 | 26 | - name: Commit update 27 | run: | 28 | git config --global user.name 'Git bot' 29 | git config --global user.email 'bot@noreply.github.com' 30 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} 31 | git commit -am "submodules: Updated references" && git push || echo "No changes to commit" 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | htmlcov/ 3 | build/ 4 | *.egg-info 5 | dist/ 6 | node_modules 7 | core/logs 8 | 9 | # Avoid local python development 10 | *__pycache__ 11 | *.pytest_cache 12 | *__mypycache__ 13 | env/ 14 | Pipfile 15 | Pipfile.lock 16 | poetry.lock 17 | registry/ 18 | 19 | # Docker compose binds 20 | core/compose/workspace/ 21 | 22 | # macOS filesystem tracking 23 | .DS_Store 24 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "core/frontend/src/ArduPilot-Parameter-Repository"] 2 | path = core/frontend/public/assets/ArduPilot-Parameter-Repository 3 | url = https://github.com/ArduPilot/ParameterRepository 4 | [submodule "core/frontend/src/libs/MAVLink2Rest/mavlink2rest-ts"] 5 | path = core/frontend/src/libs/MAVLink2Rest/mavlink2rest-ts 6 | url = https://github.com/patrickelectric/mavlink2rest-ts.git 7 | [submodule "core/frontend/src/components/vue-tour"] 8 | path = core/frontend/src/components/vue-tour 9 | url = https://github.com/pulsardev/vue-tour 10 | [submodule "core/frontend/src/PX4-parameters"] 11 | path = core/frontend/src/PX4-parameters 12 | url = https://github.com/patrickelectric/PX4-parameters.git 13 | -------------------------------------------------------------------------------- /LICENSE-BlueOS-Custom.md: -------------------------------------------------------------------------------- 1 | Get in touch for more information: software@bluerobotics.com -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This work is dual-licensed under GPLv3 and BlueOS Custom License (get in touch for more information). 2 | You can choose between one of them if you use this work. 3 | 4 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-BlueOS-Custom -------------------------------------------------------------------------------- /bootstrap/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.7-slim-bullseye 2 | 3 | COPY startup.json.default bootstrap/ /bootstrap/ 4 | COPY main.py / 5 | COPY pip.conf /etc/ 6 | RUN python3 bootstrap/setup.py install 7 | ENTRYPOINT ["/main.py"] -------------------------------------------------------------------------------- /bootstrap/README.md: -------------------------------------------------------------------------------- 1 | # BlueOS-Bootstrap 2 | 3 | Bootstrap is responsible for setting things up for the first run of BlueOS and applying docker bind updates. 4 | 5 | ### Running BlueOS 6 | If you want to run BlueOS, please check the [install script](../install/README.md) or [docker compose](../README.md). 7 | 8 | ### Build: 9 | You can run the following command to build it: 10 | 11 | ```bash 12 | cd bootstrap 13 | docker build . --tag bluerobotics/bootstrap:master 14 | ``` 15 | 16 | ### Usage: 17 | 18 | Everytime it is launched, it will load the startup settings, wait until the core container is not running, and re-create and re-launch it. 19 | 20 | ```bash 21 | docker run -it -v /var/run/docker.sock:/var/run/docker.sock -v /config:/config bluerobotics/bootstrap:master 22 | ``` 23 | 24 | This will automatically populate /config if there is no valid config file in there, fetch if necessary, and then launch BlueOS. 25 | -------------------------------------------------------------------------------- /bootstrap/bootstrap/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/bootstrap/bootstrap/__init__.py -------------------------------------------------------------------------------- /bootstrap/bootstrap/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name="blueos_bootstrap", 7 | version="0.0.1", 8 | description="Blue Robotics Ardusub BlueOS Docker System Bootstrap", 9 | license="MIT", 10 | py_modules=[], 11 | install_requires=[ 12 | "docker==5.0.0", 13 | "loguru==0.5.3", 14 | "requests==2.26.0", 15 | # indirect dependencies 16 | "six==1.15.0", 17 | "idna==3.4", 18 | "urllib3==1.26.16", 19 | "certifi==2023.7.22", 20 | "charset-normalizer==2.0.12", 21 | "websocket-client==1.6.3", 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /bootstrap/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | import docker 7 | from loguru import logger 8 | from bootstrap.bootstrap import Bootstrapper 9 | 10 | 11 | if __name__ == "__main__": 12 | version = os.environ.get("GIT_DESCRIBE_TAGS", None) 13 | logger.add("/var/logs/blueos/services/bootstrap/bootstrap_{time}.log", enqueue=True, rotation="30 minutes") 14 | logger.info(f"Running BlueOS Bootstrap {version}") 15 | if os.environ.get("BLUEOS_CONFIG_PATH", None) is None: 16 | logger.info("Please supply the host path for the config files as the BLUEOS_CONFIG_PATH environment variable.") 17 | logger.info("Example docker command line:") 18 | logger.info( 19 | "docker run -it -v /var/run/docker.sock:/var/run/docker.sock" 20 | " -v $HOME/.config/blueos:" 21 | " -v /var/logs/blueos:/var/logs/blueos" 22 | "/root/.config/blueos -e BLUEOS_CONFIG_PATH=$HOME/.config/blueos" 23 | "bluerobotics/blueos-bootstrap:master" 24 | ) 25 | sys.exit(1) 26 | 27 | bootstrapper = Bootstrapper(docker.client.from_env()) 28 | bootstrapper.run() 29 | -------------------------------------------------------------------------------- /bootstrap/pip.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | extra-index-url=https://www.piwheels.org/simple 3 | -------------------------------------------------------------------------------- /core/.dockerignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | **/.coverage 3 | **/.mypy_cache 4 | **/.pytest_cache 5 | **/*.pyc 6 | **/*.egg-info 7 | **/node_modules 8 | **/dist 9 | !/frontend/src/components/vue-tour/dist 10 | **/build -------------------------------------------------------------------------------- /core/.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /core/compose/workspace/blueos/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/compose/workspace/blueos/.gitignore -------------------------------------------------------------------------------- /core/compose/workspace/config/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/compose/workspace/config/.gitignore -------------------------------------------------------------------------------- /core/compose/workspace/logs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/compose/workspace/logs/.gitignore -------------------------------------------------------------------------------- /core/compose/workspace/userdata/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/compose/workspace/userdata/.gitignore -------------------------------------------------------------------------------- /core/configuration/motd: -------------------------------------------------------------------------------- 1 | 2 | Welcome to BlueOS 1.4! 3 | 4 | What you currently see is a tmux session built into BlueOS's docker container. 5 | This means you are not in the host Raspbian OS itself, and most of the data in here is 6 | not-persistent, except for things in /root/.config, although you should not store your data there. 7 | 8 | USAGE NOTES: 9 | To check all running services, press ctrl+b, then s. This will show all current sessions, 10 | and is helpful for checking the state of individual services, restarting them, and checking their verbose outputs. 11 | 12 | To split the screen horizontally, press ctrl+b then % 13 | To split the screen vertically press ctrl+b then " 14 | To switch tabs, click them or press ctrl+b then use the directional keys in the keyboard 15 | 16 | To login into the Raspberry Pi itself, do `red-pill`. 17 | 18 | Happy Hacking! 19 | -------------------------------------------------------------------------------- /core/configuration/tmux.conf: -------------------------------------------------------------------------------- 1 | # Basic configuration 2 | set-option -g history-limit 10000 3 | 4 | # Enable mouse over splits 5 | set -g mouse on 6 | 7 | # Force utf-8 usage for non ASCII characters 8 | setw -gq utf8 on -------------------------------------------------------------------------------- /core/frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | ../../.eslintrc.js -------------------------------------------------------------------------------- /core/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .yarn/ 4 | yarn.lock 5 | dev-dist/ 6 | components.d.ts 7 | bun.lockb 8 | -------------------------------------------------------------------------------- /core/frontend/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/bun.lockb -------------------------------------------------------------------------------- /core/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | BlueOS 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/images/bb120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/images/bb120.png -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/images/bluerov2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/images/bluerov2.png -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/boat/UNDEFINED.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/boat/UNDEFINED.glb -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/boat/UNDEFINED.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/rover/unknown.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/rover/unknown.glb -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/BLUEROV1.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/sub/BLUEROV1.glb -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/BLUEROV1.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "Motor1": { 4 | "surface": "0 13 1671 1673 1701 0.302 0.402 0.296", 5 | "text": "Motor 1" 6 | }, 7 | "Motor2": { 8 | "surface": "0 11 2780 2782 2810 0.649 0.137 0.214", 9 | "text": "Motor 2" 10 | }, 11 | "Motor3": { 12 | "surface": "0 15 5643 5645 5661 0.334 0.424 0.242", 13 | "text": "Motor 3" 14 | }, 15 | "Motor4": { 16 | "surface": "0 0 5865 5851 5866 0.121 0.134 0.744", 17 | "text": "Motor 4" 18 | }, 19 | "Motor5": { 20 | "surface": "0 7 5615 5613 5616 0.412 0.178 0.409", 21 | "text": "Motor 5" 22 | }, 23 | "Motor6": { 24 | "surface": "0 10 4269 4271 4430 0.366 0.355 0.278", 25 | "text": "Motor 6" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/SIMPLEROV_3.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/sub/SIMPLEROV_3.glb -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/SIMPLEROV_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "Motor1": { 4 | "surface": "3 1 2510 2509 2511 0.356 0.124 0.521", 5 | "text": "Motor 1" 6 | }, 7 | "Motor2": { 8 | "surface": "2 1 2642 2643 2674 0.638 0.230 0.133", 9 | "text": "Motor 2" 10 | }, 11 | "Motor3": { 12 | "surface": "0 1 4468 4447 4469 0.029 0.376 0.595", 13 | "text": "Motor 3" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/SIMPLEROV_4.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/sub/SIMPLEROV_4.glb -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/SIMPLEROV_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "Motor1": { 4 | "surface": "3 1 2510 2509 2511 0.356 0.124 0.521", 5 | "text": "Motor 1" 6 | }, 7 | "Motor2": { 8 | "surface": "2 1 2642 2643 2674 0.638 0.230 0.133", 9 | "text": "Motor 2" 10 | }, 11 | "Motor3": { 12 | "surface": "0 1 4467 4447 4468 0.188 0.510 0.302", 13 | "text": "Motor 3" 14 | }, 15 | "Motor4": { 16 | "surface": "21 1 4448 4449 4470 0.666 0.013 0.321", 17 | "text": "Motor 4" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/SIMPLEROV_5.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/sub/SIMPLEROV_5.glb -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/VECTORED.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/sub/VECTORED.glb -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/VECTORED.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "Motor6": { 4 | "position": "-0.005735193473105887m 0.09324964239559687m -0.10908695850046612m", 5 | "normal": "0 1 0", 6 | "text": "Motor 6" 7 | }, 8 | "Motor5": { 9 | "position": "-0.014944891051514142m 0.09296470070596237m 0.11045209178891321m", 10 | "normal": "0 1 0", 11 | "text": "Motor 5" 12 | }, 13 | "Motor1": { 14 | "position": "0.16237901231379895m -0.04480420421952533m 0.07717136042469966m", 15 | "normal": "1 0 0", 16 | "text": "Motor 1" 17 | }, 18 | "Motor2": { 19 | "position": "0.16239254215613144m -0.04414622495516646m -0.07747335058393973m", 20 | "normal": "1 0 0", 21 | "text": "Motor 2" 22 | }, 23 | "Motor3": { 24 | "position": "-0.20541099975383176m -0.04400868294373063m 0.07452124173999553m", 25 | "normal": "-1 0 0", 26 | "text": "Motor 3" 27 | }, 28 | "Motor4": { 29 | "position": "-0.20498456499943424m -0.0442730983355142m -0.07412432106505812m", 30 | "normal": "-1 0 0", 31 | "text": "Motor 4" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/VECTORED_6DOF.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/sub/VECTORED_6DOF.glb -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/VECTORED_6DOF.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "Motor6": { 4 | "position": "0.10586189429698731m 0.0713389215292608m -0.21733868001562004m", 5 | "normal": "0 1 0", 6 | "text": "Motor 6" 7 | }, 8 | "Motor5": { 9 | "position": "0.10578823301632309m 0.07133540921620643m 0.2167787642211954m", 10 | "normal": "0 1 0", 11 | "text": "Motor 5" 12 | }, 13 | "Motor7": { 14 | "position": "-0.13428090736591214m 0.07133942670184504m 0.2164338103576852m", 15 | "normal": "0 1 0", 16 | "text": "Motor 7" 17 | }, 18 | "Motor8": { 19 | "position": "-0.13346028414827638m 0.07134115867631283m -0.21678615277237565m", 20 | "normal": "0 1 0", 21 | "text": "Motor 8" 22 | }, 23 | "Motor1": { 24 | "position": "0.16237901231379895m -0.04480420421952533m 0.07717136042469966m", 25 | "normal": "1 0 0", 26 | "text": "Motor 1" 27 | }, 28 | "Motor2": { 29 | "position": "0.16239254215613144m -0.04414622495516646m -0.07747335058393973m", 30 | "normal": "1 0 0", 31 | "text": "Motor 2" 32 | }, 33 | "Motor3": { 34 | "position": "-0.20541099975383176m -0.04400868294373063m 0.07452124173999553m", 35 | "normal": "-1 0 0", 36 | "text": "Motor 3" 37 | }, 38 | "Motor4": { 39 | "position": "-0.20498456499943424m -0.0442730983355142m -0.07412432106505812m", 40 | "normal": "-1 0 0", 41 | "text": "Motor 4" 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /core/frontend/public/assets/vehicles/models/sub/bluerov.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/assets/vehicles/models/sub/bluerov.glb -------------------------------------------------------------------------------- /core/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/favicon.ico -------------------------------------------------------------------------------- /core/frontend/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /core/frontend/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /core/frontend/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /core/frontend/src/assets/img/ping/Ping1D.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/src/assets/img/ping/Ping1D.gif -------------------------------------------------------------------------------- /core/frontend/src/assets/img/ping/Ping360.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/src/assets/img/ping/Ping360.gif -------------------------------------------------------------------------------- /core/frontend/src/assets/vehicles/images/unknown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/frontend/src/components/app/Alerter.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 62 | -------------------------------------------------------------------------------- /core/frontend/src/components/app/BackendStatusChecker.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 41 | -------------------------------------------------------------------------------- /core/frontend/src/components/app/PirateModeMenu.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 51 | -------------------------------------------------------------------------------- /core/frontend/src/components/app/PirateModeTrayMenu.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 53 | 54 | 56 | -------------------------------------------------------------------------------- /core/frontend/src/components/app/ThemeTrayMenu.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /core/frontend/src/components/app/VehicleRebootMenu.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 37 | -------------------------------------------------------------------------------- /core/frontend/src/components/app/VehicleRebootRequiredTrayMenu.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 56 | 57 | 76 | -------------------------------------------------------------------------------- /core/frontend/src/components/bridges/BridgetUpdater.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/frontend/src/components/bridges/BridgetUpdater.vue -------------------------------------------------------------------------------- /core/frontend/src/components/common/DefaultTooltip.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /core/frontend/src/components/common/NotSafeOverlay.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 44 | -------------------------------------------------------------------------------- /core/frontend/src/components/common/ParameterSwitch.vue: -------------------------------------------------------------------------------- 1 | 6 | 53 | -------------------------------------------------------------------------------- /core/frontend/src/components/common/PasswordInput.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 53 | 54 | 56 | -------------------------------------------------------------------------------- /core/frontend/src/components/common/SpinningLogo.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | 32 | 49 | -------------------------------------------------------------------------------- /core/frontend/src/components/common/StatusTextWatcher.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 46 | -------------------------------------------------------------------------------- /core/frontend/src/components/common/rebootRequiredOverlay.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 40 | -------------------------------------------------------------------------------- /core/frontend/src/components/ethernet/EthernetManager.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 55 | -------------------------------------------------------------------------------- /core/frontend/src/components/ethernet/EthernetTrayMenu.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 50 | 51 | 53 | -------------------------------------------------------------------------------- /core/frontend/src/components/ethernet/EthernetUpdater.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 45 | -------------------------------------------------------------------------------- /core/frontend/src/components/kraken/Utils.ts: -------------------------------------------------------------------------------- 1 | import semver from 'semver' 2 | import stable from 'semver-stable' 3 | 4 | import { Version } from '@/types/kraken' 5 | 6 | export function getSortedVersions(versions: Record): Version[] { 7 | return Object.values(versions).sort((a, b) => semver.compare(a.tag, b.tag)) 8 | } 9 | 10 | export function getLatestVersion(versions: Record, beta = true): Version | undefined { 11 | const values = (Object.values(versions) ?? []).filter((v) => semver.valid(v.tag)) 12 | 13 | if (values.length === 0) { 14 | return undefined 15 | } 16 | 17 | if (!beta) { 18 | return values.find((v) => v.tag === stable.max(values.map((v1) => v1.tag))) 19 | } 20 | 21 | return values.reduce( 22 | (a: Version, b: Version) => (semver.compare(a.tag, b.tag) > 0 ? a : b), 23 | ) 24 | } 25 | 26 | export function isStable(version: string): boolean { 27 | return stable.is(version) 28 | } 29 | 30 | export function updateAvailableTag( 31 | versions: Record, 32 | current: string, 33 | beta = true, 34 | ): undefined | string { 35 | if (!semver.valid(current)) { 36 | return undefined 37 | } 38 | 39 | const latest = getLatestVersion(versions, beta) 40 | return latest !== undefined && semver.gt(latest.tag, current) ? latest.tag : undefined 41 | } 42 | 43 | export default { 44 | getSortedVersions, 45 | getLatestVersion, 46 | isStable, 47 | updateAvailableTag, 48 | } 49 | -------------------------------------------------------------------------------- /core/frontend/src/components/mavlink/MavlinkUpdater.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /core/frontend/src/components/nmea-injector/NMEASocketCard.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 60 | 61 | 67 | -------------------------------------------------------------------------------- /core/frontend/src/components/notifications/ConfigMenu.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /core/frontend/src/components/notifications/TrayButton.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 60 | 61 | 63 | -------------------------------------------------------------------------------- /core/frontend/src/components/parameter-editor/ParameterLabel.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 50 | -------------------------------------------------------------------------------- /core/frontend/src/components/system-information/Network.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 50 | -------------------------------------------------------------------------------- /core/frontend/src/components/system-information/SystemConditionCard.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 70 | 71 | 77 | -------------------------------------------------------------------------------- /core/frontend/src/components/utils/ParameterLoadingSpinner.vue: -------------------------------------------------------------------------------- 1 | 11 | 46 | -------------------------------------------------------------------------------- /core/frontend/src/components/utils/RebootButton.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /core/frontend/src/components/utils/themedSVG.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 46 | 52 | -------------------------------------------------------------------------------- /core/frontend/src/components/vehiclesetup/configuration/failsafes/types.ts: -------------------------------------------------------------------------------- 1 | export interface ParamDefinitions { 2 | name: string, 3 | replacementTitle?: string, 4 | replacementDescription?: string, 5 | optional?: boolean, 6 | icon?: string 7 | } 8 | 9 | export interface FailsafeDefinition { 10 | name: string, 11 | generalDescription: string, 12 | params: ParamDefinitions[], 13 | image: string, 14 | } 15 | -------------------------------------------------------------------------------- /core/frontend/src/components/vehiclesetup/overview/LightsInfo.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 52 | -------------------------------------------------------------------------------- /core/frontend/src/components/vehiclesetup/overview/common.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from '@/types/autopilot' 2 | 3 | export default function toBoardFriendlyChannel(board: string | undefined, servo: string): string { 4 | const servo_number = parseInt(servo.replace('SERVO', '').replace('_FUNCTION', ''), 10) 5 | if (board === Platform.Pixhawk1) { 6 | if (servo_number >= 9) { 7 | return `Aux ${servo_number - 8}` 8 | } 9 | } 10 | return `Channel ${servo_number}` 11 | } 12 | -------------------------------------------------------------------------------- /core/frontend/src/components/vehiclesetup/viewers/VehicleViewer.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 43 | -------------------------------------------------------------------------------- /core/frontend/src/components/video-manager/VideoUpdater.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | -------------------------------------------------------------------------------- /core/frontend/src/libs/MAVLink2Rest/Endpoint.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | // The library is an interface for MAVLink objects, messages can by of any type 3 | 4 | import Listener from './Listener' 5 | 6 | export default class Endpoint { 7 | socket: WebSocket 8 | 9 | listeners: Array = [] 10 | 11 | latestData: any = null 12 | 13 | constructor(url: string) { 14 | this.socket = this.createSocket(url) 15 | } 16 | 17 | /** 18 | * Update Endpoint url 19 | * @param {string} url 20 | */ 21 | updateUrl(url: string): void { 22 | this.socket.close() 23 | this.socket = this.createSocket(url) 24 | } 25 | 26 | /** 27 | * Create websocket for desired URL 28 | * @param {string} url 29 | * @returns WebSocket 30 | */ 31 | createSocket(url: string): WebSocket { 32 | const socket = new WebSocket(url) 33 | socket.onmessage = (message: MessageEvent): void => { 34 | this.latestData = JSON.parse(message.data) 35 | for (const listener of this.listeners) { 36 | listener.onNewData(this.latestData) 37 | } 38 | } 39 | socket.onclose = () => { 40 | setTimeout(() => { 41 | this.socket = this.createSocket(url) 42 | }, 5000) 43 | } 44 | return socket 45 | } 46 | 47 | /** 48 | * Return a new listener for Endpoint 49 | */ 50 | addListener(): Listener { 51 | const newListener = new Listener(this) 52 | this.listeners.push(newListener) 53 | return newListener 54 | } 55 | 56 | /** 57 | * Remove sired listener from Endpoint 58 | * @param {Listener} listener 59 | */ 60 | removeListener(listener: Listener): void { 61 | this.listeners = this.listeners.filter((item) => item !== listener) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/frontend/src/libs/MAVLink2Rest/Listener.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | // The library is an interface for MAVLink objects, messages can by of any type 3 | 4 | import type Endpoint from './Endpoint' 5 | 6 | export default class Listener { 7 | // eslint-disable-next-line class-methods-use-this 8 | callback: (msg: any) => void = () => { console.log('Listener not assigned a callback') } 9 | 10 | parent: Endpoint 11 | 12 | frequency = 1 13 | 14 | interval = -1 15 | 16 | constructor(parent: Endpoint) { 17 | this.parent = parent 18 | this.setFrequency(1) 19 | } 20 | 21 | /** 22 | * Define callback to be used when a new message is available 23 | * @param {(msg:any)=>void} callback 24 | * @returns Listener 25 | */ 26 | setCallback(callback: (msg: any) => void): Listener { 27 | this.callback = callback 28 | return this 29 | } 30 | 31 | /** 32 | * Set desired frequency for the callback 33 | * @param {number} frequency 34 | * @returns Listener 35 | */ 36 | setFrequency(frequency: number): Listener { 37 | clearInterval(this.interval) 38 | this.frequency = frequency 39 | if (frequency === 0) { 40 | return this 41 | } 42 | this.interval = window.setInterval(() => { 43 | if (this.parent.latestData !== null) { 44 | this.callback(this.parent.latestData) 45 | } 46 | }, 1000 / frequency) 47 | return this 48 | } 49 | 50 | /** 51 | * If frequency is set to zero, consume data as soon as received 52 | */ 53 | onNewData(data: any): void { 54 | if (this.frequency === 0) { 55 | this.callback(data) 56 | } 57 | } 58 | 59 | discard(): void { 60 | clearInterval(this.interval) 61 | this.parent.removeListener(this) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/frontend/src/libs/firmware/ardupilot/ardusub.ts: -------------------------------------------------------------------------------- 1 | export enum ArduSubMode { 2 | // Mode not set by vehicle yet 3 | PRE_FLIGHT = -1, 4 | // Manual angle with manual depth/throttle 5 | STABILIZE = 0, 6 | // Manual body-frame angular rate with manual depth/throttle 7 | ACRO = 1, 8 | // Manual angle with automatic depth/throttle 9 | ALT_HOLD = 2, 10 | // Fully automatic waypoint control using mission commands 11 | AUTO = 3, 12 | // Fully automatic fly to coordinate or fly at velocity/direction using GCS immediate commands 13 | GUIDED = 4, 14 | // Automatic circular flight with automatic throttle 15 | CIRCLE = 7, 16 | // Automatically return to surface, pilot maintains horizontal control 17 | SURFACE = 9, 18 | // Automatic position hold with manual override, with automatic throttle 19 | POSHOLD = 16, 20 | // Pass-through input with no stabilization 21 | MANUAL = 19, 22 | // Automatically detect motors orientation 23 | MOTOR_DETECT = 20, 24 | // Manual angle with automatic depth/throttle (from rangefinder altitude) 25 | SURFTRAK = 21, 26 | } 27 | -------------------------------------------------------------------------------- /core/frontend/src/store/ethernet.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getModule, Module, Mutation, VuexModule, 3 | } from 'vuex-module-decorators' 4 | 5 | import store from '@/store' 6 | import { EthernetInterface } from '@/types/ethernet' 7 | 8 | @Module({ 9 | dynamic: true, 10 | store, 11 | name: 'ethernet', 12 | }) 13 | 14 | class EthernetStore extends VuexModule { 15 | API_URL = '/cable-guy/v1.0' 16 | 17 | available_interfaces: EthernetInterface[] = [] 18 | 19 | updating_interfaces = true 20 | 21 | @Mutation 22 | setUpdatingInterfaces(updating: boolean): void { 23 | this.updating_interfaces = updating 24 | } 25 | 26 | @Mutation 27 | setInterfaces(ethernet_interfaces: EthernetInterface[]): void { 28 | this.available_interfaces = ethernet_interfaces 29 | this.updating_interfaces = false 30 | } 31 | } 32 | 33 | export { EthernetStore } 34 | 35 | const ethernet: EthernetStore = getModule(EthernetStore) 36 | export default ethernet 37 | -------------------------------------------------------------------------------- /core/frontend/src/store/frontend.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios' 2 | import { 3 | getModule, Module, Mutation, VuexModule, 4 | } from 'vuex-module-decorators' 5 | 6 | import store from '@/store' 7 | 8 | @Module({ 9 | dynamic: true, 10 | store, 11 | name: 'frontend', 12 | }) 13 | 14 | class FrontendStore extends VuexModule { 15 | backend_status_url = '/status' 16 | 17 | backend_status_request = null as Promise | null 18 | 19 | backend_offline = false 20 | 21 | @Mutation 22 | setBackendStatusRequest(check: Promise | null): void { 23 | this.backend_status_request = check 24 | } 25 | 26 | @Mutation 27 | setBackendOffline(offline: boolean): void { 28 | this.backend_offline = offline 29 | } 30 | } 31 | 32 | export { FrontendStore } 33 | 34 | const frontend: FrontendStore = getModule(FrontendStore) 35 | export default frontend 36 | -------------------------------------------------------------------------------- /core/frontend/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({}) 7 | -------------------------------------------------------------------------------- /core/frontend/src/style/colors/blue_robotics.js: -------------------------------------------------------------------------------- 1 | export const SKY_BLUE = '#D1EAF1' 2 | export const BR_BLUE = '#2699D0' 3 | export const MARINER_BLUE = '#135DA3' 4 | export const BLUE_WHALE = '#012F46' 5 | export const KELP_GREEN = '#4DA383' 6 | export const TETHER_YELLOW = '#FFE01B' 7 | export const TUNA = '#8C8C8C' 8 | export const OYSTER = '#403B3B' 9 | export const GARIBALDI_ORANGE = '#FF9A00' 10 | export const AXOLOTL_PINK = '#FF00DE' 11 | -------------------------------------------------------------------------------- /core/frontend/src/style/colors/default.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { BR_BLUE, BLUE_WHALE } from './blue_robotics' 3 | 4 | export const PRIMARY = BR_BLUE 5 | export const SECONDARY = BLUE_WHALE 6 | export const ACCENT = '#67a5d7' 7 | export const SUCCESS = '#4CAF50' 8 | export const ERROR = '#FF5252' 9 | export const INFO = '#2196F3' 10 | export const WARNING = '#e0a600' 11 | export const CRITICAL = '#E91E63' 12 | export const SHEET_DARK_BG = '#1E1E1E' 13 | export const SHEET_LIGHT_BG = '#FFFFFF' 14 | -------------------------------------------------------------------------------- /core/frontend/src/style/colors/vuetify.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | module.exports = { 3 | VUETIFY_PRIMARY: '#1976D2', 4 | VUETIFY_SECONDARY: '#424242', 5 | VUETIFY_ACCENT: '#82B1FF', 6 | VUETIFY_SUCCESS: '#4CAF50', 7 | VUETIFY_ERROR: '#FF5252', 8 | VUETIFY_INFO: '#2196F3', 9 | VUETIFY_WARNING: '#e0a600', 10 | } 11 | -------------------------------------------------------------------------------- /core/frontend/src/style/css/animations.css: -------------------------------------------------------------------------------- 1 | .scroll-container { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | white-space: nowrap; 5 | } 6 | 7 | .scroll-text { 8 | -moz-transform: translateX(100%); 9 | -webkit-transform: translateX(100%); 10 | transform: translateX(100%); 11 | 12 | -moz-animation: my-animation 7.5s linear infinite; 13 | -webkit-animation: my-animation 7.5s linear infinite; 14 | animation: my-animation 7.5s linear infinite; 15 | } 16 | 17 | @-moz-keyframes my-animation { 18 | from { -moz-transform: translateX(100%); } 19 | to { -moz-transform: translateX(-100%); } 20 | } 21 | 22 | @-webkit-keyframes my-animation { 23 | from { -webkit-transform: translateX(100%); } 24 | to { -webkit-transform: translateX(-100%); } 25 | } 26 | 27 | @keyframes my-animation { 28 | from { 29 | -moz-transform: translateX(100%); 30 | -webkit-transform: translateX(100%); 31 | transform: translateX(100%); 32 | } 33 | to { 34 | -moz-transform: translateX(-100%); 35 | -webkit-transform: translateX(-100%); 36 | transform: translateX(-100%); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/frontend/src/style/css/vuetify-global.css: -------------------------------------------------------------------------------- 1 | /* Prevent words in titles to break in half */ 2 | .v-card__text, .v-card__title { 3 | word-break: normal; 4 | } 5 | -------------------------------------------------------------------------------- /core/frontend/src/types/autopilot/px4/metadata-fetcher.ts: -------------------------------------------------------------------------------- 1 | interface PX4ParametersMetadataValuesItem { 2 | description: string 3 | value: number 4 | } 5 | 6 | interface PX4ParametersMetadataBitmaskItem { 7 | description: string 8 | index: number 9 | } 10 | 11 | interface PX4ParametersMetadata { 12 | name: string 13 | category: string 14 | group: string 15 | type: string 16 | 17 | shortDesc: string 18 | longDesc?: string 19 | 20 | default: number 21 | max?: number 22 | min?: number 23 | increment?: number 24 | units?: string 25 | 26 | values?: PX4ParametersMetadataValuesItem[] 27 | bitmask?: PX4ParametersMetadataBitmaskItem[] 28 | 29 | decimalPlaces?: number 30 | volatile?: boolean 31 | rebootRequire?: boolean 32 | } 33 | 34 | async function fetchPX4MetadataFromBoard(): Promise { 35 | // TODO - Add mav ftp fetch to get parameters.json from board 36 | throw new Error('Not implemented') 37 | } 38 | 39 | async function fetchPX4Metadata(): Promise { 40 | let metadata 41 | try { 42 | metadata = await fetchPX4MetadataFromBoard() 43 | } catch (e) { 44 | metadata = (await import('@/PX4-parameters/master/parameters.json')).parameters 45 | } 46 | 47 | return metadata as PX4ParametersMetadata[] 48 | } 49 | 50 | export { 51 | fetchPX4Metadata, 52 | fetchPX4MetadataFromBoard, 53 | PX4ParametersMetadata, 54 | PX4ParametersMetadataBitmaskItem, 55 | PX4ParametersMetadataValuesItem, 56 | } 57 | -------------------------------------------------------------------------------- /core/frontend/src/types/beacon.ts: -------------------------------------------------------------------------------- 1 | export enum InterfaceType { 2 | WIRED = 'WIRED', 3 | WIFI = 'WIFI', 4 | HOTSPOT = 'HOTSPOT', 5 | UNKNOWN = 'UNKNOWN', 6 | USB = 'USB', 7 | } 8 | export interface Domain { 9 | ip: string 10 | fullname: string 11 | hostname: string 12 | interface: string 13 | interface_type: InterfaceType 14 | service_type: string 15 | } 16 | -------------------------------------------------------------------------------- /core/frontend/src/types/bridges.ts: -------------------------------------------------------------------------------- 1 | import { Baudrate } from '@/types/common' 2 | import { SerialPortInfo } from '@/types/system-information/serial' 3 | 4 | export interface Bridge { 5 | serial_path: string 6 | baud: Baudrate 7 | ip: string 8 | udp_listen_port: number 9 | udp_target_port: number 10 | } 11 | 12 | export interface BridgeWithSerialInfo { 13 | bridge: Bridge 14 | serial_info: SerialPortInfo | undefined 15 | } 16 | -------------------------------------------------------------------------------- /core/frontend/src/types/commander.ts: -------------------------------------------------------------------------------- 1 | export enum ShutdownType { 2 | Reboot = 'reboot', 3 | PowerOff = 'poweroff', 4 | } 5 | 6 | export interface ReturnStruct { 7 | stdout: string 8 | stderr: string 9 | return_code: number 10 | } 11 | -------------------------------------------------------------------------------- /core/frontend/src/types/ethernet.ts: -------------------------------------------------------------------------------- 1 | export enum AddressMode { 2 | client = 'client', 3 | server = 'server', 4 | backupServer = 'backup_server', 5 | unmanaged = 'unmanaged', 6 | } 7 | 8 | export interface InterfaceAddress { 9 | ip: string, 10 | mode: AddressMode, 11 | } 12 | 13 | export interface InterfaceInfo { 14 | connected: boolean, 15 | number_of_disconnections: number, 16 | priority: number, 17 | } 18 | 19 | export interface EthernetInterface { 20 | name: string, 21 | addresses: InterfaceAddress[], 22 | info?: InterfaceInfo, 23 | priority?: number, 24 | } 25 | -------------------------------------------------------------------------------- /core/frontend/src/types/filebrowser.ts: -------------------------------------------------------------------------------- 1 | export interface FilebrowserFile { 2 | path: string 3 | name: string 4 | size: number 5 | extension: string 6 | modified: string 7 | mode: number 8 | isDir: boolean 9 | type: string 10 | } 11 | 12 | export interface FolderSorting { 13 | by: string 14 | asc: boolean 15 | } 16 | 17 | export interface FilebrowserFolder extends FilebrowserFile { 18 | items: FilebrowserFile[] 19 | numDirs: number 20 | numFiles: number 21 | sorting: FolderSorting 22 | } 23 | 24 | export interface FilebrowserCredentials { 25 | username: string 26 | password: string 27 | recaptcha: string 28 | } 29 | -------------------------------------------------------------------------------- /core/frontend/src/types/helper.ts: -------------------------------------------------------------------------------- 1 | export interface ServiceMetadata { 2 | name: string 3 | description: string 4 | icon: string 5 | company: string 6 | version: string 7 | webpage: string 8 | api: string 9 | route?: string 10 | new_page?: boolean 11 | sanitized_name?: string 12 | extra_query?: string 13 | avoid_iframes?: boolean 14 | works_in_relative_paths?: boolean 15 | } 16 | 17 | export interface Service { 18 | valid: boolean 19 | title: string 20 | documentation_url: string 21 | versions: Array 22 | port: number 23 | path?: string 24 | metadata?: ServiceMetadata 25 | } 26 | 27 | export interface SpeedtestServer { 28 | url: string 29 | lat: string 30 | lon: string 31 | name: string 32 | country: string 33 | cc: string 34 | sponsor: string 35 | id: string 36 | host: string 37 | d: number 38 | latency: number 39 | } 40 | 41 | export interface SpeedtestClient { 42 | ip: string 43 | lat: string 44 | lon: string 45 | isp: string 46 | isprating: string 47 | rating: string 48 | ispdlavg: string 49 | ispulavg: string 50 | loggedin: string 51 | country: string 52 | } 53 | 54 | export interface SpeedTestResult { 55 | download: number 56 | upload: number 57 | ping: number 58 | server: SpeedtestServer 59 | timestamp: Date 60 | bytes_sent: number 61 | bytes_received: number 62 | share: string | null 63 | client: SpeedtestClient 64 | } 65 | 66 | export enum InternetConnectionState { 67 | OFFLINE = 0, 68 | UNKNOWN = 1, 69 | LIMITED = 2, 70 | ONLINE = 3, 71 | } 72 | -------------------------------------------------------------------------------- /core/frontend/src/types/json-viewer.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-json-viewer'; 2 | -------------------------------------------------------------------------------- /core/frontend/src/types/mavlink.ts: -------------------------------------------------------------------------------- 1 | import { JSONValue } from '@/types/common' 2 | 3 | export interface MavlinkHeader { 4 | system_id: number, 5 | component_id: number, 6 | sequence: number 7 | } 8 | export interface MavlinkData { 9 | header: MavlinkHeader, 10 | message: JSONValue 11 | } 12 | 13 | export interface MavlinkMessage { 14 | messageName: string 15 | messageData: MavlinkData 16 | requestedMessageRate: number 17 | timestamp: Date 18 | } 19 | -------------------------------------------------------------------------------- /core/frontend/src/types/nmea-injector.ts: -------------------------------------------------------------------------------- 1 | import { SocketKind } from '@/types/common' 2 | 3 | export interface NMEASocket { 4 | kind: SocketKind 5 | port: number 6 | component_id: number 7 | } 8 | -------------------------------------------------------------------------------- /core/frontend/src/types/parameter_repository.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'https://bluerobotics.github.io/Blueos-Parameter-Repository/params_v1.json' { 2 | const parameters: Dictionary> 3 | export default parameters 4 | } 5 | -------------------------------------------------------------------------------- /core/frontend/src/types/ping.ts: -------------------------------------------------------------------------------- 1 | export enum PingType { 2 | Ping1D = 'Ping1D', 3 | Ping360 = 'Ping360' 4 | } 5 | 6 | export interface DriverStatus { 7 | udp_port: number | undefined 8 | mavlink_driver_enabled: boolean 9 | } 10 | 11 | export interface PingDevice { 12 | ping_type: PingType, 13 | device_id: number, 14 | device_model: number, 15 | device_revision: number, 16 | firmware_version_major: number, 17 | firmware_version_minor: number, 18 | firmware_version_patch: number, 19 | port: string, 20 | driver_status: DriverStatus, 21 | ethernet_discovery_info: string 22 | } 23 | 24 | export function formatVersion(device: PingDevice): string { 25 | return `${device.firmware_version_major}.${device.firmware_version_minor}.${device.firmware_version_patch}` 26 | } 27 | -------------------------------------------------------------------------------- /core/frontend/src/types/shims-general.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const value: string 3 | export default value 4 | } 5 | 6 | declare module '*.jpg' { 7 | const value: string 8 | export default value 9 | } 10 | 11 | declare module '*.png' { 12 | const value: string 13 | export default value 14 | } 15 | 16 | declare module '*.glb' { 17 | const value: string 18 | export default value 19 | } 20 | 21 | declare module 'vue-tooltip-directive' 22 | 23 | declare module '@/assets/colors/default' 24 | declare module '@/assets/colors/blue_robotics' 25 | declare module '@/assets/colors/vuetify' 26 | -------------------------------------------------------------------------------- /core/frontend/src/types/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /core/frontend/src/types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | 4 | export default Vue 5 | } 6 | -------------------------------------------------------------------------------- /core/frontend/src/types/shims-vuetify.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vuetify/lib/framework' { 2 | import Vuetify from 'vuetify' 3 | 4 | export default Vuetify 5 | } 6 | -------------------------------------------------------------------------------- /core/frontend/src/types/system-information/kernel.ts: -------------------------------------------------------------------------------- 1 | /** Base structure that provides information about kernel message */ 2 | export interface KernelMessage { 3 | /** 4 | * @param facility - Message source, E.g: "kern", "daemon" 5 | * @param level - Level of message, E.g: "info", "notice" 6 | * @param message - Content 7 | * @param sequence_number - Sequence number since first kernel message 8 | * @param timestamp_from_system_start_ns - When the message was received by the kernel 9 | * */ 10 | facility: string, 11 | level: string, 12 | message: string, 13 | sequence_number: number, 14 | timestamp_from_system_start_ns: number, 15 | } 16 | -------------------------------------------------------------------------------- /core/frontend/src/types/system-information/model.ts: -------------------------------------------------------------------------------- 1 | /** Device model information */ 2 | export interface Model { 3 | /** 4 | * @param model - Generic model name 5 | * @param arch - Architecture 6 | * @param cpu_name - CPU name 7 | */ 8 | model: string, 9 | arch: string, 10 | cpu_name: string, 11 | } 12 | -------------------------------------------------------------------------------- /core/frontend/src/types/system-information/netstat.ts: -------------------------------------------------------------------------------- 1 | /** Base structure that provides connection information */ 2 | export interface Connection { 3 | /** 4 | * @param address - Address where the connection is happening 5 | * @param port - Port that the connection is using 6 | * */ 7 | address: string, 8 | port: number, 9 | } 10 | 11 | /** Base structure that provides TCP information */ 12 | export interface TCP { 13 | /** 14 | * @param remote - TCP remote connection 15 | * @param local - TCP local connection 16 | * @param pids - Processes PIDs that is using such connection 17 | * @param state - E.g: Listen, time_wait, close_wait 18 | * */ 19 | remote: Connection, 20 | local: Connection, 21 | pids: number[], 22 | status: string, 23 | } 24 | 25 | /** Base structure that provides UDP information */ 26 | export interface UDP { 27 | /** 28 | * @param local - UDP local bind 29 | * @param pids - Processes PIDs that is using such connection 30 | * */ 31 | local: Connection, 32 | pids: number[], 33 | } 34 | 35 | /** Base structure that provides netstat information */ 36 | export interface Netstat { 37 | /** 38 | * @param tcp - List of TCP connections 39 | * @param udp - List od UDP connections 40 | * */ 41 | tcp: TCP[], 42 | udp: UDP[], 43 | } 44 | -------------------------------------------------------------------------------- /core/frontend/src/types/system-information/platform.ts: -------------------------------------------------------------------------------- 1 | /** Enum for possible Raspberry events */ 2 | export enum RaspberryEventType { 3 | FrequencyCapping = 'FrequencyCapping', 4 | TemperatureLimit = 'TemperatureLimit', 5 | Throttling = 'Throttling', 6 | UnderVoltage = 'UnderVoltage', 7 | } 8 | 9 | /** Base structure that provides event information for Raspberry */ 10 | export interface RaspberryEvent { 11 | /** 12 | * @param time - Time when the event occurred 13 | * @param type - E.g: "BCM2711" 14 | * */ 15 | time: string, 16 | type: RaspberryEventType, 17 | } 18 | 19 | /** Base structure that provides system events for Raspberry */ 20 | export interface RaspberryEvents { 21 | /** 22 | * @param model - E.g: "Raspberry Pi 4 B" 23 | * @param soc - E.g: "BCM2711" 24 | * */ 25 | occurring: RaspberryEvent[], 26 | list: RaspberryEvent[], 27 | } 28 | 29 | /** Base structure that provides platform information for Raspberry */ 30 | export interface Raspberry { 31 | /** 32 | * @param model - E.g: "Raspberry Pi 4 B" 33 | * @param soc - E.g: "BCM2711" 34 | * */ 35 | model: string, 36 | soc: string, 37 | events: RaspberryEvents, 38 | } 39 | 40 | /** Base structure that provides platform information */ 41 | export interface Platform { 42 | /** 43 | * @param raspberry - Platform specific information for Raspberry Pi 44 | * @param udp - List od UDP connections 45 | * */ 46 | raspberry?: Raspberry, 47 | } 48 | -------------------------------------------------------------------------------- /core/frontend/src/types/system-information/serial.ts: -------------------------------------------------------------------------------- 1 | import { JSONValue } from '@/types/common' 2 | 3 | export interface UsbPortInfo { 4 | /// Vendor ID 5 | vid: number, 6 | /// Product ID 7 | pid: number, 8 | /// Serial number (arbitrary string) 9 | serial_number: string | null, 10 | /// Manufacturer (arbitrary string) 11 | manufacturer: string | null, 12 | /// Product name (arbitrary string) 13 | product: string | null, 14 | } 15 | 16 | export type SerialPortType = null | string | UsbPortInfo; 17 | 18 | export interface SerialPortInfo { 19 | // The short name of the serial port 20 | name: string, 21 | // The long name of the serial port 22 | by_path: string | null, 23 | // Time since the device was created, 24 | by_path_created_ms_ago: number | null 25 | // Udev information from the device 26 | udev_properties: JSONValue | null, 27 | // Is the port in use? by whom? 28 | current_user: string | null, 29 | } 30 | 31 | /** Base structure that provides serial port information */ 32 | export interface Serial { 33 | ports: SerialPortInfo[], 34 | } 35 | -------------------------------------------------------------------------------- /core/frontend/src/types/version-chooser.ts: -------------------------------------------------------------------------------- 1 | export interface Version { 2 | repository: string, 3 | tag: string, 4 | last_modified: string, 5 | sha: (string | null), 6 | } 7 | 8 | export interface VersionsQuery { 9 | local: Version[], 10 | remote: Version[], 11 | error: (string | null), 12 | } 13 | 14 | export interface LocalVersionsQuery { 15 | local: Version[], 16 | error: (string | null), 17 | } 18 | 19 | export enum VersionType { 20 | Custom = 'custom', 21 | Master = 'master', 22 | Beta = 'beta', 23 | Stable = 'stable', 24 | } 25 | 26 | // Used to get internal error or status from connexion 27 | export interface ServerResponse { 28 | title: string, 29 | status: number, 30 | detail: string, 31 | type: string, 32 | } 33 | 34 | export interface DockerLoginInfo { 35 | root?: boolean 36 | registry?: string 37 | username: string 38 | password?: string 39 | } 40 | 41 | export function isServerResponse(response: unknown): response is ServerResponse { 42 | // eslint-disable-next-line no-extra-parens 43 | return (response as ServerResponse).status !== undefined 44 | } 45 | -------------------------------------------------------------------------------- /core/frontend/src/types/vuetify.ts: -------------------------------------------------------------------------------- 1 | export type VForm = Vue & { 2 | reset: () => void, 3 | resetValidation: () => void, 4 | validate: () => boolean, 5 | } 6 | -------------------------------------------------------------------------------- /core/frontend/src/types/wifi.ts: -------------------------------------------------------------------------------- 1 | export interface Network { 2 | ssid: string 3 | signal: number 4 | locked: boolean 5 | saved: boolean 6 | bssid: string 7 | frequency: number 8 | } 9 | 10 | export interface HotspotStatus { 11 | enabled: boolean 12 | supported: boolean 13 | } 14 | 15 | export interface WifiStatus { 16 | bssid: string 17 | freq: number 18 | ssid: string 19 | id: number 20 | mode: string 21 | wifi_generation: number 22 | pairwise_cipher: string 23 | group_cipher: string 24 | key_mgmt: string 25 | wpa_state: string 26 | ip_address: string 27 | p2p_device_address: string 28 | address: string 29 | uuid: string 30 | } 31 | 32 | export interface WPANetwork { 33 | ssid: string 34 | bssid: string 35 | flags: string 36 | frequency: number 37 | signallevel: number 38 | } 39 | 40 | export interface SavedNetwork { 41 | networkid: number 42 | ssid: string 43 | bssid: string 44 | flags: string 45 | } 46 | 47 | export interface NetworkCredentials { 48 | ssid: string 49 | password: string 50 | } 51 | -------------------------------------------------------------------------------- /core/frontend/src/utils/api.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from 'axios' 2 | 3 | import frontend from '@/store/frontend' 4 | 5 | const backend_offline_error = new Error('Backend is offline') 6 | backend_offline_error.name = 'BackendOffline' 7 | export { backend_offline_error } 8 | 9 | export const isBackendOffline = (error: any): boolean => { 10 | if (error === backend_offline_error) { return true } 11 | if (error.message === 'Network Error') { return true } 12 | return false; 13 | } 14 | 15 | const axios_backend_instance: AxiosInstance = axios.create() 16 | axios_backend_instance.interceptors.request.use(async (config) => { 17 | // Check if there's already a backend status request running. If yes, use it. If not, start one. 18 | if (frontend.backend_status_request === null) { 19 | frontend.setBackendStatusRequest(axios.get(frontend.backend_status_url, { timeout: 5000 })) 20 | } 21 | 22 | if (frontend.backend_status_request !== null) { 23 | // Backend status verification through /status endpoint should always return a 204 status-code. 24 | const backend_offline = await frontend.backend_status_request 25 | .then((response) => response.status !== 204) 26 | .catch(() => true) 27 | 28 | // Update backend status and reset status-request variable 29 | frontend.setBackendOffline(backend_offline) 30 | frontend.setBackendStatusRequest(null) 31 | 32 | if (backend_offline) { 33 | // Throw dedicated error so services can differentiate between offline backend and other kind of errors 34 | throw backend_offline_error 35 | } 36 | } 37 | return config 38 | }, (error) => Promise.reject(error)) 39 | 40 | export default axios_backend_instance 41 | -------------------------------------------------------------------------------- /core/frontend/src/utils/mavlink.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash' 2 | 3 | export default function mavlink_store_get(storage: T, path: string, system_id? :number, component_id?: number): unknown { 4 | if (system_id !== undefined && component_id !== undefined) { 5 | return get(storage, `available_identified_messages.${system_id}_${component_id}.${path}`) 6 | } 7 | return get(storage, `available_messages.${path}`) 8 | } 9 | -------------------------------------------------------------------------------- /core/frontend/src/utils/mavlink_math.ts: -------------------------------------------------------------------------------- 1 | // ported from https://github.com/ArduPilot/pymavlink/blob/master/mavextra.py#L60 2 | 3 | import { glMatrix, vec3, mat3 } from 'gl-matrix' 4 | 5 | export default function mag_heading(RawImu: vec3, attitude: vec3, declination: number): number { 6 | // calculate heading from raw magnetometer 7 | const magX = RawImu[0] 8 | const magY = RawImu[1] 9 | const magZ = RawImu[2] 10 | 11 | // go via a DCM matrix to match the APM calculation 12 | const dcmMatrix = mat3.fromEuler(mat3.create(), attitude[0], attitude[1], attitude[2]) 13 | const cosPitchSqr = 1.0 - dcmMatrix[6] * dcmMatrix[6] 14 | const headY = magY * dcmMatrix[8] - magZ * dcmMatrix[7] 15 | const headX = magX * cosPitchSqr - dcmMatrix[6] * (magY * dcmMatrix[7] + magZ * dcmMatrix[8]) 16 | let heading = glMatrix.toDegree(Math.atan2(-headY, headX)) + declination 17 | while (heading < -180) { 18 | heading += 360 19 | } 20 | return heading 21 | } 22 | -------------------------------------------------------------------------------- /core/frontend/src/utils/networking.ts: -------------------------------------------------------------------------------- 1 | export function formatBandwidth(bytesPerSecond: number): string { 2 | const mbps = (8 * bytesPerSecond / 1024 / 1024) 3 | const decimal_places = mbps < 10 ? 2 : mbps < 100 ? 1 : 0 4 | return `${mbps.toFixed(decimal_places)}Mbps` 5 | } 6 | -------------------------------------------------------------------------------- /core/frontend/src/utils/pattern_validators.ts: -------------------------------------------------------------------------------- 1 | import { isIP } from 'is-ip' 2 | 3 | import { Baudrate } from '@/types/common' 4 | 5 | export function isSocketPort(port: number, public_range = true): boolean { 6 | if (!Number.isInteger(port)) { 7 | return false 8 | } 9 | if (port > 65535) { 10 | return false 11 | } 12 | if (public_range) { 13 | return port >= 1024 14 | } 15 | return port >= 0 16 | } 17 | 18 | export function isIntegerString(input: string): boolean { 19 | return input.match(/^[0-9]+$/) != null 20 | } 21 | 22 | export function isBaudrate(baudrate: number): boolean { 23 | return Object.values(Baudrate).map((baud) => parseInt(baud, 10)).includes(baudrate) 24 | } 25 | 26 | export function isIpAddress(ip: string): boolean { 27 | return isIP(ip) 28 | } 29 | 30 | export function isUdpAddress(address: string): boolean { 31 | try { 32 | return ['udp:', 'udp265:'].includes(new URL(address).protocol) 33 | } catch (error) { 34 | return false 35 | } 36 | } 37 | 38 | export function isRtspAddress(address: string): boolean { 39 | try { 40 | return new URL(address).protocol === 'rtsp:' 41 | } catch (error) { 42 | return false 43 | } 44 | } 45 | 46 | export function isRtspVariantAddress(address: string): boolean { 47 | const allowedVariants = ['rtspu:', 'rtspt:', 'rtsph:'] 48 | 49 | try { 50 | const { protocol } = new URL(address) 51 | 52 | return allowedVariants.includes(protocol) 53 | } catch (error) { 54 | return false 55 | } 56 | } 57 | 58 | 59 | export function isFilepath(filepath: string): boolean { 60 | const filepath_pattern = /^(.+)\/([^/]+)$/ 61 | return filepath_pattern.test(filepath) 62 | } 63 | 64 | export function isNotEmpty(sizable: Array | string | undefined | null): boolean { 65 | return !!sizable && sizable.length > 0 66 | } 67 | -------------------------------------------------------------------------------- /core/frontend/src/utils/update_time.ts: -------------------------------------------------------------------------------- 1 | import Notifier from '@/libs/notifier' 2 | import { update_time_service } from '@/types/frontend_services' 3 | import back_axios from '@/utils/api' 4 | 5 | const notifier = new Notifier(update_time_service) 6 | 7 | export default async function run() : Promise { 8 | await back_axios({ 9 | url: '/commander/v1.0/set_time', 10 | method: 'post', 11 | params: { 12 | unix_time_seconds: Math.round(new Date().getTime() / 1000), 13 | i_know_what_i_am_doing: true, 14 | }, 15 | timeout: 10000, 16 | }) 17 | .catch((error) => { 18 | // Connection lost/timeout, normal when we are turnning off/rebooting 19 | if (error.code === 'ECONNABORTED') { 20 | run() 21 | return 22 | } 23 | 24 | notifier.pushBackError('UPDATE_TIME_FAIL', error) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /core/frontend/src/views/AvailableServicesView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /core/frontend/src/views/BagEditorView.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | -------------------------------------------------------------------------------- /core/frontend/src/views/BridgesView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /core/frontend/src/views/EndpointView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 29 | -------------------------------------------------------------------------------- /core/frontend/src/views/FileBrowserView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | -------------------------------------------------------------------------------- /core/frontend/src/views/LogView.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /core/frontend/src/views/MavlinkInspectorView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | -------------------------------------------------------------------------------- /core/frontend/src/views/NMEAInjectorView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /core/frontend/src/views/NetworkTestView.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 64 | -------------------------------------------------------------------------------- /core/frontend/src/views/PageNotFound.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /core/frontend/src/views/ParameterEditorView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /core/frontend/src/views/Pings.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 62 | 63 | 68 | -------------------------------------------------------------------------------- /core/frontend/src/views/TerminalView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | -------------------------------------------------------------------------------- /core/frontend/src/views/VersionChooser.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | -------------------------------------------------------------------------------- /core/frontend/src/views/VideoManagerView.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /core/frontend/src/views/ZenohInspectorView.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /core/frontend/src/widgets/Cpu.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 38 | -------------------------------------------------------------------------------- /core/frontend/src/widgets/Disk.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 40 | -------------------------------------------------------------------------------- /core/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "bundler", 9 | "moduleDetection": "force", 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "resolveJsonModule": true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "experimentalDecorators": true, 17 | "types": [ 18 | "webpack-env", 19 | "vite/client" 20 | ], 21 | "paths": { 22 | "@/*": [ 23 | "src/*" 24 | ] 25 | }, 26 | "lib": [ 27 | "esnext", 28 | "dom", 29 | "dom.iterable", 30 | "scripthost" 31 | ] 32 | }, 33 | "include": [ 34 | "src/**/*.ts", 35 | "src/**/*.d.ts", 36 | "src/**/*.tsx", 37 | "src/**/*.vue", 38 | "tests/**/*.ts", 39 | "tests/**/*.tsx" 40 | ], 41 | "exclude": [ 42 | "node_modules" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /core/libs/bridges/README.md: -------------------------------------------------------------------------------- 1 | # Bridges 2 | 3 | ## The Bridges library is a abstraction layer over the Bridges tool, allowing it to be instantiated and used from Python scripts. 4 | -------------------------------------------------------------------------------- /core/libs/bridges/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "bridges" 3 | version = "0.1.0" 4 | description = "BlueOS abstraction over bridges tool." 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [] 8 | 9 | [build-system] 10 | requires = ["hatchling"] 11 | build-backend = "hatchling.build" 12 | -------------------------------------------------------------------------------- /core/libs/bridges/src/bridges/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/bridges/src/bridges/__init__.py -------------------------------------------------------------------------------- /core/libs/bridges/src/bridges/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/bridges/src/bridges/py.typed -------------------------------------------------------------------------------- /core/libs/bridges/src/bridges/serialhelper.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from enum import IntEnum 3 | from pathlib import Path 4 | 5 | from serial.tools.list_ports_linux import SysFS 6 | 7 | 8 | class Baudrate(IntEnum): 9 | b1200 = 1200 10 | b1800 = 1800 11 | b2400 = 2400 12 | b4800 = 4800 13 | b9600 = 9600 14 | b19200 = 19200 15 | b38400 = 38400 16 | b57600 = 57600 17 | b115200 = 115200 18 | b230400 = 230400 19 | b460800 = 460800 20 | b500000 = 500000 21 | b576000 = 576000 22 | b921600 = 921600 23 | b1000000 = 1000000 24 | b1152000 = 1152000 25 | b1500000 = 1500000 26 | b2000000 = 2000000 27 | b2500000 = 2500000 28 | b3000000 = 3000000 29 | b3500000 = 3500000 30 | b4000000 = 4000000 31 | 32 | 33 | def set_low_latency(port: SysFS) -> None: 34 | """ 35 | sets the latency_timer for the serial adapter 36 | """ 37 | 38 | device_name = Path(port.device.strip()).name 39 | latency_file = f"/sys/bus/usb-serial/devices/{device_name}/latency_timer" 40 | logging.info(f"Latency file: {latency_file}") 41 | 42 | try: 43 | with open(latency_file, "w", encoding="utf-8") as p: 44 | p.write("1") 45 | p.flush() 46 | except IOError: 47 | logging.warning(f"Unable to set latency for device {device_name}, your device may work slower than expected.") 48 | -------------------------------------------------------------------------------- /core/libs/commonwealth/README.md: -------------------------------------------------------------------------------- 1 | # Commonwealth of Services 2 | 3 | ## The Commonwealth of Services, generally known simply as the Commonwealth, is a technological association of BlueOS services to share common knowledge. 4 | -------------------------------------------------------------------------------- /core/libs/commonwealth/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "commonwealth" 3 | version = "0.1.0" 4 | description = "BlueOS library to share common code." 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "aiohttp==3.7.4", 9 | "appdirs==1.4.4", 10 | "eclipse-zenoh==1.4.0", 11 | "loguru==0.5.3", 12 | "psutil==5.7.2", 13 | "pykson==1.0.2", 14 | "sentry-sdk==2.29.1", 15 | "starlette==0.27.0", 16 | ] 17 | 18 | [tool.uv.sources] 19 | pykson = { url = "https://github.com/patrickelectric/pykson/archive/refs/tags/1.0.2.tar.gz" } 20 | 21 | [build-system] 22 | requires = ["hatchling"] 23 | build-backend = "hatchling.build" 24 | -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/__init__.py -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/mavlink_comm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/mavlink_comm/__init__.py -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/mavlink_comm/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mavlink-comm exception classes. 3 | """ 4 | 5 | 6 | class MavlinkMessageSendFail(RuntimeError): 7 | """Mavlink message could no be sent.""" 8 | 9 | 10 | class MavlinkMessageReceiveFail(RuntimeError): 11 | """Could not retrieve Mavlink message.""" 12 | 13 | 14 | class FetchUpdatedMessageFail(RuntimeError): 15 | """Unable to get an updated mavlink message.""" 16 | 17 | 18 | class VehicleDisarmFail(RuntimeError): 19 | """Could not disarm vehicle.""" 20 | -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/py.typed -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/settings/__init__.py -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/settings/bases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/settings/bases/__init__.py -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/settings/exceptions.py: -------------------------------------------------------------------------------- 1 | class BadSettingsFile(ValueError): 2 | """Settings file is not valid.""" 3 | 4 | 5 | class SettingsFromTheFuture(ValueError): 6 | """Settings file version is from a newer version of the service.""" 7 | 8 | 9 | class MigrationFail(RuntimeError): 10 | """Could not apply migration.""" 11 | 12 | 13 | class BadAttributes(BadSettingsFile): 14 | """Attributes on settings file are not valid.""" 15 | 16 | 17 | class BadSettingsClassNaming(RuntimeError): 18 | """Setting class in the inheritance chain have a name that is not valid.""" 19 | -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/settings/manager.py: -------------------------------------------------------------------------------- 1 | from commonwealth.settings.managers.pydantic_manager import PydanticManager 2 | from commonwealth.settings.managers.pykson_manager import PyksonManager as Manager 3 | 4 | __all__ = ["Manager", "PydanticManager"] 5 | -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/settings/managers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/settings/managers/__init__.py -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/settings/settings.py: -------------------------------------------------------------------------------- 1 | from commonwealth.settings.bases.pydantic_base import PydanticSettings 2 | from commonwealth.settings.bases.pykson_base import PyksonSettings as BaseSettings 3 | from commonwealth.settings.exceptions import ( 4 | BadAttributes, 5 | BadSettingsFile, 6 | MigrationFail, 7 | SettingsFromTheFuture, 8 | ) 9 | 10 | __all__ = [ 11 | "BaseSettings", 12 | "BadAttributes", 13 | "BadSettingsFile", 14 | "MigrationFail", 15 | "SettingsFromTheFuture", 16 | "PydanticSettings", 17 | ] 18 | -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/settings/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/settings/tests/__init__.py -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/utils/Singleton.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | 4 | class Singleton(type): 5 | _instances: Dict[Any, Any] = {} 6 | 7 | def __call__(cls: Any, *args: Any, **kwargs: Any) -> Any: 8 | if cls not in cls._instances: 9 | cls._instances[cls] = super().__call__(*args, **kwargs) 10 | return cls._instances[cls] 11 | -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/utils/__init__.py -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/utils/sentry_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from typing import Any, Dict, Optional 4 | 5 | import sentry_sdk 6 | 7 | 8 | def _get_sentry_config() -> Optional[Dict[str, Any]]: 9 | """ 10 | Retrieves the Sentry configuration for the BlueOS backend project 11 | 12 | Returns: 13 | Optional[ClientConstructor]: The Sentry configuration if valid, otherwise None. 14 | """ 15 | 16 | SENTRY_PROJECT = "BlueOS" 17 | SENTRY_DSN = "https://d93d1be8ddb7d5e1f45fb1eeca287eac@o4507696465707008.ingest.us.sentry.io/4509446521683968" 18 | VALID_TAG_PATTERN = r"^\d+\.\d+\.\d+-\d+-g[0-9a-f]{7,}$" 19 | 20 | git_describe_tag = os.environ.get("GIT_DESCRIBE_TAGS") 21 | 22 | if not git_describe_tag or not re.match(VALID_TAG_PATTERN, git_describe_tag): 23 | return 24 | 25 | release = f"{SENTRY_PROJECT}@{git_describe_tag}".replace("tags/", "").replace("/", ":") 26 | 27 | return { 28 | "dsn": SENTRY_DSN, 29 | "release": release, 30 | "traces_sample_rate": 1.0, 31 | "trace_propagation_targets": [], 32 | "send_default_pii": True, 33 | } 34 | 35 | 36 | def init_sentry(name: Optional[str] = None) -> None: 37 | sentry_config = _get_sentry_config() 38 | if sentry_config: 39 | sentry_sdk.init(server_name=name, **sentry_config) 40 | 41 | 42 | async def init_sentry_async(name: Optional[str] = None) -> None: 43 | """ 44 | Initializes Sentry when used in an async context. 45 | 46 | Per sentry's documentation, when using async context, the init function should be called within an async function. 47 | https://docs.sentry.io/platforms/python/#configure 48 | """ 49 | sentry_config = _get_sentry_config() 50 | if sentry_config: 51 | sentry_sdk.init(server_name=name, **sentry_config) 52 | -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/utils/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/libs/commonwealth/src/commonwealth/utils/tests/__init__.py -------------------------------------------------------------------------------- /core/libs/commonwealth/src/commonwealth/utils/tests/test_decorators.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime 3 | 4 | from .. import decorators 5 | 6 | CACHE_TIME = 0.3 7 | CACHE_WAIT_TIME = CACHE_TIME + 0.2 8 | 9 | 10 | @decorators.temporary_cache(timeout_seconds=CACHE_TIME) 11 | def cached_function(_entry: str) -> datetime: 12 | return datetime.now() 13 | 14 | 15 | def test_nested_settings_save_load() -> None: 16 | inputs = ["first", "second", "third", "fourth", "fifth", "sixth"] 17 | original_output = {key: cached_function(key) for key in inputs} 18 | 19 | # Check cache faster than light, sue me Einstein 20 | assert all(original_output[key] == cached_function(key) for key in inputs) 21 | 22 | # Wait for cache to be invalid 23 | time.sleep(CACHE_WAIT_TIME) 24 | 25 | # Check if all cache values are invalid after waiting for a long time 26 | assert all(original_output[key] != cached_function(key) for key in inputs) 27 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/.gitignore: -------------------------------------------------------------------------------- 1 | frontend/static/ -------------------------------------------------------------------------------- /core/services/ardupilot_manager/README.md: -------------------------------------------------------------------------------- 1 | # AutoPilot service manager 2 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/api/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0406 2 | from .app import application 3 | 4 | __all__ = ["application"] 5 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/api/app.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from commonwealth.utils.apis import GenericErrorHandlingRoute, PrettyJSONResponse 4 | from fastapi import FastAPI 5 | from fastapi.responses import HTMLResponse 6 | from fastapi.staticfiles import StaticFiles 7 | from fastapi_versioning import VersionedFastAPI 8 | 9 | # Routers 10 | from api.v1.routers import endpoints_router_v1, index_router_v1 11 | from api.v2.routers import index_router_v2 12 | 13 | application = FastAPI( 14 | title="AutoPilot Manager API", 15 | description="AutoPilot Manager is responsible for managing AutoPilot devices connected to BlueOS.", 16 | default_response_class=PrettyJSONResponse, 17 | debug=True, # TODO - Add debug after based on args 18 | ) 19 | application.router.route_class = GenericErrorHandlingRoute 20 | 21 | # API v1 22 | application.include_router(index_router_v1) 23 | application.include_router(endpoints_router_v1) 24 | 25 | # API v2 26 | application.include_router(index_router_v2) 27 | 28 | application = VersionedFastAPI(application, prefix_format="/v{major}.{minor}", enable_latest=True) 29 | 30 | 31 | @application.get("/", status_code=200) 32 | async def root() -> HTMLResponse: 33 | html_content = """ 34 | 35 | 36 | AutoPilot Manager 37 | 38 | 39 | """ 40 | return HTMLResponse(content=html_content, status_code=200) 41 | 42 | 43 | # Mount static files 44 | application.mount("/static", StaticFiles(directory=path.join(path.dirname(__file__), "static")), name="static") 45 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/api/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/ardupilot_manager/api/static/.gitkeep -------------------------------------------------------------------------------- /core/services/ardupilot_manager/api/v1/routers/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0406 2 | from .endpoints import endpoints_router_v1 3 | from .index import index_router_v1 4 | 5 | __all__ = ["endpoints_router_v1", "index_router_v1"] 6 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/api/v2/routers/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0406 2 | from .index import index_router_v2 3 | 4 | __all__ = ["index_router_v2"] 5 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/api/v2/routers/index.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, status 2 | from fastapi.responses import HTMLResponse 3 | from fastapi_versioning import versioned_api_route 4 | 5 | index_router_v2 = APIRouter( 6 | tags=["index_v2"], 7 | route_class=versioned_api_route(2, 0), 8 | responses={status.HTTP_404_NOT_FOUND: {"description": "Not found"}}, 9 | ) 10 | 11 | 12 | @index_router_v2.get("/", status_code=status.HTTP_200_OK) 13 | async def root_v2() -> HTMLResponse: 14 | html_content = """ 15 | 16 | 17 | AutoPilot Manager 18 | 19 | 20 | """ 21 | return HTMLResponse(content=html_content, status_code=200) 22 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/args.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class CommandLineArgs: 7 | """ 8 | Represents command line argument for Autopilot Manager. 9 | 10 | Attributes: 11 | debug (bool): Enable debug mode 12 | host (str): Host to server kraken on 13 | port (int): Port to server kraken on 14 | """ 15 | 16 | sitl: bool 17 | debug: bool 18 | host: str 19 | port: int 20 | 21 | @staticmethod 22 | def from_args() -> "CommandLineArgs": 23 | parser = argparse.ArgumentParser(description="AutoPilot Manager service") 24 | 25 | parser.add_argument("-s", "--sitl", action="store_true", help="run SITL instead of connecting any board") 26 | parser.add_argument("--debug", action="store_true", default=False, help="Enable debug mode") 27 | parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to server AutoPilot Manager on") 28 | parser.add_argument("--port", type=int, default=8000, help="Port to server AutoPilot Manager on") 29 | 30 | args = parser.parse_args() 31 | client_args = CommandLineArgs(sitl=args.sitl, debug=args.debug, host=args.host, port=args.port) 32 | 33 | return client_args 34 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | AutoPilot-manager exception classes. 3 | """ 4 | 5 | 6 | class FirmwareDownloadFail(RuntimeError): 7 | """Firmware download operation failed.""" 8 | 9 | 10 | class NoVersionAvailable(ValueError): 11 | """No firmware versions available for specified configuration.""" 12 | 13 | 14 | class InvalidManifest(ValueError): 15 | """AutoPilot manifest file cannot be validated.""" 16 | 17 | 18 | class ManifestUnavailable(RuntimeError): 19 | """AutoPilot manifest file unavailable.""" 20 | 21 | 22 | class NoCandidate(ValueError): 23 | """No firmware candidate found for specified configuration.""" 24 | 25 | 26 | class MoreThanOneCandidate(ValueError): 27 | """More than one firmware candidate found for specified configuration.""" 28 | 29 | 30 | class FirmwareUploadFail(RuntimeError): 31 | """Firmware upload operation failed.""" 32 | 33 | 34 | class UploadToolNotFound(RuntimeError): 35 | """Firmware upload tool not found.""" 36 | 37 | 38 | class InvalidUploadTool(ValueError): 39 | """Firmware upload tool cannot be validated.""" 40 | 41 | 42 | class InvalidFirmwareFile(ValueError): 43 | """Firmware file cannot be validated.""" 44 | 45 | 46 | class UndefinedPlatform(ValueError): 47 | """AutoPilot platform is not defined.""" 48 | 49 | 50 | class UnsupportedPlatform(ValueError): 51 | """AutoPilot platform not supported.""" 52 | 53 | 54 | class FirmwareInstallFail(RuntimeError): 55 | """Firmware install operation failed.""" 56 | 57 | 58 | class AutoPilotProcessKillFail(RuntimeError): 59 | """Could not kill AutoPilot process.""" 60 | 61 | 62 | class NoDefaultFirmwareAvailable(RuntimeError): 63 | """Default firmware file is not available.""" 64 | 65 | 66 | class NoPreferredBoardSet(RuntimeError): 67 | """No preferred board is set yet.""" 68 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/firmware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/ardupilot_manager/firmware/__init__.py -------------------------------------------------------------------------------- /core/services/ardupilot_manager/flight_controller_detector/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/ardupilot_manager/flight_controller_detector/__init__.py -------------------------------------------------------------------------------- /core/services/ardupilot_manager/flight_controller_detector/linux/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/ardupilot_manager/flight_controller_detector/linux/__init__.py -------------------------------------------------------------------------------- /core/services/ardupilot_manager/flight_controller_detector/linux/argonot.py: -------------------------------------------------------------------------------- 1 | from flight_controller_detector.linux.navigator import NavigatorPi4 2 | from typedefs import Platform 3 | 4 | 5 | class Argonot(NavigatorPi4): 6 | name = "Argonot" 7 | manufacturer = "SymbyTech" 8 | platform = Platform.Argonot 9 | 10 | devices = { 11 | "swap_multiplexer": (0x77, 1), 12 | } 13 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/flight_controller_detector/linux/detector.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from typing import List, Optional, Type 3 | 4 | from loguru import logger 5 | 6 | from flight_controller_detector.linux.argonot import Argonot 7 | from flight_controller_detector.linux.linux_boards import LinuxFlightController 8 | from flight_controller_detector.linux.navigator import NavigatorPi4, NavigatorPi5 9 | 10 | 11 | class LinuxFlightControllerDetector: 12 | # for sanity reasons, let's assume a linux board never gets disconnected 13 | # this will prevent a lot of loading/unloading of modules and overlays in the future 14 | previously_detected: Optional["LinuxFlightController"] = None 15 | 16 | @classmethod 17 | def detect_boards(cls, ignore_cache: bool = False) -> Optional["LinuxFlightController"]: 18 | if cls.previously_detected and not ignore_cache: 19 | return cls.previously_detected 20 | 21 | for candidate in LinuxFlightController.get_all_boards(): 22 | logger.info(f"Detecting Linux board: {candidate.__name__}") 23 | if candidate().detect(): 24 | logger.info(f"Detected Linux board: {candidate.__name__}") 25 | cls.previously_detected = candidate() 26 | return candidate() 27 | return None 28 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/flight_controller_detector/linux/linux_boards.py: -------------------------------------------------------------------------------- 1 | from typing import List, Type 2 | 3 | from smbus2 import SMBus 4 | 5 | from typedefs import FlightController, PlatformType, Serial 6 | 7 | 8 | class LinuxFlightController(FlightController): 9 | """Linux-based Flight-controller board.""" 10 | 11 | @property 12 | def type(self) -> PlatformType: 13 | return PlatformType.Linux 14 | 15 | def detect(self) -> bool: 16 | raise NotImplementedError 17 | 18 | def get_serials(self) -> List[Serial]: 19 | raise NotImplementedError 20 | 21 | def check_for_i2c_device(self, bus_number: int, address: int) -> bool: 22 | try: 23 | bus = SMBus(bus_number) 24 | bus.read_byte_data(address, 0) 25 | return True 26 | except OSError: 27 | return False 28 | 29 | @classmethod 30 | def get_all_boards(cls) -> List[Type["LinuxFlightController"]]: 31 | all_subclasses = [] 32 | 33 | for subclass in cls.__subclasses__(): 34 | all_subclasses.append(subclass) 35 | all_subclasses.extend(subclass.get_all_boards()) 36 | 37 | return all_subclasses 38 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/mavlink_proxy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/ardupilot_manager/mavlink_proxy/__init__.py -------------------------------------------------------------------------------- /core/services/ardupilot_manager/mavlink_proxy/exceptions.py: -------------------------------------------------------------------------------- 1 | class DuplicateEndpointName(ValueError): 2 | """Another mavlink endpoint with same name already exists.""" 3 | 4 | 5 | class EndpointAlreadyExists(ValueError): 6 | """Mavlink endpoint already exists.""" 7 | 8 | 9 | class EndpointDontExist(ValueError): 10 | """Given Mavlink endpoint do not exist.""" 11 | 12 | 13 | class MavlinkRouterStartFail(RuntimeError): 14 | """Failed to initiate Mavlink router.""" 15 | 16 | 17 | class NoMasterMavlinkEndpoint(ValueError): 18 | """No master Mavlink endpoint set.""" 19 | 20 | 21 | class EndpointCreationFail(RuntimeError): 22 | """Failed to add endpoint.""" 23 | 24 | 25 | class EndpointDeleteFail(RuntimeError): 26 | """Failed to delete endpoint.""" 27 | 28 | 29 | class EndpointUpdateFail(RuntimeError): 30 | """Failed to update endpoint.""" 31 | -------------------------------------------------------------------------------- /core/services/ardupilot_manager/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "ardupilot_manager" 3 | version = "0.0.1" 4 | description = "AutoPilot service manager." 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "aiofiles==0.6.0", 9 | "anyio==3.7.1", 10 | "appdirs==1.4.4", 11 | "commonwealth==0.1.0", 12 | "fastapi==0.105.0", 13 | "fastapi-versioning==0.9.1", 14 | "loguru==0.5.3", 15 | "packaging==20.4", 16 | "psutil==5.7.2", 17 | "pydantic==1.10.12", 18 | "pyelftools==0.30", 19 | "pyserial==3.5", 20 | "python-multipart==0.0.5", 21 | "smbus2==0.3.0", 22 | "starlette==0.27.0", 23 | "uvicorn==0.18.0", 24 | "validators==0.18.2", 25 | ] 26 | 27 | [tool.uv.sources] 28 | commonwealth = { workspace = true } 29 | -------------------------------------------------------------------------------- /core/services/bag_of_holding/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "bag_of_holding" 3 | version = "0.1.0" 4 | description = "Allow the persistence of arbitrary data in a Blue Robotics BlueOS system." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "anyio==3.7.1", 8 | "appdirs==1.4.4", 9 | "commonwealth==0.1.0", 10 | "dpath==2.1.5", 11 | "fastapi==0.105.0", 12 | "fastapi-versioning==0.9.1", 13 | "loguru==0.5.3", 14 | "uvicorn==0.18.0", 15 | ] 16 | 17 | [tool.uv.sources] 18 | commonwealth = { workspace = true } 19 | -------------------------------------------------------------------------------- /core/services/beacon/default-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "VERSION": 1, 3 | "default": { 4 | "domain_names": ["blueos"], 5 | "advertise": ["_http"], 6 | "ip": "ips[0]" 7 | }, 8 | "blacklist": ["lo", "docker"], 9 | "interfaces": [ 10 | { 11 | "name": "eth0", 12 | "domain_names": ["blueos", "blueos-ethernet", "companion"], 13 | "advertise": ["_mavlink", "_http"], 14 | "ip": "ips[*]" 15 | }, 16 | { 17 | "name": "wlan0", 18 | "domain_names": ["blueos-wifi"], 19 | "advertise": ["_http"], 20 | "ip": "ips[0]" 21 | }, 22 | { 23 | "name": "uap0", 24 | "domain_names": ["blueos-hotspot"], 25 | "advertise": ["_http"], 26 | "ip": "ips[0]" 27 | } 28 | ], 29 | "advertisement_types": [ 30 | { 31 | "name": "_mavlink", 32 | "protocol": "_udp", 33 | "port": 14550, 34 | "properties": "{\"name\": \"ardusub\"}" 35 | }, 36 | { 37 | "name": "_http", 38 | "protocol": "_tcp", 39 | "port": 80 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /core/services/beacon/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "beacon" 3 | version = "0.1.0" 4 | description = "MDNS service." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "commonwealth==0.1.0", 8 | "loguru==0.5.3", 9 | "uvicorn==0.18.0", 10 | "zeroconf==0.38.4", 11 | ] 12 | 13 | [tool.uv.sources] 14 | commonwealth = { workspace = true } 15 | -------------------------------------------------------------------------------- /core/services/beacon/typedefs.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class InterfaceType(str, Enum): 7 | WIRED = "WIRED" 8 | WIFI = "WIFI" 9 | HOTSPOT = "HOTSPOT" 10 | USB = "USB" 11 | UNKNOWN = "UNKNOWN" 12 | 13 | @staticmethod 14 | def guess_from_name(name: str) -> "InterfaceType": 15 | if name.startswith("wl"): 16 | return InterfaceType.WIFI 17 | if name.startswith("uap"): 18 | return InterfaceType.HOTSPOT 19 | if name.startswith("en"): 20 | return InterfaceType.WIRED 21 | if name.startswith("eth"): 22 | return InterfaceType.WIRED 23 | if name.startswith("usb"): 24 | return InterfaceType.USB 25 | return InterfaceType.UNKNOWN 26 | 27 | 28 | class MdnsEntry(BaseModel): 29 | ip: str 30 | hostname: str 31 | fullname: str 32 | interface: str 33 | interface_type: InterfaceType 34 | service_type: str 35 | 36 | 37 | class IpInfo(BaseModel): 38 | client_ip: str 39 | interface_ip: str 40 | -------------------------------------------------------------------------------- /core/services/bridget/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "bridget" 3 | version = "0.1.0" 4 | description = "Manager for 'bridges' links." 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "anyio==3.7.1", 9 | "bridges==0.1.0", 10 | "commonwealth==0.1.0", 11 | "fastapi==0.105.0", 12 | "fastapi-versioning==0.9.1", 13 | "loguru==0.5.3", 14 | "uvicorn==0.18.0", 15 | ] 16 | 17 | [tool.uv.sources] 18 | bridges = { workspace = true } 19 | commonwealth = { workspace = true } 20 | -------------------------------------------------------------------------------- /core/services/cable_guy/.gitignore: -------------------------------------------------------------------------------- 1 | html/static/ 2 | -------------------------------------------------------------------------------- /core/services/cable_guy/README.md: -------------------------------------------------------------------------------- 1 | Cable Guy 2 | 3 | API for ethernet configuration, such as: 4 | - IP: Static or Dynamic 5 | - DHCP Server: WIP 6 | 7 |

8 | 9 |

-------------------------------------------------------------------------------- /core/services/cable_guy/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/cable_guy/api/__init__.py -------------------------------------------------------------------------------- /core/services/cable_guy/config.py: -------------------------------------------------------------------------------- 1 | # This file is used to define general configurations for the app 2 | 3 | from typedefs import AddressMode, InterfaceAddress, NetworkInterface, Route 4 | from typedefs_pydantic_network_shin import IPvAnyNetwork 5 | 6 | SERVICE_NAME = "cable-guy" 7 | 8 | # If no valid configuration is found, this will be used as the default 9 | DEFAULT_NETWORK_INTERFACES = [ 10 | NetworkInterface( 11 | name="eth0", 12 | addresses=[ 13 | InterfaceAddress(ip="192.168.2.2", mode=AddressMode.BackupServer), 14 | InterfaceAddress(ip="0.0.0.0", mode=AddressMode.Client), 15 | ], 16 | routes=[ 17 | Route( 18 | destination=str(IPvAnyNetwork("224.0.0.0/4")), 19 | gateway=None, 20 | priority=None, 21 | managed=True, 22 | ) 23 | ], 24 | ), 25 | NetworkInterface(name="usb0", addresses=[InterfaceAddress(ip="192.168.3.1", mode=AddressMode.Server)], routes=[]), 26 | ] 27 | -------------------------------------------------------------------------------- /core/services/cable_guy/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cable_guy" 3 | version = "0.1.0" 4 | description = "A simple web api to provide access to ethernet configuration." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "aiofiles==0.6.0", 8 | "anyio==3.7.1", 9 | "appdirs==1.4.4", 10 | "commonwealth==0.1.0", 11 | "fastapi==0.105.0", 12 | "fastapi-versioning==0.9.1", 13 | "loguru==0.5.3", 14 | "psutil==5.7.2", 15 | "pyroute2==0.8.1", 16 | "sdbus-networkmanager==2.0.0", 17 | "starlette==0.27.0", 18 | "uvicorn==0.18.0", 19 | ] 20 | 21 | [tool.uv.sources] 22 | commonwealth = { workspace = true } 23 | -------------------------------------------------------------------------------- /core/services/commander/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "commander" 3 | version = "0.1.0" 4 | description = "Allow the usage of simple commands from the frontend." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "anyio==3.7.1", 8 | "appdirs==1.4.4", 9 | "commonwealth==0.1.0", 10 | "fastapi==0.105.0", 11 | "fastapi-versioning==0.9.1", 12 | "loguru==0.5.3", 13 | "uvicorn==0.18.0", 14 | ] 15 | 16 | [tool.uv.sources] 17 | commonwealth = { workspace = true } 18 | -------------------------------------------------------------------------------- /core/services/helper/.gitignore: -------------------------------------------------------------------------------- 1 | html/static 2 | -------------------------------------------------------------------------------- /core/services/helper/README.md: -------------------------------------------------------------------------------- 1 | # Helper 2 | 3 | Helper webpage that shows all available webpages, run at port 8080 4 | -------------------------------------------------------------------------------- /core/services/helper/nginx_parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def parse_nginx_file(filepath: str) -> dict[int, str]: 5 | with open(filepath, "r", encoding="utf-8") as f: 6 | content = f.read() 7 | # Define pattern for extracting location blocks 8 | location_block_pattern = re.compile(r"location\s+(?P/[\w-]+)\s*{(?P[^}]+)}", re.DOTALL) 9 | 10 | # Pattern for extracting proxy_pass directive 11 | proxy_pass_pattern = re.compile(r"proxy_pass\s.*:(?P\d+).*;") 12 | location_blocks = location_block_pattern.finditer(content) 13 | result = {} 14 | for match in location_blocks: 15 | location = match.group("location") 16 | block_content = match.group("block_content") 17 | 18 | proxy_pass_match = proxy_pass_pattern.search(block_content) 19 | if proxy_pass_match: 20 | proxy_port = int(proxy_pass_match.group("port")) 21 | result[proxy_port] = location 22 | 23 | return result 24 | -------------------------------------------------------------------------------- /core/services/helper/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "helper" 3 | version = "0.1.0" 4 | description = "Helper information for development." 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "aiofiles==0.6.0", 9 | "anyio==3.7.1", 10 | "beautifulsoup4==4.9.3", 11 | "commonwealth==0.1.0", 12 | "fastapi==0.105.0", 13 | "fastapi-versioning==0.9.1", 14 | "loguru==0.5.3", 15 | "psutil==5.7.2", 16 | "requests==2.26.0", 17 | "speedtest-cli==2.1.3", 18 | "starlette==0.27.0", 19 | "uvicorn==0.18.0", 20 | ] 21 | 22 | [tool.uv.sources] 23 | commonwealth = { workspace = true } 24 | -------------------------------------------------------------------------------- /core/services/kraken/api/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0406 2 | from .app import application 3 | 4 | __all__ = ["application"] 5 | -------------------------------------------------------------------------------- /core/services/kraken/api/app.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from commonwealth.utils.apis import GenericErrorHandlingRoute 4 | from fastapi import FastAPI 5 | from fastapi.responses import RedirectResponse 6 | from fastapi.staticfiles import StaticFiles 7 | from fastapi_versioning import VersionedFastAPI 8 | 9 | # Routers 10 | from api.v1.routers import extension_router_v1, index_router_v1 11 | from api.v2.routers import ( 12 | container_router_v2, 13 | extension_router_v2, 14 | index_router_v2, 15 | jobs_router_v2, 16 | manifest_router_v2, 17 | ) 18 | 19 | application = FastAPI( 20 | title="Kraken API", 21 | description="Kraken is the BlueOS service responsible for installing and managing extensions.", 22 | ) 23 | application.router.route_class = GenericErrorHandlingRoute 24 | 25 | # API v1 26 | application.include_router(index_router_v1) 27 | application.include_router(extension_router_v1) 28 | 29 | # API v2 30 | application.include_router(index_router_v2) 31 | application.include_router(container_router_v2) 32 | application.include_router(extension_router_v2) 33 | application.include_router(jobs_router_v2) 34 | application.include_router(manifest_router_v2) 35 | 36 | application = VersionedFastAPI(application, prefix_format="/v{major}.{minor}", enable_latest=True) 37 | 38 | 39 | @application.get("/", status_code=200) 40 | async def root() -> RedirectResponse: 41 | """ 42 | Root endpoint for the Kraken. 43 | """ 44 | 45 | return RedirectResponse(url="/static/pages/root.html") 46 | 47 | 48 | # Mount static files 49 | application.mount("/static", StaticFiles(directory=path.join(path.dirname(__file__), "static")), name="static") 50 | -------------------------------------------------------------------------------- /core/services/kraken/api/static/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/kraken/api/static/assets/logo.png -------------------------------------------------------------------------------- /core/services/kraken/api/static/pages/root.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Kraken 7 | 39 | 40 | 41 |
42 |

You have found the Kraken!

43 | 44 |

Check out Kraken's docs:

45 |
    46 |
  • V1
  • 47 |
  • V2
  • 48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /core/services/kraken/api/v1/routers/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0406 2 | from .extension import extension_router_v1 3 | from .index import index_router_v1 4 | 5 | __all__ = ["extension_router_v1", "index_router_v1"] 6 | -------------------------------------------------------------------------------- /core/services/kraken/api/v2/routers/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0406 2 | from .container import container_router_v2 3 | from .extension import extension_router_v2 4 | from .index import index_router_v2 5 | from .jobs import jobs_router_v2 6 | from .manifest import manifest_router_v2 7 | 8 | __all__ = ["container_router_v2", "extension_router_v2", "index_router_v2", "jobs_router_v2", "manifest_router_v2"] 9 | -------------------------------------------------------------------------------- /core/services/kraken/api/v2/routers/index.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, status 2 | from fastapi.responses import RedirectResponse 3 | from fastapi_versioning import versioned_api_route 4 | 5 | index_router_v2 = APIRouter( 6 | tags=["index_v2"], 7 | route_class=versioned_api_route(2, 0), 8 | responses={status.HTTP_404_NOT_FOUND: {"description": "Not found"}}, 9 | ) 10 | 11 | 12 | @index_router_v2.get("/", status_code=200) 13 | async def root() -> RedirectResponse: 14 | """ 15 | Root endpoint for the Kraken API V2. 16 | """ 17 | return RedirectResponse(url="/v2.0/docs") 18 | -------------------------------------------------------------------------------- /core/services/kraken/args.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class CommandLineArgs: 7 | """ 8 | Represents command-line arguments for the client. 9 | 10 | Attributes: 11 | debug (bool): Enable debug mode 12 | host (str): Host to server kraken on 13 | port (int): Port to server kraken on 14 | """ 15 | 16 | debug: bool 17 | host: str 18 | port: int 19 | 20 | @staticmethod 21 | def from_args() -> "CommandLineArgs": 22 | parser = argparse.ArgumentParser(description="Kraken Extension manager client.") 23 | 24 | parser.add_argument("--debug", action="store_true", default=False, help="Enable debug mode") 25 | parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to server kraken on") 26 | parser.add_argument("--port", type=int, default=9134, help="Port to server kraken on") 27 | 28 | args = parser.parse_args() 29 | client_args = CommandLineArgs(debug=args.debug, host=args.host, port=args.port) 30 | 31 | return client_args 32 | -------------------------------------------------------------------------------- /core/services/kraken/config.py: -------------------------------------------------------------------------------- 1 | # This file is used to define general configurations for the app 2 | 3 | SERVICE_NAME = "kraken" 4 | 5 | DEFAULT_MANIFESTS = [ 6 | { 7 | "identifier": "bluerobotics-production", 8 | "name": "BlueOS Extensions Repository", 9 | "url": "https://bluerobotics.github.io/BlueOS-Extensions-Repository/manifest.json", 10 | }, 11 | ] 12 | 13 | DEFAULT_EXTENSIONS = [ 14 | { 15 | "identifier": "blueos.major_tom", 16 | "url": "https://blueos.cloud/major_tom/install", 17 | }, 18 | ] 19 | 20 | __all__ = ["SERVICE_NAME", "DEFAULT_MANIFESTS", "DEFAULT_EXTENSIONS"] 21 | -------------------------------------------------------------------------------- /core/services/kraken/extension/__init__.py: -------------------------------------------------------------------------------- 1 | from extension.extension import Extension 2 | 3 | __all__ = ["Extension"] 4 | -------------------------------------------------------------------------------- /core/services/kraken/extension/exceptions.py: -------------------------------------------------------------------------------- 1 | class ExtensionNotFound(Exception): 2 | pass 3 | 4 | 5 | class ExtensionNotRunning(Exception): 6 | pass 7 | 8 | 9 | class ExtensionPullFailed(Exception): 10 | pass 11 | 12 | 13 | class IncompatibleExtension(Exception): 14 | pass 15 | 16 | 17 | class ExtensionInsufficientStorage(Exception): 18 | pass 19 | -------------------------------------------------------------------------------- /core/services/kraken/extension/models.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from manifest.models import ExtensionVersion, RepositoryEntry 7 | from settings import ExtensionSettings 8 | 9 | 10 | class ExtensionSourceAuth(BaseModel): 11 | username: str 12 | password: str 13 | 14 | 15 | class ExtensionSource(BaseModel): 16 | identifier: str 17 | tag: str 18 | name: str 19 | docker: str 20 | enabled: bool 21 | permissions: str 22 | user_permissions: str = "" 23 | auth: Optional[ExtensionSourceAuth] = None 24 | 25 | @staticmethod 26 | def from_settings(settings: ExtensionSettings) -> "ExtensionSource": 27 | return ExtensionSource( 28 | identifier=settings.identifier, 29 | tag=settings.tag, 30 | name=settings.name, 31 | docker=settings.docker, 32 | enabled=settings.enabled, 33 | permissions=settings.permissions, 34 | user_permissions=settings.user_permissions, 35 | ) 36 | 37 | @staticmethod 38 | def from_repository_version(entry: RepositoryEntry, version: ExtensionVersion) -> "ExtensionSource": 39 | return ExtensionSource( 40 | identifier=entry.identifier, 41 | tag=version.tag, 42 | name=entry.name, 43 | docker=entry.docker, 44 | enabled=False, 45 | permissions=json.dumps(version.permissions), 46 | user_permissions="", 47 | ) 48 | -------------------------------------------------------------------------------- /core/services/kraken/harbor/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0406 2 | from harbor.container import ContainerManager 3 | from harbor.contexts import DockerCtx 4 | 5 | __all__ = ["ContainerManager", "DockerCtx"] 6 | -------------------------------------------------------------------------------- /core/services/kraken/harbor/contexts.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from aiodocker import Docker 4 | 5 | 6 | class DockerCtx: 7 | """ 8 | Context manager for Docker clients. 9 | """ 10 | 11 | def __init__(self) -> None: 12 | self._client: Docker = Docker() 13 | 14 | async def __aenter__(self) -> Docker: 15 | return self._client 16 | 17 | async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: 18 | await self._client.close() 19 | -------------------------------------------------------------------------------- /core/services/kraken/harbor/exceptions.py: -------------------------------------------------------------------------------- 1 | class ContainerNotFound(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /core/services/kraken/harbor/models.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class ContainerModel(BaseModel): 5 | name: str 6 | image: str 7 | image_id: str 8 | status: str 9 | 10 | 11 | class ContainerUsageModel(BaseModel): 12 | cpu: float 13 | memory: float | str 14 | disk: int | str 15 | -------------------------------------------------------------------------------- /core/services/kraken/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | from jobs.jobs import JobsManager 2 | from jobs.models import Job 3 | 4 | __all__ = ["Job", "JobsManager"] 5 | -------------------------------------------------------------------------------- /core/services/kraken/jobs/exceptions.py: -------------------------------------------------------------------------------- 1 | class JobNotFound(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /core/services/kraken/jobs/models.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Any 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class JobMethod(str, Enum): 8 | GET = "GET" 9 | HEAD = "HEAD" 10 | POST = "POST" 11 | PUT = "PUT" 12 | PATCH = "PATCH" 13 | DELETE = "DELETE" 14 | 15 | 16 | class Job(BaseModel): 17 | id: str 18 | route: str 19 | method: JobMethod 20 | body: Any 21 | retries: int = 5 22 | -------------------------------------------------------------------------------- /core/services/kraken/main.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | import asyncio 3 | import logging 4 | 5 | from commonwealth.utils.logs import InterceptHandler, init_logger 6 | from commonwealth.utils.sentry_config import init_sentry_async 7 | from loguru import logger 8 | from uvicorn import Config, Server 9 | 10 | from args import CommandLineArgs 11 | from config import SERVICE_NAME 12 | 13 | logging.basicConfig(handlers=[InterceptHandler()], level=0) 14 | init_logger(SERVICE_NAME) 15 | 16 | from api import application 17 | from jobs import JobsManager 18 | from kraken import Kraken 19 | 20 | kraken = Kraken() 21 | jobs = JobsManager() 22 | 23 | 24 | async def main() -> None: 25 | await init_sentry_async(SERVICE_NAME) 26 | 27 | args = CommandLineArgs.from_args() 28 | 29 | if args.debug: 30 | logging.getLogger(SERVICE_NAME).setLevel(logging.DEBUG) 31 | 32 | logger.info("Releasing the Kraken service.") 33 | 34 | config = Config(app=application, host=args.host, port=args.port, log_config=None) 35 | server = Server(config) 36 | 37 | jobs.set_base_host(f"http://{args.host}:{args.port}") 38 | 39 | # Launch background tasks 40 | asyncio.create_task(kraken.start_cleaner_task()) 41 | asyncio.create_task(kraken.start_starter_task()) 42 | asyncio.create_task(jobs.start()) 43 | 44 | await server.serve() 45 | 46 | await jobs.stop() 47 | await kraken.stop() 48 | 49 | 50 | if __name__ == "__main__": 51 | asyncio.run(main()) 52 | -------------------------------------------------------------------------------- /core/services/kraken/manifest/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0406 2 | from manifest.manifest import ManifestManager 3 | from manifest.models import Manifest 4 | 5 | __all__ = ["ManifestManager", "Manifest"] 6 | -------------------------------------------------------------------------------- /core/services/kraken/manifest/exceptions.py: -------------------------------------------------------------------------------- 1 | class ManifestDataFetchFailed(Exception): 2 | pass 3 | 4 | 5 | class ManifestInvalidURL(Exception): 6 | pass 7 | 8 | 9 | class ManifestDataParseFailed(Exception): 10 | pass 11 | 12 | 13 | class ManifestNotFound(Exception): 14 | pass 15 | 16 | 17 | class ManifestOperationNotAllowed(Exception): 18 | pass 19 | 20 | 21 | class ManifestBackendOffline(Exception): 22 | pass 23 | -------------------------------------------------------------------------------- /core/services/kraken/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "kraken" 3 | version = "0.1.0" 4 | description = "Manages BlueOS extensions." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "aiocache==0.12.2", 8 | "aiodocker==0.21.0", 9 | "anyio==3.7.1", 10 | "appdirs==1.4.4", 11 | "commonwealth==0.1.0", 12 | "dataclass-wizard==0.22.3", 13 | "fastapi==0.105.0", 14 | "fastapi-versioning==0.9.1", 15 | "loguru==0.5.3", 16 | "psutil==5.7.2", 17 | "semver==3.0.2", 18 | "uvicorn==0.18.0", 19 | ] 20 | 21 | [tool.uv.sources] 22 | commonwealth = { workspace = true } 23 | -------------------------------------------------------------------------------- /core/services/kraken/utils.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | 3 | 4 | def has_enough_disk_space(path: str = "/", required_bytes: int = 2**30) -> bool: 5 | try: 6 | free_space = psutil.disk_usage(path).free 7 | # Default is to require 1GB (2**30) 8 | return bool(free_space > required_bytes) 9 | except FileNotFoundError: 10 | return False 11 | -------------------------------------------------------------------------------- /core/services/log_zipper/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "log_zipper" 3 | version = "0.1.0" 4 | description = "logrotate but better." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "commonwealth==0.1.0", 8 | "loguru==0.5.3", 9 | ] 10 | 11 | [tool.uv.sources] 12 | commonwealth = { workspace = true } 13 | -------------------------------------------------------------------------------- /core/services/nmea_injector/README.md: -------------------------------------------------------------------------------- 1 | NMEA injection service 2 | 3 | Currently supports the following NMEA devices: 4 | - GPS: 5 | - Following GPS sentence types are currently supported: "GGA", "RMC", "GLL" and "GNS"; 6 | - All sentence types include "lat" and "lon" fields; 7 | - GGA also includes "hdop", "alt" and "satellites_visible"; 8 | - GNS also includes "hdop", and "satellites_visible"; 9 | -------------------------------------------------------------------------------- /core/services/nmea_injector/nmea_injector/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/nmea_injector/nmea_injector/__init__.py -------------------------------------------------------------------------------- /core/services/nmea_injector/nmea_injector/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | NMEA Injector exception classes. 3 | """ 4 | 5 | 6 | class UnsupportedSentenceType(ValueError): 7 | """NMEA sentence type not supported.""" 8 | 9 | 10 | class UnsupportedSocketKind(ValueError): 11 | """Socket type provided is not supported.""" 12 | 13 | 14 | class ReceiveFailure(ValueError): 15 | """Failed to receive external data.""" 16 | -------------------------------------------------------------------------------- /core/services/nmea_injector/nmea_injector/settings.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | import pykson # type: ignore 4 | from commonwealth.settings import settings 5 | 6 | 7 | class NmeaInjectorSettingsSpecV1(pykson.JsonObject): 8 | kind = pykson.StringField() 9 | port = pykson.IntegerField() 10 | component_id = pykson.IntegerField() 11 | 12 | def __eq__(self, other: object) -> Any: 13 | if isinstance(other, NmeaInjectorSettingsSpecV1): 14 | return self.kind == other.kind and self.port == other.port 15 | return False 16 | 17 | 18 | class SettingsV1(settings.BaseSettings): 19 | VERSION = 1 20 | specs = pykson.ObjectListField(NmeaInjectorSettingsSpecV1) 21 | 22 | def __init__(self, *args: str, **kwargs: int) -> None: 23 | super().__init__(*args, **kwargs) 24 | 25 | self.VERSION = SettingsV1.VERSION 26 | 27 | def migrate(self, data: Dict[str, Any]) -> None: 28 | if data["VERSION"] == SettingsV1.VERSION: 29 | return 30 | 31 | if data["VERSION"] < SettingsV1.VERSION: 32 | super().migrate(data) 33 | 34 | data["VERSION"] = SettingsV1.VERSION 35 | -------------------------------------------------------------------------------- /core/services/nmea_injector/nmea_injector/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/nmea_injector/nmea_injector/tests/__init__.py -------------------------------------------------------------------------------- /core/services/nmea_injector/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "nmea_injector" 3 | version = "0.1.0" 4 | description = "BlueOS NMEA Injector." 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "anyio==3.7.1", 9 | "appdirs==1.4.4", 10 | "commonwealth==0.1.0", 11 | "fastapi==0.105.0", 12 | "fastapi-versioning==0.9.1", 13 | "loguru==0.5.3", 14 | "pynmea2==1.18.0", 15 | "pytest-mock==3.10.0", 16 | "starlette==0.27.0", 17 | "uvicorn==0.18.0", 18 | "validators==0.18.2", 19 | ] 20 | 21 | [tool.uv.sources] 22 | commonwealth = { workspace = true } 23 | -------------------------------------------------------------------------------- /core/services/pardal/README.md: -------------------------------------------------------------------------------- 1 | # Pardal 2 | 3 | Web service to help with speed and latency tests 4 | 5 |

6 | 7 |

-------------------------------------------------------------------------------- /core/services/pardal/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pardal" 3 | version = "0.1.0" 4 | description = "Web service to help with speed and latency tests." 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "aiohttp==3.7.4", 9 | "commonwealth==0.1.0", 10 | "loguru==0.5.3", 11 | ] 12 | 13 | [tool.uv.sources] 14 | commonwealth = { workspace = true } 15 | -------------------------------------------------------------------------------- /core/services/ping/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidDeviceDescriptor(RuntimeError): 2 | """PingDeviceDescripttor is invalid.""" 3 | 4 | 5 | class NoUDPPortAssignedToPingDriver(RuntimeError): 6 | """PingDriver attempted to start with no UDP port assigned.""" 7 | -------------------------------------------------------------------------------- /core/services/ping/ping360_driver.py: -------------------------------------------------------------------------------- 1 | from pingdriver import PingDriver 2 | from pingutils import PingDeviceDescriptor 3 | 4 | 5 | class Ping360Driver(PingDriver): 6 | def __init__(self, ping: PingDeviceDescriptor, port: int) -> None: 7 | super().__init__(ping, port) 8 | -------------------------------------------------------------------------------- /core/services/ping/ping360_ethernet_driver.py: -------------------------------------------------------------------------------- 1 | from pingdriver import PingDriver 2 | from pingutils import PingDeviceDescriptor 3 | 4 | 5 | class Ping360EthernetDriver(PingDriver): 6 | def __init__(self, ping: PingDeviceDescriptor) -> None: 7 | super().__init__(ping, None) 8 | -------------------------------------------------------------------------------- /core/services/ping/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "ping" 3 | version = "0.2.0" 4 | description = "Ping service for BlueRobotics' Ping1D and Ping360." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "anyio==3.7.1", 8 | "bluerobotics-ping==0.1.5", 9 | "bridges==0.1.0", 10 | "commonwealth==0.1.0", 11 | "fastapi==0.105.0", 12 | "fastapi-versioning==0.9.1", 13 | "loguru==0.5.3", 14 | "pyserial==3.5", 15 | "starlette==0.27.0", 16 | "uvicorn==0.18.0", 17 | ] 18 | 19 | [tool.uv.sources] 20 | bridges = { workspace = true } 21 | commonwealth = { workspace = true } 22 | -------------------------------------------------------------------------------- /core/services/ping/settings.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | import pykson # type: ignore 4 | from commonwealth.settings import settings 5 | 6 | 7 | class Ping1dSettingsSpecV1(pykson.JsonObject): 8 | port = pykson.StringField() 9 | mavlink_enabled = pykson.BooleanField() 10 | 11 | def __str__(self) -> str: 12 | return f"{self.port} - {self.mavlink_enabled}" 13 | 14 | @staticmethod 15 | def new(port: str, enabled: bool) -> "Ping1dSettingsSpecV1": 16 | return Ping1dSettingsSpecV1(port=port, mavlink_enabled=enabled) 17 | 18 | 19 | class SettingsV1(settings.BaseSettings): 20 | VERSION = 1 21 | ping1d_specs = pykson.ObjectListField(Ping1dSettingsSpecV1) 22 | # no settings for ping360 as of V1 23 | 24 | def __init__(self, *args: str, **kwargs: int) -> None: 25 | super().__init__(*args, **kwargs) 26 | 27 | self.VERSION = SettingsV1.VERSION 28 | 29 | def migrate(self, data: Dict[str, Any]) -> None: 30 | if data["VERSION"] == SettingsV1.VERSION: 31 | return 32 | 33 | if data["VERSION"] < SettingsV1.VERSION: 34 | super().migrate(data) 35 | 36 | data["VERSION"] = SettingsV1.VERSION 37 | -------------------------------------------------------------------------------- /core/services/ping/typedefs.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from pingutils import PingDeviceDescriptor 6 | 7 | 8 | class DriverStatus(BaseModel): 9 | udp_port: Optional[int] 10 | mavlink_driver_enabled: bool 11 | 12 | @staticmethod 13 | def unknown() -> "DriverStatus": 14 | return DriverStatus(udp_port=None, mavlink_driver_enabled=False) 15 | 16 | 17 | # TODO: This is a ugly workaround to have SysFS working for us 18 | # Issue: https://github.com/tiangolo/fastapi/issues/4189 19 | class PingDeviceDescriptorModel(BaseModel): 20 | ping_type: str 21 | device_id: int 22 | device_model: int 23 | device_revision: int 24 | firmware_version_major: int 25 | firmware_version_minor: int 26 | firmware_version_patch: int 27 | port: str 28 | ethernet_discovery_info: Optional[str] # ip:port string for pings found with ethernet discovery 29 | driver_status: DriverStatus 30 | 31 | @staticmethod 32 | def from_descriptor(descriptor: PingDeviceDescriptor) -> "PingDeviceDescriptorModel": 33 | return PingDeviceDescriptorModel( 34 | ping_type=str(descriptor.ping_type), 35 | device_id=descriptor.device_id, 36 | device_model=descriptor.device_model, 37 | device_revision=descriptor.device_revision, 38 | firmware_version_major=descriptor.firmware_version_major, 39 | firmware_version_minor=descriptor.firmware_version_minor, 40 | firmware_version_patch=descriptor.firmware_version_patch, 41 | port=descriptor.port.device if descriptor.port is not None else "", 42 | ethernet_discovery_info=descriptor.ethernet_discovery_info, 43 | driver_status=descriptor.driver.driver_status if descriptor.driver is not None else DriverStatus.unknown(), 44 | ) 45 | -------------------------------------------------------------------------------- /core/services/versionchooser/.gitignore: -------------------------------------------------------------------------------- 1 | frontend/static/ 2 | -------------------------------------------------------------------------------- /core/services/versionchooser/frontend/static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #ECF0F5; 3 | } 4 | 5 | .panel .panel-title .btn-custom { 6 | width: auto; 7 | } 8 | 9 | .disabled { 10 | cursor:not-allowed; 11 | } 12 | 13 | #repo { 14 | width: 50%; 15 | } 16 | 17 | .docker-pull-output { 18 | white-space: pre; font-family:monospace; 19 | } 20 | 21 | .image-small { 22 | font-size: 70%; 23 | } 24 | 25 | /* from https://loading.io/css/ */ 26 | .lds-dual-ring { 27 | display: block; 28 | width: 80px; 29 | height: 80px; 30 | margin: auto; 31 | } 32 | .lds-dual-ring:after { 33 | content: " "; 34 | display: block; 35 | width: 64px; 36 | height: 64px; 37 | margin: 8px; 38 | border-radius: 50%; 39 | border: 6px solid #fff; 40 | border-color: #2799D0 transparent #2799D0 transparent; 41 | animation: lds-dual-ring 1.2s linear infinite; 42 | } 43 | @keyframes lds-dual-ring { 44 | 0% { 45 | transform: rotate(0deg); 46 | } 47 | 100% { 48 | transform: rotate(360deg); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/services/versionchooser/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "versionchooser" 3 | version = "0.1.0" 4 | description = "Blue Robotics Ardusub BlueOS Version Chooser." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "aiodocker==0.21.0", 8 | "aiohttp==3.7.4", 9 | "aiohttp-jinja2==1.4.2", 10 | "appdirs==1.4.4", 11 | "asyncmock==0.4.2", 12 | "attrs==20.3.0", 13 | "commonwealth==0.1.0", 14 | "connexion[aiohttp,swagger-ui]==2.14.2", 15 | "docker==6.0.0", 16 | "itsdangerous==2.1.1", 17 | "jinja2==3.0.3", 18 | "jsonschema==3.2.0", 19 | "loguru==0.5.3", 20 | "pyrsistent==0.16.0", 21 | "pytest-asyncio==0.14.0", 22 | "werkzeug==2.2.3", 23 | "yarl==1.12.1", 24 | ] 25 | 26 | [tool.uv.sources] 27 | commonwealth = { workspace = true } 28 | -------------------------------------------------------------------------------- /core/services/versionchooser/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/versionchooser/utils/__init__.py -------------------------------------------------------------------------------- /core/services/wifi/.gitignore: -------------------------------------------------------------------------------- 1 | frontend/static/ -------------------------------------------------------------------------------- /core/services/wifi/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wifi-manager exception classes. 3 | """ 4 | 5 | 6 | class ParseError(ValueError): 7 | """Raise for errors regarding fail parsing string data to proper structs.""" 8 | 9 | 10 | class FetchError(ValueError): 11 | """Raise for errors regarding fail on fetching data.""" 12 | 13 | 14 | class BusyError(ValueError): 15 | """Raise for errors regarding excessive number of requests on a given time.""" 16 | 17 | 18 | class SockCommError(ConnectionError): 19 | """Raise for errors regarding WPA socket communication.""" 20 | 21 | 22 | class WPAOperationFail(ConnectionError): 23 | """Raised when a WPA operation fails.""" 24 | 25 | 26 | class NetworkAddFail(ValueError): 27 | """Raised when a WPA add_network operation fails to return an int.""" 28 | -------------------------------------------------------------------------------- /core/services/wifi/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "wifi" 3 | version = "0.1.0" 4 | description = "Wifi manager." 5 | requires-python = ">=3.11" 6 | dependencies = [ 7 | "aiofiles==0.6.0", 8 | "anyio==3.7.1", 9 | "commonwealth==0.1.0", 10 | "fastapi==0.105.0", 11 | "fastapi-versioning==0.9.1", 12 | "loguru==0.5.3", 13 | "starlette==0.27.0", 14 | "tabulate==0.8.9", 15 | "types-tabulate==0.8.3", 16 | "uvicorn==0.18.0", 17 | ] 18 | 19 | [tool.uv.sources] 20 | commonwealth = { workspace = true } 21 | -------------------------------------------------------------------------------- /core/services/wifi/settings.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | import pykson # type: ignore 4 | from commonwealth.settings import settings 5 | 6 | 7 | class SettingsV1(settings.BaseSettings): 8 | VERSION = 1 9 | hotspot_enabled = pykson.BooleanField() 10 | hotspot_ssid = pykson.StringField() 11 | hotspot_password = pykson.StringField() 12 | smart_hotspot_enabled = pykson.BooleanField() 13 | 14 | def __init__(self, *args: str, **kwargs: int) -> None: 15 | super().__init__(*args, **kwargs) 16 | 17 | self.VERSION = SettingsV1.VERSION 18 | 19 | def migrate(self, data: Dict[str, Any]) -> None: 20 | if data["VERSION"] == SettingsV1.VERSION: 21 | return 22 | 23 | if data["VERSION"] < SettingsV1.VERSION: 24 | super().migrate(data) 25 | 26 | data["VERSION"] = SettingsV1.VERSION 27 | -------------------------------------------------------------------------------- /core/services/wifi/typedefs.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class HotspotStatus(BaseModel): 8 | supported: bool 9 | enabled: bool 10 | 11 | 12 | class WifiStatus(BaseModel): 13 | bssid: Optional[str] 14 | freq: Optional[str] 15 | ssid: Optional[str] 16 | id: Optional[str] 17 | mode: Optional[str] 18 | wifi_generation: Optional[str] 19 | pairwise_cipher: Optional[str] 20 | group_cipher: Optional[str] 21 | key_mgmt: Optional[str] 22 | wpa_state: Optional[str] 23 | ip_address: Optional[str] 24 | p2p_device_address: Optional[str] 25 | address: Optional[str] 26 | uuid: Optional[str] 27 | ieee80211ac: Optional[str] 28 | state: Optional[str] 29 | disabled: Optional[str] 30 | 31 | 32 | class ScannedWifiNetwork(BaseModel): 33 | ssid: Optional[str] 34 | bssid: str 35 | flags: str 36 | frequency: int 37 | signallevel: int 38 | 39 | 40 | class SavedWifiNetwork(BaseModel): 41 | networkid: int 42 | ssid: str 43 | bssid: Optional[str] 44 | flags: Optional[str] 45 | nm_id: Optional[str] 46 | 47 | 48 | class WifiCredentials(BaseModel): 49 | ssid: str 50 | password: str 51 | 52 | 53 | class ConnectionStatus(str, Enum): 54 | DISCONNECTING = "DISCONNECTING" 55 | JUST_DISCONNECTED = "JUST_DISCONNECTED" 56 | STILL_DISCONNECTED = "STILL_DISCONNECTED" 57 | CONNECTING = "CONNECTING" 58 | JUST_CONNECTED = "JUST_CONNECTED" 59 | STILL_CONNECTED = "STILL_CONNECTED" 60 | UNKNOWN = "UNKNOWN" 61 | -------------------------------------------------------------------------------- /core/services/wifi/wifi_handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/wifi/wifi_handlers/__init__.py -------------------------------------------------------------------------------- /core/services/wifi/wifi_handlers/wpa_supplicant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/core/services/wifi/wifi_handlers/wpa_supplicant/__init__.py -------------------------------------------------------------------------------- /core/tools/ardupilot_tools/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Immediately exit on errors 4 | set -e 5 | 6 | # Create the logs folder before ardupilot so we prevent a Filebrowser error if the user opens it 7 | # before arming the vehicle for the first time. 8 | if [ -z "$NOSUDO" ]; then 9 | $SUDO mkdir -p /root/.config/ardupilot-manager/firmware/logs/ 10 | fi 11 | 12 | # Download firmware defaults 13 | AUTOPILOT_DEFAULT_FIRMWARE_PATH="$HOME/blueos-files/ardupilot-manager/default" 14 | 15 | download_if_not_exists() { 16 | local url=$1 17 | local dest=$2 18 | 19 | if [ ! -f "$dest" ]; then 20 | echo "Downloading $url to $dest" 21 | mkdir -p "$(dirname "$dest")" 22 | wget -q "$url" -O "$dest" && echo "Downloaded $dest" & 23 | else 24 | echo "File $dest already exists. Skipping download." 25 | fi 26 | } 27 | 28 | download_if_not_exists "https://firmware.ardupilot.org/Sub/stable-4.5.3/navigator/ardusub" \ 29 | "$AUTOPILOT_DEFAULT_FIRMWARE_PATH/ardupilot_navigator/ardusub" 30 | 31 | download_if_not_exists "https://firmware.ardupilot.org/Sub/stable-4.5.3/navigator64/ardusub" \ 32 | "$AUTOPILOT_DEFAULT_FIRMWARE_PATH/ardupilot_navigator64/ardusub" 33 | 34 | download_if_not_exists "https://firmware.ardupilot.org/Sub/stable-4.5.3/Pixhawk1/ardusub.apj" \ 35 | "$AUTOPILOT_DEFAULT_FIRMWARE_PATH/ardupilot_pixhawk1/ardusub.apj" 36 | 37 | download_if_not_exists "https://firmware.ardupilot.org/Sub/stable-4.5.3/Pixhawk4/ardusub.apj" \ 38 | "$AUTOPILOT_DEFAULT_FIRMWARE_PATH/ardupilot_pixhawk4/ardusub.apj" 39 | 40 | # Wait for all background jobs to finish 41 | wait 42 | echo "All downloads completed." 43 | -------------------------------------------------------------------------------- /core/tools/blueos_startup_update/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | SCRIPTS_PATH=$(dirname "$0") 7 | cp $PWD/$SCRIPTS_PATH/blueos_startup_update.py /usr/bin/ 8 | -------------------------------------------------------------------------------- /core/tools/bridges/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Immediately exit on errors 4 | set -e 5 | 6 | VERSION="0.10.3" 7 | REPOSITORY_ORG="patrickelectric" 8 | REPOSITORY_NAME="bridges" 9 | PROJECT_NAME="$REPOSITORY_NAME" 10 | REPOSITORY_URL="https://github.com/$REPOSITORY_ORG/$REPOSITORY_NAME" 11 | 12 | echo "Installing project $PROJECT_NAME version $VERSION" 13 | 14 | # Step 1: Prepare the download URL 15 | 16 | ARCH="$(uname -m)" 17 | case "$ARCH" in 18 | x86_64 | amd64) 19 | BUILD_NAME="x86_64-unknown-linux-musl" 20 | ;; 21 | armv7l | armhf) 22 | BUILD_NAME="armv7-unknown-linux-musleabihf" 23 | ;; 24 | aarch64 | arm64) 25 | BUILD_NAME="aarch64-unknown-linux-musl" 26 | ;; 27 | *) 28 | echo "Architecture: $ARCH is unsupported, please create a new issue on https://github.com/bluerobotics/BlueOS/issues" 29 | exit 1 30 | ;; 31 | esac 32 | ARTIFACT_NAME="$PROJECT_NAME-$BUILD_NAME" 33 | echo "For architecture $ARCH, using build $BUILD_NAME" 34 | 35 | REMOTE_URL="$REPOSITORY_URL/releases/download/$VERSION/$ARTIFACT_NAME" 36 | echo "Remote URL is $REMOTE_URL" 37 | 38 | # Step 2: Prepare the installation path 39 | 40 | if [ -n "$VIRTUAL_ENV" ]; then 41 | BIN_DIR="$VIRTUAL_ENV/bin" 42 | else 43 | BIN_DIR="/usr/bin" 44 | fi 45 | mkdir -p "$BIN_DIR" 46 | 47 | BINARY_PATH="$BIN_DIR/$PROJECT_NAME" 48 | echo "Installing to $BINARY_PATH" 49 | 50 | # Step 3: Download and install 51 | 52 | wget -q "$REMOTE_URL" -O "$BINARY_PATH" 53 | chmod +x "$BINARY_PATH" 54 | 55 | echo "Installed binary type: $(file "$(which "$BINARY_PATH")")" 56 | 57 | echo "Finished installing $PROJECT_NAME" 58 | -------------------------------------------------------------------------------- /core/tools/install-python-libs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Script to install python binaries or single files that should be available within BlueOS python venv 3 | 4 | # Immediately exit on errors 5 | set -e 6 | 7 | TOOLS=( 8 | ardupilot_tools 9 | ) 10 | 11 | parallel --halt now,fail=1 '/home/pi/tools/{}/setup-python-libs.sh' ::: "${TOOLS[@]}" 12 | -------------------------------------------------------------------------------- /core/tools/install-static-binaries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Script to install tools that are simple static binaries 3 | 4 | # Immediately exit on errors 5 | set -e 6 | 7 | # Remember to update Dockerfile to copy from multistage 8 | TOOLS=( 9 | blueos_startup_update 10 | bridges 11 | linux2rest 12 | machineid 13 | mavlink2rest 14 | mavlink_server 15 | ttyd 16 | zenoh 17 | ) 18 | 19 | parallel --halt now,fail=1 'RUNNING_IN_CI=true /home/pi/tools/{}/bootstrap.sh' ::: "${TOOLS[@]}" 20 | -------------------------------------------------------------------------------- /core/tools/install-system-tools.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Script to install tools that needs to configure filesystem of the running docker 3 | 4 | # Immediately exit on errors 5 | set -e 6 | 7 | TOOLS=( 8 | ardupilot_tools 9 | filebrowser 10 | linux2rest 11 | logviewer 12 | mavlink_camera_manager 13 | scripts 14 | wifi 15 | ) 16 | 17 | parallel --halt now,fail=1 '/home/pi/tools/{}/bootstrap.sh' ::: "${TOOLS[@]}" 18 | 19 | # Tools that uses apt to do the installation 20 | # APT is terrible like pip and don't know how to handle parallel installation 21 | # These should periodically be moved onto the base image 22 | apt update && apt install -y --no-install-recommends dhcpcd5 iptables iproute2 isc-dhcp-client nmap 23 | -------------------------------------------------------------------------------- /core/tools/linux2rest/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | VERSION="v0.6.3" 7 | REPOSITORY_ORG="patrickelectric" 8 | REPOSITORY_NAME="linux2rest" 9 | PROJECT_NAME="$REPOSITORY_NAME" 10 | REPOSITORY_URL="https://github.com/$REPOSITORY_ORG/$REPOSITORY_NAME" 11 | 12 | echo "Installing project $PROJECT_NAME version $VERSION" 13 | 14 | # Step 1: Prepare the download URL 15 | 16 | ARCH="$(uname -m)" 17 | case "$ARCH" in 18 | x86_64 | amd64) 19 | BUILD_NAME="x86_64-unknown-linux-gnu" 20 | ;; 21 | armv7l | armhf) 22 | BUILD_NAME="armv7-unknown-linux-gnueabihf" 23 | ;; 24 | aarch64 | arm64) 25 | BUILD_NAME="aarch64-unknown-linux-gnu" 26 | ;; 27 | *) 28 | echo "Architecture: $ARCH is unsupported, please create a new issue on https://github.com/bluerobotics/BlueOS/issues" 29 | exit 1 30 | ;; 31 | esac 32 | ARTIFACT_NAME="$PROJECT_NAME-$BUILD_NAME" 33 | echo "For architecture $ARCH, using build $BUILD_NAME" 34 | 35 | REMOTE_URL="$REPOSITORY_URL/releases/download/$VERSION/$ARTIFACT_NAME" 36 | echo "Remote URL is $REMOTE_URL" 37 | 38 | # Step 2: Prepare the installation path 39 | 40 | if [ -n "$VIRTUAL_ENV" ]; then 41 | BIN_DIR="$VIRTUAL_ENV/bin" 42 | else 43 | BIN_DIR="/usr/bin" 44 | fi 45 | mkdir -p "$BIN_DIR" 46 | 47 | BINARY_PATH="$BIN_DIR/$PROJECT_NAME" 48 | echo "Installing to $BINARY_PATH" 49 | 50 | # Step 3: Download and install 51 | 52 | wget -q "$REMOTE_URL" -O "$BINARY_PATH" 53 | chmod +x "$BINARY_PATH" 54 | 55 | echo "Installed binary type: $(file "$(which "$BINARY_PATH")")" 56 | 57 | echo "Finished installing $PROJECT_NAME" 58 | -------------------------------------------------------------------------------- /core/tools/logviewer/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Immediately exit on errors 4 | set -e 5 | 6 | VERSION="v1.0.1" 7 | REPOSITORY_ORG="Ardupilot" 8 | REPOSITORY_NAME="UAVLogViewer" 9 | PROJECT_NAME="logviewer" 10 | REPOSITORY_URL="https://github.com/$REPOSITORY_ORG/$REPOSITORY_NAME" 11 | 12 | echo "Installing project $PROJECT_NAME version $VERSION" 13 | 14 | # Step 1: Prepare the download URL 15 | 16 | ARCH="$(uname -m)" 17 | ARTIFACT_NAME="$PROJECT_NAME.tar.gz" 18 | echo "For architecture $ARCH, using build $BUILD_NAME" 19 | 20 | REMOTE_URL="$REPOSITORY_URL/releases/download/$VERSION/$ARTIFACT_NAME" 21 | echo "Remote URL is $REMOTE_URL" 22 | 23 | # Step 2: Prepare the installation path 24 | 25 | INSTALL_FOLDER="/var/www/html/$PROJECT_NAME" 26 | mkdir -p "$INSTALL_FOLDER" 27 | 28 | echo "Installing to $INSTALL_FOLDER" 29 | 30 | # Step 3: Download and install 31 | 32 | wget -q "$REMOTE_URL" -O - | tar -zxf - -C "$INSTALL_FOLDER" 33 | find "$INSTALL_FOLDER/dist" -name "*.gz" -type f -delete 34 | mv "$INSTALL_FOLDER"/dist/* "$INSTALL_FOLDER" 35 | rm -rf "$INSTALL_FOLDER/dist" 36 | 37 | echo "Finished installing $PROJECT_NAME" 38 | -------------------------------------------------------------------------------- /core/tools/machineid/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Immediately exit on errors 4 | set -e 5 | 6 | VERSION="0.2.3" 7 | REPOSITORY_ORG="patrickelectric" 8 | REPOSITORY_NAME="machineid-cli" 9 | PROJECT_NAME="$REPOSITORY_NAME" 10 | REPOSITORY_URL="https://github.com/$REPOSITORY_ORG/$REPOSITORY_NAME" 11 | 12 | echo "Installing project $PROJECT_NAME version $VERSION" 13 | 14 | # Step 1: Prepare the download URL 15 | 16 | ARCH="$(uname -m)" 17 | case "$ARCH" in 18 | x86_64 | amd64) 19 | BUILD_NAME="x86_64-unknown-linux-musl" 20 | ;; 21 | armv7l | armhf) 22 | BUILD_NAME="armv7-unknown-linux-musleabihf" 23 | ;; 24 | aarch64 | arm64) 25 | BUILD_NAME="aarch64-unknown-linux-musl" 26 | ;; 27 | *) 28 | echo "Architecture: $ARCH is unsupported, please create a new issue on https://github.com/bluerobotics/BlueOS/issues" 29 | exit 1 30 | ;; 31 | esac 32 | ARTIFACT_NAME="$PROJECT_NAME-$BUILD_NAME" 33 | echo "For architecture $ARCH, using build $BUILD_NAME" 34 | 35 | REMOTE_URL="$REPOSITORY_URL/releases/download/$VERSION/$ARTIFACT_NAME" 36 | echo "Remote URL is $REMOTE_URL" 37 | 38 | # Step 2: Prepare the installation path 39 | 40 | if [ -n "$VIRTUAL_ENV" ]; then 41 | BIN_DIR="$VIRTUAL_ENV/bin" 42 | else 43 | BIN_DIR="/usr/bin" 44 | fi 45 | mkdir -p "$BIN_DIR" 46 | 47 | BINARY_PATH="$BIN_DIR/$PROJECT_NAME" 48 | echo "Installing to $BINARY_PATH" 49 | 50 | # Step 3: Download and install 51 | 52 | wget -q "$REMOTE_URL" -O "$BINARY_PATH" 53 | chmod +x "$BINARY_PATH" 54 | 55 | echo "Installed binary type: $(file "$(which "$BINARY_PATH")")" 56 | 57 | echo "Finished installing $PROJECT_NAME" 58 | -------------------------------------------------------------------------------- /core/tools/mavlink2rest/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Immediately exit on errors 4 | set -e 5 | 6 | VERSION="t0.11.24" 7 | REPOSITORY_ORG="mavlink" 8 | REPOSITORY_NAME="mavlink2rest" 9 | PROJECT_NAME="$REPOSITORY_NAME" 10 | REPOSITORY_URL="https://github.com/$REPOSITORY_ORG/$REPOSITORY_NAME" 11 | 12 | echo "Installing project $PROJECT_NAME version $VERSION" 13 | 14 | # Step 1: Prepare the download URL 15 | 16 | ARCH="$(uname -m)" 17 | case "$ARCH" in 18 | x86_64 | amd64) 19 | BUILD_NAME="x86_64-unknown-linux-musl" 20 | ;; 21 | armv7l | armhf) 22 | BUILD_NAME="armv7-unknown-linux-musleabihf" 23 | ;; 24 | aarch64 | arm64) 25 | BUILD_NAME="aarch64-unknown-linux-musl" 26 | ;; 27 | *) 28 | echo "Architecture: $ARCH is unsupported, please create a new issue on https://github.com/bluerobotics/BlueOS/issues" 29 | exit 1 30 | ;; 31 | esac 32 | ARTIFACT_NAME="$PROJECT_NAME-$BUILD_NAME" 33 | echo "For architecture $ARCH, using build $BUILD_NAME" 34 | 35 | REMOTE_URL="$REPOSITORY_URL/releases/download/$VERSION/$ARTIFACT_NAME" 36 | echo "Remote URL is $REMOTE_URL" 37 | 38 | # Step 2: Prepare the installation path 39 | 40 | if [ -n "$VIRTUAL_ENV" ]; then 41 | BIN_DIR="$VIRTUAL_ENV/bin" 42 | else 43 | BIN_DIR="/usr/bin" 44 | fi 45 | mkdir -p "$BIN_DIR" 46 | 47 | BINARY_PATH="$BIN_DIR/$PROJECT_NAME" 48 | echo "Installing to $BINARY_PATH" 49 | 50 | # Step 3: Download and install 51 | 52 | wget -q "$REMOTE_URL" -O "$BINARY_PATH" 53 | chmod +x "$BINARY_PATH" 54 | 55 | echo "Installed binary type: $(file "$(which "$BINARY_PATH")")" 56 | 57 | echo "Finished installing $PROJECT_NAME" 58 | -------------------------------------------------------------------------------- /core/tools/mavlink_camera_manager/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | VERSION="t3.19.3" 7 | REPOSITORY_ORG="mavlink" 8 | REPOSITORY_NAME="mavlink-camera-manager" 9 | PROJECT_NAME="$REPOSITORY_NAME" 10 | REPOSITORY_URL="https://github.com/$REPOSITORY_ORG/$REPOSITORY_NAME" 11 | 12 | echo "Installing project $PROJECT_NAME version $VERSION" 13 | 14 | # Step 1: Prepare the download URL 15 | 16 | ARCH="$(uname -m)" 17 | case "$ARCH" in 18 | x86_64 | amd64) 19 | BUILD_NAME="linux-desktop" 20 | ;; 21 | armv7l | armhf) 22 | BUILD_NAME="armv7" 23 | ;; 24 | aarch64 | arm64) 25 | BUILD_NAME="aarch64" 26 | ;; 27 | *) 28 | echo "Architecture: $ARCH is unsupported, please create a new issue on https://github.com/bluerobotics/BlueOS/issues" 29 | exit 1 30 | ;; 31 | esac 32 | ARTIFACT_NAME="$PROJECT_NAME-$BUILD_NAME" 33 | echo "For architecture $ARCH, using build $BUILD_NAME" 34 | 35 | REMOTE_URL="$REPOSITORY_URL/releases/download/$VERSION/$ARTIFACT_NAME" 36 | echo "Remote URL is $REMOTE_URL" 37 | 38 | # Step 2: Prepare the installation path 39 | 40 | if [ -n "$VIRTUAL_ENV" ]; then 41 | BIN_DIR="$VIRTUAL_ENV/bin" 42 | else 43 | BIN_DIR="/usr/bin" 44 | fi 45 | mkdir -p "$BIN_DIR" 46 | 47 | BINARY_PATH="$BIN_DIR/$PROJECT_NAME" 48 | echo "Installing to $BINARY_PATH" 49 | 50 | # Step 3: Download and install 51 | 52 | wget -q "$REMOTE_URL" -O "$BINARY_PATH" 53 | chmod +x "$BINARY_PATH" 54 | 55 | echo "Installed binary type: $(file "$(which "$BINARY_PATH")")" 56 | 57 | echo "Finished installing $PROJECT_NAME" 58 | -------------------------------------------------------------------------------- /core/tools/mavlink_server/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Immediately exit on errors 4 | set -e 5 | 6 | VERSION="0.5.5" 7 | PROJECT_NAME="mavlink-server" 8 | REPOSITORY_ORG="bluerobotics" 9 | REPOSITORY_NAME="$PROJECT_NAME" 10 | REPOSITORY_URL="https://github.com/$REPOSITORY_ORG/$REPOSITORY_NAME" 11 | 12 | echo "Installing project $PROJECT_NAME version $VERSION" 13 | 14 | # Step 1: Prepare the download URL 15 | 16 | ARCH="$(uname -m)" 17 | case "$ARCH" in 18 | x86_64 | amd64) 19 | BUILD_NAME="x86_64-unknown-linux-musl" 20 | ;; 21 | armv7l | armhf) 22 | BUILD_NAME="armv7-unknown-linux-musleabihf" 23 | ;; 24 | aarch64 | arm64) 25 | BUILD_NAME="aarch64-unknown-linux-musl" 26 | ;; 27 | *) 28 | echo "Architecture: $ARCH is unsupported, please create a new issue on https://github.com/bluerobotics/BlueOS/issues" 29 | exit 1 30 | ;; 31 | esac 32 | ARTIFACT_NAME="$PROJECT_NAME-$BUILD_NAME" 33 | echo "For architecture $ARCH, using build $BUILD_NAME" 34 | 35 | REMOTE_URL="$REPOSITORY_URL/releases/download/$VERSION/$ARTIFACT_NAME" 36 | echo "Remote URL is $REMOTE_URL" 37 | 38 | # Step 2: Prepare the installation path 39 | 40 | if [ -n "$VIRTUAL_ENV" ]; then 41 | BIN_DIR="$VIRTUAL_ENV/bin" 42 | else 43 | BIN_DIR="/usr/bin" 44 | fi 45 | mkdir -p "$BIN_DIR" 46 | 47 | BINARY_PATH="$BIN_DIR/$PROJECT_NAME" 48 | echo "Installing to $BINARY_PATH" 49 | 50 | # Step 3: Download and install 51 | 52 | wget -q "$REMOTE_URL" -O "$BINARY_PATH" 53 | chmod +x "$BINARY_PATH" 54 | 55 | echo "Installed binary type: $(file "$(which "$BINARY_PATH")")" 56 | 57 | echo "Finished installing $PROJECT_NAME" 58 | -------------------------------------------------------------------------------- /core/tools/nginx/cors.conf: -------------------------------------------------------------------------------- 1 | # Cors Preflight methods needs additional options and different Return Code 2 | if ($request_method = 'OPTIONS') { 3 | add_header 'Access-Control-Allow-Origin' '*' always; 4 | add_header 'Access-Control-Allow-Credentials' '*' always; 5 | add_header 'Access-Control-Allow-Methods' '*' always; 6 | add_header 'Access-Control-Allow-Headers' '*' always; 7 | add_header 'Access-Control-Max-Age' 1728000; 8 | add_header 'Content-Type' 'text/plain charset=UTF-8'; 9 | add_header 'Content-Length' 0; 10 | return 204; 11 | } 12 | 13 | add_header 'Access-Control-Allow-Origin' '*' always; 14 | add_header 'Access-Control-Allow-Credentials' '*' always; 15 | add_header 'Access-Control-Allow-Methods' '*' always; 16 | add_header 'Access-Control-Allow-Headers' '*' always; 17 | -------------------------------------------------------------------------------- /core/tools/scripts/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | SCRIPTS_PATH="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )"; 7 | cp $SCRIPTS_PATH/red-pill /usr/bin/ 8 | -------------------------------------------------------------------------------- /core/tools/scripts/red-pill: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | usage() { 4 | echo "Usage: $0 [-h] [-u user]" 5 | echo 6 | echo " -h, Show this help message" 7 | echo " -u, Define a user (default: pi)" 8 | exit 1 9 | } 10 | 11 | # Default values 12 | user=${SSH_USER:-pi} 13 | 14 | while getopts ":hu:" opt; do 15 | case ${opt} in 16 | h ) 17 | usage 18 | ;; 19 | u ) 20 | user=$OPTARG 21 | ;; 22 | \? ) 23 | echo "Invalid Option: -$OPTARG" 1>&2 24 | usage 25 | ;; 26 | : ) 27 | echo "Invalid Option: -$OPTARG requires an argument" 1>&2 28 | usage 29 | ;; 30 | esac 31 | done 32 | shift $((OPTIND -1)) 33 | 34 | # Don't simply ignore additional arguments 35 | if [ $# -gt 0 ]; then 36 | echo "Invalid Argument: $1" 1>&2 37 | usage 38 | fi 39 | 40 | echo "You took the red pill." 41 | echo "You stay in Wonderland, and I show you how deep the rabbit hole goes." 42 | echo "Remember, all I'm offering is the truth. Nothing more." 43 | echo "Exiting from docker, welcome to the real world." 44 | ssh -i /root/.config/.ssh/id_rsa -o StrictHostKeyChecking=no $user@localhost 45 | -------------------------------------------------------------------------------- /core/tools/ttyd/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Immediately exit on errors 4 | set -e 5 | 6 | VERSION="1.6.3" 7 | REPOSITORY_ORG="tsl0922" 8 | REPOSITORY_NAME="ttyd" 9 | PROJECT_NAME="$REPOSITORY_NAME" 10 | REPOSITORY_URL="https://github.com/$REPOSITORY_ORG/$REPOSITORY_NAME" 11 | 12 | echo "Installing project $PROJECT_NAME version $VERSION" 13 | 14 | # Step 1: Prepare the download URL 15 | 16 | ARCH="$(uname -m)" 17 | case "$ARCH" in 18 | x86_64 | amd64) 19 | BUILD_NAME="x86_64" 20 | ;; 21 | armv7l | armhf) 22 | BUILD_NAME="armhf" 23 | ;; 24 | aarch64 | arm64) 25 | BUILD_NAME="aarch64" 26 | ;; 27 | *) 28 | echo "Architecture: $ARCH is unsupported, please create a new issue on https://github.com/bluerobotics/BlueOS/issues" 29 | exit 1 30 | ;; 31 | esac 32 | ARTIFACT_NAME="$PROJECT_NAME.$BUILD_NAME" 33 | echo "For architecture $ARCH, using build $BUILD_NAME" 34 | 35 | REMOTE_URL="$REPOSITORY_URL/releases/download/$VERSION/$ARTIFACT_NAME" 36 | echo "Remote URL is $REMOTE_URL" 37 | 38 | # Step 2: Prepare the installation path 39 | 40 | if [ -n "$VIRTUAL_ENV" ]; then 41 | BIN_DIR="$VIRTUAL_ENV/bin" 42 | else 43 | BIN_DIR="/usr/bin" 44 | fi 45 | mkdir -p "$BIN_DIR" 46 | 47 | BINARY_PATH="$BIN_DIR/$PROJECT_NAME" 48 | echo "Installing to $BINARY_PATH" 49 | 50 | # Step 3: Download and install 51 | 52 | wget -q "$REMOTE_URL" -O "$BINARY_PATH" 53 | chmod +x "$BINARY_PATH" 54 | 55 | echo "Installed binary type: $(file "$(which "$BINARY_PATH")")" 56 | 57 | echo "Finished installing $PROJECT_NAME" 58 | -------------------------------------------------------------------------------- /core/tools/wifi/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Immediately exit on errors 4 | set -e 5 | 6 | # Wifi service / Bind path for wpa 7 | mkdir -p /var/run/wpa_supplicant/ 8 | 9 | # Install create_ap script 10 | alias curl="curl --retry 6 --max-time 15 --retry-all-errors --retry-delay 20 --connect-timeout 60" 11 | 12 | CREATE_AP_COMMIT="4627e3c0ec0a7c86ba08089a8a00d32a61a05f1e" 13 | CREATE_AP_URL="https://raw.githubusercontent.com/lakinduakash/linux-wifi-hotspot/${CREATE_AP_COMMIT}/src/scripts/create_ap" 14 | CREATE_AP_DEST="/usr/bin/create_ap" 15 | 16 | echo "Downloading create_ap script from commit: ${CREATE_AP_COMMIT}" 17 | 18 | curl -fsSL "${CREATE_AP_URL}" -o "${CREATE_AP_DEST}" 19 | 20 | chmod 755 "${CREATE_AP_DEST}" 21 | 22 | echo " - create_ap installed successfully at ${CREATE_AP_DEST}" 23 | -------------------------------------------------------------------------------- /core/tools/zenoh/blueos-zenoh.json5: -------------------------------------------------------------------------------- 1 | { 2 | plugins: { 3 | rest: { http_port: 7117 }, 4 | remote_api: { websocket_port: 7118 } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /deploy/README.md: -------------------------------------------------------------------------------- 1 | # Temporary helpers to generate BlueOS images -------------------------------------------------------------------------------- /deploy/expand_fs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Borrowed and modified from Raspbian usr/lib/raspi-config/init_resize.sh 4 | 5 | # Abort if we are not on a Raspberry Pi 6 | if grep -q 'Hardware.*: BCM2' /proc/cpuinfo; then 7 | echo "Expanding file system on Raspberry Pi!" 8 | else 9 | echo "This script should only be run on a Raspberry Pi!" 10 | exit 1 11 | fi 12 | 13 | get_variables () { 14 | ROOT_PART_DEV=$(findmnt / -o source -n) 15 | #/dev/mmcblk0p2 16 | ROOT_PART_NAME=$(echo "$ROOT_PART_DEV" | cut -d "/" -f 3) 17 | #mmcblk0p2 18 | ROOT_DEV_NAME=$(echo /sys/block/*/"${ROOT_PART_NAME}" | cut -d "/" -f 4) 19 | #mmcblk0 20 | ROOT_DEV="/dev/${ROOT_DEV_NAME}" 21 | #/dev/mmcblk0 22 | ROOT_PART_NUM=$(cat "/sys/block/${ROOT_DEV_NAME}/${ROOT_PART_NAME}/partition") 23 | 24 | check_noobs 25 | 26 | ROOT_DEV_SIZE=$(cat "/sys/block/${ROOT_DEV_NAME}/size") 27 | TARGET_END=$((ROOT_DEV_SIZE - 1)) 28 | 29 | PARTITION_TABLE=$(parted -m "$ROOT_DEV" unit s print | tr -d 's') 30 | 31 | ROOT_PART_LINE=$(echo "$PARTITION_TABLE" | grep -e "^${ROOT_PART_NUM}:") 32 | ROOT_PART_END=$(echo "$ROOT_PART_LINE" | cut -d ":" -f 3) 33 | echo Root part end: $ROOT_PART_END 34 | echo target end: $TARGET_END 35 | } 36 | 37 | get_variables 38 | 39 | if ! parted -m "$ROOT_DEV" u s resizepart "$ROOT_PART_NUM" "$TARGET_END"; then 40 | echo "Root partition resize failed" 41 | return 1 42 | fi 43 | 44 | resize2fs -p $ROOT_PART_DEV 45 | 46 | sed -i '\%/usr/bin/expand_fs.sh%d' /etc/rc.local -------------------------------------------------------------------------------- /deploy/github-runner/README.md: -------------------------------------------------------------------------------- 1 | # BlueOS-runner 2 | 3 | Runs an instance of a github self hosted runner for blueos-core repository 4 | -------------------------------------------------------------------------------- /deploy/github-runner/blueos-runner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This script should run on a raspberry 3 | # To check the necessary token permissions: 4 | # https://github.com/myoung34/docker-github-actions-runner#create-github-personal-access-token 5 | 6 | if [ -z "$1" ]; then 7 | echo "Please specify a GitHub token as argument." 8 | exit 1 9 | fi 10 | 11 | NAME=blueos-runner 12 | ORG=bluerobotics 13 | WORKDIR="/tmp/github-runner-${NAME}" 14 | sudo docker rm -f "${NAME}" 15 | sudo docker run -d --restart=always \ 16 | --privileged \ 17 | -e LABELS="blueos" \ 18 | -e ORG_NAME="${ORG}" \ 19 | -e ACCESS_TOKEN="$1" \ 20 | -e RUNNER_NAME="${NAME}" \ 21 | -e RUNNER_WORKDIR="${WORKDIR}" \ 22 | -e RUNNER_GROUP="Default" \ 23 | -e RUNNER_SCOPE="org" \ 24 | -v /var/run/docker.sock:/var/run/docker.sock \ 25 | -v ${WORKDIR}:${WORKDIR} \ 26 | --name ${NAME} myoung34/github-runner:ubuntu-bionic -------------------------------------------------------------------------------- /deploy/pimod/README.md: -------------------------------------------------------------------------------- 1 | # How to run: 2 | 3 | 1. Install pimod 4 | 1. Run pimod using the current file as source 5 | 6 | # FAQ 7 | 8 | ## Docker fails to run with a cgroup problem 9 | It may need to uncomment the cgroup_controllers configuration on `qemu.conf` for cgroup bind 10 | 11 | ## Docker fails to connect with docker.com API 12 | It may be necessary to set `resolv.conf` on qemu or bind it with host -------------------------------------------------------------------------------- /deploy/pimod/blueos.Pifile: -------------------------------------------------------------------------------- 1 | FROM https://downloads.raspberrypi.org/${BASE_IMAGE} 2 | 3 | PUMP 3000M 4 | 5 | # expand_fs 6 | INSTALL deploy/expand_fs.sh /usr/bin/expand_fs.sh 7 | RUN chmod +x /usr/bin/expand_fs.sh 8 | 9 | RUN touch /boot/ssh 10 | 11 | # rfkill can't run in chroot, run here instead 12 | RUN sed -i "\%^exit 0%irfkill unblock all" /etc/rc.local 13 | 14 | RUN bash -c ' 15 | mkdir -p /root/.config/blueos/bootstrap/ 16 | curl -o /root/.config/blueos/bootstrap/startup.json https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$VERSION/bootstrap/startup.json.default 17 | sed -i 's/factory/$VERSION/g' /root/.config/blueos/bootstrap/startup.json 18 | ' 19 | 20 | RUN bash -c ' 21 | curl -O https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$VERSION/install/install.sh 22 | chmod +x install.sh 23 | USER=pi ./install.sh --ci-run 24 | ' 25 | -------------------------------------------------------------------------------- /deploy/wpa_supplicant.conf: -------------------------------------------------------------------------------- 1 | ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev 2 | update_config=1 -------------------------------------------------------------------------------- /doc/blueboat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/doc/blueboat.png -------------------------------------------------------------------------------- /doc/bluerov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/doc/bluerov.png -------------------------------------------------------------------------------- /doc/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluerobotics/BlueOS/049a0d319d0b99af2c72d6a44c7a90d44239869d/doc/dashboard.png -------------------------------------------------------------------------------- /install/README.md: -------------------------------------------------------------------------------- 1 | # Installation directory 2 | 3 | This folder contains all necessary files for configuration of the host computer and installation of BlueOS. 4 | 5 | To use it, just run the installation script in your terminal **as root**, like so: 6 | 7 | ```bash 8 | sudo su -c 'curl -fsSL https://raw.githubusercontent.com/bluerobotics/BlueOS/master/install/install.sh | bash' 9 | ``` 10 | 11 | # Using different versions or custom builds 12 | To use a different remote or version, you can se the following environment variables: 13 | - `REMOTE`: Where the files are, E.g: https://raw.githubusercontent.com/patrickelectric/BlueOS-docker 14 | - `VERSION`: Branch (If using GitHub) or folder (If using HTTP server) to be used. 15 | 16 | Remember that to do that, you need to set the environment variables as root: 17 | ```sh 18 | sudo su 19 | # You can also change the install URL to use a different source for files 20 | curl -fsSL https://raw.githubusercontent.com/patrickelectric/BlueOS-Docker/example-version/install/install.sh | export REMOTE=https://raw.githubusercontent.com/patrickelectric/BlueOS-docker export VERSION=example-version bash 21 | ``` -------------------------------------------------------------------------------- /install/boards/bcm_28xx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Configuring BCM28XX board (Raspberry Pi zero, 1, 2, 3).." 4 | 5 | CMDLINE_FILE=/boot/cmdline.txt 6 | 7 | # Remove any configuration related to i2c and spi/spi1 and do the necessary changes for navigator 8 | echo "- Enable I2C, SPI and UART." 9 | for STRING in "dtparam=i2c_arm=" "dtparam=spi=" "dtoverlay=spi1" "dtoverlay=uart1"; do 10 | sudo sed -i "/$STRING/d" /boot/config.txt 11 | done 12 | for STRING in "dtparam=i2c_arm=on" "dtparam=spi=on" "dtoverlay=spi1-3cs" "dtoverlay=uart1"; do 13 | echo "$STRING" | sudo tee -a /boot/config.txt 14 | done 15 | 16 | # Check for valid modules file to load kernel modules 17 | if [ -f "/etc/modules" ]; then 18 | MODULES_FILE="/etc/modules" 19 | else 20 | MODULES_FILE="/etc/modules-load.d/blueos.conf" 21 | touch "$MODULES_FILE" || true # Create if it does not exist 22 | fi 23 | 24 | echo "- Set up kernel modules." 25 | # Remove any configuration or commented part related to the i2c drive 26 | for STRING in "bcm2835-v4l2" "i2c-bcm2835" "i2c-dev"; do 27 | sudo sed -i "/$STRING/d" "$MODULES_FILE" 28 | echo "$STRING" | sudo tee -a "$MODULES_FILE" 29 | done 30 | 31 | # Remove any console serial configuration 32 | echo "- Configure serial." 33 | sudo sed -e 's/console=serial[0-9],[0-9]*\ //' -i $CMDLINE_FILE 34 | 35 | # Set cgroup, necessary for docker access to memory information 36 | echo "- Enable cgroup with memory and cpu" 37 | grep -q cgroup $CMDLINE_FILE || ( 38 | # Append cgroups on the first line 39 | sed -i '1 s/$/ cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory/' $CMDLINE_FILE 40 | ) -------------------------------------------------------------------------------- /install/boards/config.toml: -------------------------------------------------------------------------------- 1 | # Raspberry PI OS config.toml 2 | # This file is used for the initial setup of the system on the first boot, if 3 | # it's s present in the boot partition of the installation. 4 | # 5 | # This file is loaded by firstboot, parsed by init_config and ends up 6 | # as several calls to imager_custom. 7 | # The example below has all current fields. 8 | # 9 | # References: 10 | # - https://github.com/RPi-Distro/raspberrypi-sys-mods/blob/master/usr/lib/raspberrypi-sys-mods/firstboot 11 | # - https://github.com/RPi-Distro/raspberrypi-sys-mods/blob/master/usr/lib/raspberrypi-sys-mods/init_config 12 | # - https://github.com/RPi-Distro/raspberrypi-sys-mods/blob/master/usr/lib/raspberrypi-sys-mods/imager_custom 13 | 14 | # Required: 15 | config_version = 1 16 | 17 | [system] 18 | hostname = "blueos" 19 | 20 | [user] 21 | # If present, the default "rpi" user gets renamed to this "name" 22 | name = "pi" 23 | # The password can be encrypted or plain. To encrypt, we can use "openssl passwd -5 raspberry" 24 | password = "$5$jN49NV5TpvPOd.dA$cNLchFFnGqbYgyyHpIs5jZwCgAFbTb6QhaxiN8UdjO/" 25 | password_encrypted = true 26 | 27 | [ssh] 28 | # ssh_import_id = "gh:user" # import public keys from github 29 | enabled = true 30 | password_authentication = true 31 | # We can also seed the ssh public keys configured for the default user: 32 | # authorized_keys = [ "ssh-rsa ... user@host", ... ] 33 | -------------------------------------------------------------------------------- /install/configs/blueos.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Start BlueOS on boot 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=/usr/bin/docker start blueos-bootstrap 7 | 8 | [Install] 9 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /install/configs/journald.conf: -------------------------------------------------------------------------------- 1 | # BlueOS journal systemd file. 2 | # 3 | # Entries in this file show the compile time defaults. Local configuration 4 | # should be created by either modifying this file (or a copy of it placed in 5 | # /etc/ if the original file is shipped in /usr/), or by creating "drop-ins" in 6 | # the /etc/systemd/journald.conf.d/ directory. The latter is generally 7 | # recommended. Defaults can be restored by simply deleting the main 8 | # configuration file and all drop-ins located in /etc/. 9 | # 10 | # Use 'systemd-analyze cat-config systemd/journald.conf' to display the full config. 11 | # 12 | # See journald.conf(5) for details. 13 | 14 | [Journal] 15 | SystemMaxUse=200M 16 | -------------------------------------------------------------------------------- /install/network/avahi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION="${VERSION:-master}" 4 | GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-bluerobotics/BlueOS} 5 | REMOTE="${REMOTE:-https://raw.githubusercontent.com/${GITHUB_REPOSITORY}}" 6 | REMOTE="$REMOTE/$VERSION" 7 | CONFIGURE_NETWORK_PATH="$REMOTE/install/network" 8 | alias curl="curl --retry 6 --max-time 15 --retry-all-errors --retry-delay 20 --connect-timeout 60" 9 | 10 | # Exit if something goes wrong 11 | set -e 12 | 13 | systemctl is-active --quiet avahi-daemon || ( 14 | echo "Avahi daemon is not installed or running." 15 | exit 1 16 | ) 17 | 18 | echo "Configuring blueos avahi service" 19 | AVAHI_SERVICE_PATH="/etc/avahi/services" 20 | [ ! -d "${AVAHI_SERVICE_PATH}" ] && ( 21 | echo "Avahi service directory does not exist: ${AVAHI_SERVICE_PATH}" 22 | exit 1 23 | ) 24 | curl -fsSL $CONFIGURE_NETWORK_PATH/blueos.service > "${AVAHI_SERVICE_PATH}/blueos.service" 25 | 26 | AVAHI_DAEMON_CONFIG_PATH="/etc/avahi/avahi-daemon.conf" 27 | AVAHI_HOST_NAME="blueos-avahi" 28 | [ ! -d "${AVAHI_DAEMON_CONFIG_PATH}" ] && ( 29 | echo "Avahi daemon config file found, changing avahi host name to: ${AVAHI_HOST_NAME}" 30 | sed -i "s/#host-name=.*/host-name=${AVAHI_HOST_NAME}/g" ${AVAHI_DAEMON_CONFIG_PATH} 31 | ) || echo "Avahi daemon config file not found in ${AVAHI_DAEMON_CONFIG_PATH}" 32 | 33 | echo "Configure hostname to blueos" 34 | OLD_HOSTNAME="$(cat /etc/hostname)" 35 | NEW_HOSTNAME="blueos" 36 | # Overwrite with new name 37 | echo $NEW_HOSTNAME > /etc/hostname 38 | # Replace current name 39 | sed -i "s/127.0.1.1.*$OLD_HOSTNAME/127.0.1.1\t$NEW_HOSTNAME/g" /etc/hosts 40 | -------------------------------------------------------------------------------- /install/network/blueos.service: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %h 6 | 7 | _http._tcp 8 | 80 9 | 10 | 11 | _mavlink._udp 12 | 14550 13 | name=ArduSub 14 | 15 | -------------------------------------------------------------------------------- /install/overlays/spi0-led.dts: -------------------------------------------------------------------------------- 1 | // This is a custom device tree overlay for the spi0 peripheral on the 2 | // Raspberry Pi 4. It will configure only the spi0 mosi pin 3 | // (The other spi0 pins will not be driven by the spi0 peripheral, 4 | // and can be used for other functions). This is to be used with 5 | // the Blue Robotics Navigator autopilot hat, where the RGB 6 | // 'neopixel' led data pin is connected to the spi0 mosi pin on the 7 | // Raspberry Pi 4. 8 | 9 | /dts-v1/; 10 | /plugin/; 11 | 12 | 13 | / { 14 | compatible = "brcm,bcm2835"; 15 | 16 | fragment@0 { 17 | target = <&spi0_cs_pins>; 18 | frag0: __overlay__ { 19 | brcm,pins = <>; 20 | }; 21 | }; 22 | 23 | fragment@1 { 24 | target = <&spi0>; 25 | frag1: __overlay__ { 26 | cs-gpios = <>; 27 | status = "okay"; 28 | }; 29 | }; 30 | 31 | fragment@2 { 32 | target = <&spidev1>; 33 | __overlay__ { 34 | status = "disabled"; 35 | }; 36 | }; 37 | 38 | fragment@3 { 39 | target = <&spi0_pins>; 40 | __overlay__ { 41 | brcm,pins = <10>; 42 | }; 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /install/udev/100.autopilot.rules: -------------------------------------------------------------------------------- 1 | # CP210X USB UART 2 | ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" 3 | 4 | # FT231XS USB UART 5 | ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" 6 | 7 | # Prolific Technology, Inc. PL2303 Serial Port 8 | ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" 9 | 10 | # QinHeng Electronics HL-340 USB-Serial adapter 11 | ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" 12 | --------------------------------------------------------------------------------