├── .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 |
2 |
6 | {{ message }}
7 |
8 |
9 |
15 | Close
16 |
17 |
18 |
19 |
20 |
21 |
62 |
--------------------------------------------------------------------------------
/core/frontend/src/components/app/BackendStatusChecker.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ status_text }}
3 |
4 |
5 |
41 |
--------------------------------------------------------------------------------
/core/frontend/src/components/app/PirateModeMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
15 | Use Pirate Mode to show hidden pages and advanced settings.
16 | Pirate powers should be used with care.
17 |
18 |
21 | {{ settings.is_pirate_mode ? "Disable Pirate Mode" : "Enable Pirate Mode" }}
22 |
23 |
24 |
25 |
26 |
51 |
--------------------------------------------------------------------------------
/core/frontend/src/components/app/PirateModeTrayMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
23 |
24 |
27 |
28 |
29 |
30 |
53 |
54 |
56 |
--------------------------------------------------------------------------------
/core/frontend/src/components/app/ThemeTrayMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | {{ settings.is_dark_theme ? 'mdi-weather-night' : 'mdi-white-balance-sunny' }}
9 |
10 |
11 |
12 |
29 |
--------------------------------------------------------------------------------
/core/frontend/src/components/app/VehicleRebootMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 | Autopilot reboot is necessary for new settings to take effect.
14 |
15 |
18 | Reboot Autopilot
19 |
20 |
21 |
22 |
23 |
37 |
--------------------------------------------------------------------------------
/core/frontend/src/components/app/VehicleRebootRequiredTrayMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
12 |
26 |
27 |
30 |
31 |
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 |
2 |
9 | {{ content }}
10 |
11 |
12 |
13 |
26 |
--------------------------------------------------------------------------------
/core/frontend/src/components/common/NotSafeOverlay.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | This feature is disabled because the vehicle is armed.
10 |
11 |
16 | I know what I'm doing, let me through
17 |
18 |
19 |
20 |
21 |
22 |
44 |
--------------------------------------------------------------------------------
/core/frontend/src/components/common/ParameterSwitch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
53 |
--------------------------------------------------------------------------------
/core/frontend/src/components/common/PasswordInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
53 |
54 |
56 |
--------------------------------------------------------------------------------
/core/frontend/src/components/common/SpinningLogo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 | {{ subtitle }}
10 |
11 |
12 |
13 |
31 |
32 |
49 |
--------------------------------------------------------------------------------
/core/frontend/src/components/common/StatusTextWatcher.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
46 |
--------------------------------------------------------------------------------
/core/frontend/src/components/common/rebootRequiredOverlay.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | An autopilot Reboot is required.
10 |
11 |
16 | Reboot Autopilot
17 |
18 |
19 |
20 |
21 |
22 |
40 |
--------------------------------------------------------------------------------
/core/frontend/src/components/ethernet/EthernetManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
14 |
18 |
19 |
20 |
21 | No ethernet interfaces available
22 |
23 |
24 |
25 |
26 |
27 |
55 |
--------------------------------------------------------------------------------
/core/frontend/src/components/ethernet/EthernetTrayMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
24 |
25 |
26 |
27 |
28 |
29 |
50 |
51 |
53 |
--------------------------------------------------------------------------------
/core/frontend/src/components/ethernet/EthernetUpdater.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
18 |
--------------------------------------------------------------------------------
/core/frontend/src/components/nmea-injector/NMEASocketCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
12 |
13 | {{ nmeaSocket.kind }}:{{ nmeaSocket.port }}
14 |
15 | mdi-arrow-right-circle
16 |
17 | Mavlink Component #{{ nmeaSocket.component_id }}
18 |
19 |
20 |
21 |
22 |
23 |
32 | mdi-minus
33 |
34 |
35 |
36 |
37 |
38 |
39 |
60 |
61 |
67 |
--------------------------------------------------------------------------------
/core/frontend/src/components/notifications/ConfigMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
13 | Show old messages
14 |
15 |
16 |
17 |
18 |
19 |
36 |
37 |
39 |
--------------------------------------------------------------------------------
/core/frontend/src/components/notifications/TrayButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
29 |
30 |
33 |
34 |
35 |
36 |
60 |
61 |
63 |
--------------------------------------------------------------------------------
/core/frontend/src/components/parameter-editor/ParameterLabel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ label }}
4 |
5 |
6 |
12 | mdi-information
13 |
14 |
15 |
16 | {{ param?.name }}
17 | Description: {{ param.description }}
18 | Range: {{ param.range.low }} to {{ param.range.high }}
19 | Units: {{ param.units }}
20 | Options: {{ formatOptions }}
21 | Requires reboot
22 |
23 |
24 |
25 |
26 |
27 |
50 |
--------------------------------------------------------------------------------
/core/frontend/src/components/system-information/Network.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
50 |
--------------------------------------------------------------------------------
/core/frontend/src/components/system-information/SystemConditionCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 | {{ icon }}
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 | {{ title }}
25 |
26 |
27 |
31 |
32 |
36 | mdi-clock
37 |
38 | {{ time }}
39 |
40 |
41 |
42 |
43 |
70 |
71 |
77 |
--------------------------------------------------------------------------------
/core/frontend/src/components/utils/ParameterLoadingSpinner.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
46 |
--------------------------------------------------------------------------------
/core/frontend/src/components/utils/RebootButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 | Reboot Autopilot
8 |
9 |
10 |
11 |
31 |
--------------------------------------------------------------------------------
/core/frontend/src/components/utils/themedSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
2 |
3 |
4 | Lights
5 |
6 |
7 |
12 |
13 | mdi-dots-horizontal
14 |
15 | {{ toBoardFriendlyChannel(light) }}
16 |
17 |
18 |
19 |
20 |
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 |
2 |
8 |
9 |
10 |
43 |
--------------------------------------------------------------------------------
/core/frontend/src/components/video-manager/VideoUpdater.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/core/frontend/src/views/BagEditorView.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
36 |
--------------------------------------------------------------------------------
/core/frontend/src/views/BridgesView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/core/frontend/src/views/EndpointView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
29 |
--------------------------------------------------------------------------------
/core/frontend/src/views/FileBrowserView.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
29 |
--------------------------------------------------------------------------------
/core/frontend/src/views/LogView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/core/frontend/src/views/MavlinkInspectorView.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
24 |
--------------------------------------------------------------------------------
/core/frontend/src/views/NMEAInjectorView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/core/frontend/src/views/NetworkTestView.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
17 | {{ page.title }}
18 | {{ page.icon }}
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
64 |
--------------------------------------------------------------------------------
/core/frontend/src/views/PageNotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
12 |
13 | Page not found!
14 |
15 |
16 |
17 |
18 |
25 |
--------------------------------------------------------------------------------
/core/frontend/src/views/ParameterEditorView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/core/frontend/src/views/Pings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
21 |
25 |
26 | No Ping devices available.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
62 |
63 |
68 |
--------------------------------------------------------------------------------
/core/frontend/src/views/TerminalView.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
24 |
--------------------------------------------------------------------------------
/core/frontend/src/views/VersionChooser.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
22 |
--------------------------------------------------------------------------------
/core/frontend/src/views/VideoManagerView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/core/frontend/src/views/ZenohInspectorView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/core/frontend/src/widgets/Cpu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CPU: {{ cpu_usage }} %
5 |
6 |
7 |
8 |
9 |
38 |
--------------------------------------------------------------------------------
/core/frontend/src/widgets/Disk.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Disk: {{ disk_usage }}%
5 |
6 |
7 |
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 |
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 |
--------------------------------------------------------------------------------