Add iOS support (iPhone and iPad: native build, phone/tablet layout, touch RC, BLE, Wi-Fi MAVLink) - #16
Conversation
|
Hey man. You are really on fire now :D But please be aware. I will not add iOS, iPad OS or Android support for the 1.0 release. So please take your time. Additionally we need to have a chat anyway since although its cool to have so many platforms supported, I personally can only build and test on Win, Linux and Android but I don't have apple devices. So if we extend the support to Apple Devices I will rely on your help in the future for testing and development. |
|
@b14ckyy For me personally ipad version is also quite interesting as its super easy to take to the field and works pretty ok now with wifi and backpack. I do not have crossfire with ble to test but I could make a testflight for this so people who have other hardware can try. This is a draft and can be merged in whatever release you want ,I will be updating the branch with master every once in a while and fixing bugs. Of course I am happy to help you maintain this. |
813c043 to
cd017c8
Compare
cd017c8 to
171c32e
Compare
Gate the desktop-only pieces behind cfg(target_os = "ios") so the backend compiles for iOS while every desktop target (Windows/Linux/macOS) builds byte -for-byte as before: - Cargo.toml: move serialport, btleplug and starship-battery to a cfg(not(target_os = "ios")) dependency table (no iOS backend for any). - transport: compile out the serial and BLE modules on iOS; keep TCP/UDP. - connection: gate the serial/BLE commands and connect() match arms; iOS rejects those transports and connects over Wi-Fi (TCP/UDP MAVLink) only. - telemetry_forward + radar: gate the serial/BLE relay sinks and the serial ADS-B / FormationFlight sources; hoist the shared NodePos alias into radar::source so it stays available on iOS. - system_on_battery: iOS stub returns true (no starship-battery backend). - lib.rs: gate the serial/BLE command registrations and the desktop window-state plugin. Both aarch64-apple-ios and the native desktop lib build with zero warnings.
- flightlog/logging/terrain: resolve the DB, logs and terrain cache to Library/Application Support/kite-gc on iOS (shares the macOS branch; HOME is the app's sandbox container root there). Desktop paths unchanged. - Info.ios.plist: add NSLocalNetworkUsageDescription. iOS 14+ blocks every local-network connection until the user grants Local Network access, and only prompts when this key is present - required for Wi-Fi (TCP/UDP) MAVLink.
Phase 3 of the iPad port. All gated behind isMobile (from $lib/platform), so the Windows/Linux/macOS UI is unchanged. - platform.ts: add isIOS / isMobile. iPadOS (and the iOS Simulator) report a desktop "Macintosh" user-agent, so disambiguate with maxTouchPoints; also exclude iPadOS from isMacOS so it no longer gets the mac traffic-light controls. - Toolbar: hide the Serial and BLE transport options on mobile (Wi-Fi only). - +page.svelte: default the connection to UDP MAVLink (port 14550) on mobile, and ignore a serial/BLE transport carried over from synced desktop settings; hide the Video and RC-control nav tabs on mobile. - widgetRegistry / WidgetPanel: drop the video widget from the mobile catalog and skip rendering it from any stale saved layout. - LogbookPanel: hide the blackbox import button on mobile (it runs the native blackbox_decode binary, which can't execute on iOS). Verified on the iPad simulator: launches, defaults to UDP with Serial/BLE gone, Video and RC tabs hidden. svelte-check passes with 0 errors.
Adds a Mode-2 virtual RC controller for iPad, since the mobile build has no physical joystick. It reuses the existing RC-injection pipeline end to end: - mobileRc store: writes the fixed AETR channel frame (CH1 roll, CH2 pitch, CH3 throttle, CH4 yaw, CH5 arm) into channelValues and the PX4 MANUAL_CONTROL setpoint into manualOutput, then engages rcEngaged so the shared rcStream pump handles the wire format (INAV MSP / ArduPilot override / PX4 manual), send rate and the 500 ms deadman. Nothing here persists to the desktop RC profiles. - VirtualSticks component: two large touch pads as a bottom overlay. Left = throttle (holds) + yaw (springs to centre); right = pitch + roll (spring). Inert until Engage; arming is a separate explicit toggle; releasing a spring axis re-centres it; tearing down the panel disengages. - +page.svelte: on mobile, offer the RC tab whenever a control-capable FC is connected and render VirtualSticks instead of the desktop RcControlPanel. Desktop is untouched (all mobile-only paths behind isMobile). Verified in the iPad simulator; live control still needs a field test. svelte-check: 0 errors.
btleplug has no iOS backend, so BLE on iOS is implemented in Rust directly against CoreBluetooth via the objc2 bindings, kept in-crate (no Swift, no Tauri plugin) so it survives cargo tauri ios init: - transport/ble_ios.rs: a Rust CBCentralManager/CBPeripheral delegate feeding a byte ring, exposing the same surface as the desktop btleplug module (scan_ble_devices / run_scan_session / connect_ble / connect_ble_listen and a BleTransport: ByteTransport). Matches the same serial GATT profiles. - connection.rs: BLE commands + the connect() BLE arm now route through a per-platform ble_backend alias (btleplug on desktop, CoreBluetooth on iOS). Serial stays desktop-only. - lib.rs: register the BLE scan commands on iOS too (list_serial_ports stays desktop-only). - Toolbar / +page.svelte: re-enable the BLE transport option on mobile. - Info.ios.plist: NSBluetoothAlwaysUsageDescription for the BLE permission. Compiles cleanly for aarch64-apple-ios and links (CoreBluetooth), desktop unchanged (0 warnings). Runtime is not yet validated against a real BLE flight controller - the delegate handshake needs an on-device pass.
Pin bundle.iOS.minimumSystemVersion so it survives gen/apple regen, and add scripts/run-ipad.sh (dev live-reload on device) and scripts/build-ios.sh (release ipa) that run the full web + Rust + Xcode flow in one command.
…ap UI On mobile the iOS status bar overlapped the toolbar and the bottom edge crowded the map zoom/compass buttons, the map attribution label, the HUD readouts and the RC sticks. Tag the document with is-mobile and expose --safe-top / --safe-bottom (env safe-area insets, viewport-fit=cover), then: grow the toolbar row and offset the top-anchored map, video and toast layers by --safe-top; lift the map control cluster and move the Leaflet attribution off it; and raise the HUD dock above the on-screen sticks on the rc-control tab.
171c32e to
a8f4d41
Compare
The WebView Geolocation API labels its permission prompt with the page origin, which for a Tauri app reads "localhost". Route the ground-station position through tauri-plugin-geolocation on mobile (iOS/Android) so the OS prompt shows the app name and the Info.plist usage string instead. Desktop keeps navigator.geolocation. The plugin is a mobile-only dependency and is registered under cfg(mobile); the vehicle GPS remains the fallback source.
Adapt the interface for iPhone and iPad separately. Phone: a collapsible toolbar with a connection/protocol/arming status chip, a two-row HUD with larger tiles, and panels that overlay the dock so their content stays reachable. Tablet: track the taller mobile toolbar height so the nav rail and panels clear it, and enlarge the small HUD and side-dock tiles to use the extra space. Both: hand map touch gestures to Leaflet (single-finger pan plus pinch) and recenter once on the first location fix. Layout tiers are gated on OS-agnostic flags (isPhone, isTablet, isMobile, with matching html classes) so a future Android build reuses them, and so phone-only rules never affect the tablet and vice versa.
# Conflicts: # src/lib/platform.ts
# Conflicts: # src/lib/platform.ts
|
I see you are closely tracking. Then we can point your iPad PR to a new development branch to get it on track while I work on the android Tablet side. |
|
Hey mate. But there are a few things I would like to discuss with your regarding available features on mobile OS devices (iOS and Andorid). Maybe we can chat somewhere 1:1 for easier communication? A few bullet points:
If we discuss that and you can make the necessary changes on what we agree on, I will do the full review and we can merge that as a base so I can work on the Android side. Alternatively if you can separate the PR into 2 and split the front end from the iOS specific code, we could merge the front end and we build on that while each of us sticks with the back end part for each OS. |
The toolbar is the title bar, and nothing in it gave way. Narrowing the window — worst with a UAV connected, where the sensor bar adds ~200 px — pushed the minimise/maximise/close buttons out of the frame, so the window could no longer be closed. The structural guarantee is that only the left and centre groups yield: the right-hand group keeps its size, and the window controls no longer shrink on either placement (the macOS traffic lights could be squeezed under the logo). On top of that the bar now sheds content in a fixed order as it runs out of room: version, then wordmark to icon, then the sensor bar, then the connection controls onto a strip below the bar. Fit is measured against the summed natural group widths rather than fixed breakpoints, because the requirement swings widely with the sensor count, the selected transport and the locale. Restoring a level uses the width that level actually measured, so dragging across a boundary cannot flicker. The second row floats out of flow deliberately: the app grid pins the toolbar row to 53 px and the map, panel and video overlays offset against that same number, so a taller header would drag four unrelated surfaces with it. Also drops the empty sensor bar that rendered as a small bordered box while disconnected. Co-Authored-By: Claude Opus 4.8
macOS was missing from the platform badge and the supported-setups list, even though the release workflow has been building a universal bundle for a while. Alongside that: - status badge said "public beta"; the tree is at 1.0.0-rc1 - Linux is x86-64 AND ARM64, both built by the release workflow - Android read "on hold"; work on it has started for a post-1.0 release - live video also covers local capture devices, not just RTSP - the build section omitted `just build-macos`, which the justfile has - CI runs clippy and the Rust tests too, on all three platforms - the feature-freeze note predates the branching model: new features are welcome against `development` now, they just don't reach `master` before 1.0 Documentation site links verified live. Co-Authored-By: Claude Opus 4.8
The mobile groundwork covers both — form-factor detection, safe-area handling and a BLE transport for iOS all exist — so listing only Android understated it. Co-Authored-By: Claude Opus 4.8
fix(toolbar): keep the window buttons reachable in a narrow window
…d state Two defects behind issue b14ckyy#26, both specific to replaying a passively recorded flight. **3D track buried underground.** The replay places its track at `startMslGps + geoid + relative altitude`, and took the anchor from the first fix's `alt_m`. That is only a true MSL for protocols that report one. CRSF and LTM report altitude relative to the arming point — the backend already says so (`msl = !matches!(p, Protocol::Ltm | Protocol::Crsf)`) — so their `alt_m` starts near zero and the whole track was placed at sea level, i.e. buried by the local terrain elevation. The reporter's flight sits at ~550 m, which is exactly how far under the ground it rendered. For those protocols the anchor now comes from terrain at the start point, the same reference the live path captures at the arm edge. A missing terrain sample falls back to the previous behaviour rather than inventing a height. The geoid fallback ground was reading the same raw `alt_m`, which would have skewed the offset by the whole local elevation on exactly these flights whenever the Copernicus lookup came back empty. It now takes the resolved anchor. **Aircraft always disarmed in replay.** The adapter read `armingFlags` from `state_flags`, which is INAV's stateFlags bitfield and only blackbox imports populate it — the live recorder has no source and stores NULL, so every replayed live flight looked disarmed regardless of protocol. Recovered from the recording itself: the recorder opens a flight on the arm edge and finalises it on disarm, so a sample carrying a flight mode was taken while armed. Both work on flights already in the database — the reporter's log included. Which protocols are relative is decided in one place (`helpers/altReference.ts`) by matching the stored protocol label; that makes the label part of a data contract, noted for replacement by a real column at the next schema change. Fixes b14ckyy#26 Co-Authored-By: Claude Opus 4.8
fix(replay): anchor relative-altitude tracks to terrain, restore armed state
Ubuntu 26.04 starts the AppImage into an empty window and aborts at EGL display creation. The installation page already warned about distributions being too old; this adds the other end of the range, points at the .deb (which works on the same system) and says plainly that we found no packaging fix — bundling less makes it worse, bundling the graphics driver is not allowed, and a newer build base would lock out everything below it. Invites correction in issue b14ckyy#29 for anyone who knows the format better. Co-Authored-By: Claude Opus 4.8
# Conflicts: # src/lib/components/Toolbar.svelte
SerialConnection::open() did a bare serialport open — the Rust serialport crate leaves DTR low. USB-CDC devices gate their device→host stream on DTR (tud_cdc_n_connected()), so against a virtual COM port (e.g. a Kite-Link USB-OTG node) the link looked dead in exactly one direction: host→device requests arrived, every response was dropped at the device. INAV Configurator, Mission Planner and any terminal work because they assert DTR on open. The raise now lives inside open() itself, so the FC connect, passive listen and FormationFlight paths all get it; the ADS-B source's existing explicit call becomes a harmless no-op. Best-effort: ports without control lines (BT-SPP) log a warning and continue. Co-Authored-By: Claude Opus 4.8
fix(serial): raise DTR/RTS on open — USB-CDC devices gate their TX on it Tested and fix confirmed with Kite-Link Virtual Com.
iOS support (iPhone and iPad)
Builds on the merged macOS work (#8) to run Kite Ground Control natively on iPhone and iPad. Desktop, Windows and Linux behavior is unchanged. Everything mobile is gated behind runtime platform checks (
isMobile, plusisPhoneandisTabletfor the two form factors) so nothing here affects the desktop builds, and phone-only layout never touches the tablet or the other way around.Now merged up to the latest
master(video and native-capture rework), builds clean foraarch64-apple-ios.What this adds
aarch64-apple-iosso the app runs as a real iOS binary, not a webview shim.tauri-plugin-geolocationon mobile, so the permission prompt shows the app name and usage string rather than the WebView origin ("localhost"). Desktop keepsnavigator.geolocation. The plugin is a mobile only dependency, registered undercfg(mobile). The vehicle GPS stays the fallback source, which matters on Wi-Fi only tablets with no GPS chip.scripts/run-ipad.shandscripts/build-ios.shfor the dev and release flows.env(safe-area-inset-*)so the iOS status bar and notch do not cover the toolbar and the home indicator and map controls do not cover the HUD or the Leaflet attribution.Phone and tablet layout
The interface adapts to each form factor at runtime:
Tested on hardware
Ran on a physical iPad and iPhone against a live aircraft. MAVLink telemetry over an ELRS TX backpack Wi-Fi link (INAV 9, ELRS link in MAVLink mode, UDP 14550) came through with full attitude, GPS, battery and flight mode, plus live RC channels. Connection stayed stable across the session. MSP is also available on iOS over Wi-Fi (TCP/UDP) and BLE; only the USB serial transport is unavailable there, since iOS has no raw serial access.
Notes
gen/applestays gitignored and regenerable, same as upstream Tauri mobile convention.APPLE_DEVELOPMENT_TEAMenv var, nothing team specific is committed.Future work
Android would slot into the same layout tiers: the
isMobile,isPhoneandisTabletflags and their html classes are named by form factor rather than by OS for exactly that reason. Out of scope for this PR, noting it as a follow up.