# MeshCore Open - Flutter Client Open-source Flutter client for MeshCore LoRa mesh networking devices. Connects to MeshCore-compatible radios over **BLE, TCP, or USB serial** and provides direct/channel chat, contact and channel management, on-map node tracking, repeater administration, and on-device message translation. ## Build Commands ```bash # Install dependencies ~/flutter/bin/flutter pub get # Run in debug mode ~/flutter/bin/flutter run # Build Android APK ~/flutter/bin/flutter build apk # Build iOS ~/flutter/bin/flutter build ios # Build versioned web release (uses build_pipe) ~/flutter/bin/dart run build_pipe # Run static analysis ~/flutter/bin/flutter analyze # Run tests ~/flutter/bin/flutter test ``` ## Project Structure ``` lib/ ├── main.dart # Entry point: MultiProvider wiring, locale + theme, initial route ├── connector/ # Unified BLE/TCP/USB transport layer │ ├── meshcore_connector.dart # Central state holder + ChangeNotifier (all transports) │ ├── meshcore_connector_tcp.dart # TCP transport helper │ ├── meshcore_connector_usb.dart # USB serial transport helper │ ├── meshcore_protocol.dart # Frame size + version constants │ └── meshcore_uuids.dart # Nordic UART UUIDs + scan name prefixes ├── models/ # Plain data classes (Contact, Channel, Message, Community, …) ├── services/ # ChangeNotifier services + IO services (retry, translation, ML, …) ├── storage/ # SharedPreferences-backed stores, scoped per device key ├── helpers/ # Pure utilities (Smaz compression, GIF parsing, scroll helpers) ├── utils/ # Platform / IO / UX utilities (logger, GPX export, dialogs) ├── theme/ # MeshPalette (defined, not yet wired in main.dart) ├── l10n/ # ARB localization for 18 locales ├── icons/ # Custom icon widgets ├── widgets/ # Reusable widgets (AppBar, BatteryUi, QR, jump-to-bottom, …) └── screens/ # ~26 screens — see Screens section below ``` ## Screens All screens are fully implemented (no remaining placeholders). ### Connection / Scanning | Screen | Purpose | |---|---| | `scanner_screen.dart` | BLE device scan and connect — main entry point | | `tcp_screen.dart` | Connect to a MeshCore device over TCP/IP | | `usb_screen.dart` | Connect to a MeshCore device over USB serial | | `discovery_screen.dart` | Browse all discovered (non-contact) mesh nodes | | `chrome_required_screen.dart` | Web gate for non-Chrome browsers (BLE unavailable) | ### Chat / Messaging | Screen | Purpose | |---|---| | `chat_screen.dart` | Direct (private) messaging with a contact | | `channel_chat_screen.dart` | Group messaging inside a named channel | | `channels_screen.dart` | List and manage channels (add/edit/delete) | | `channel_message_path_screen.dart` | Hop-by-hop route a channel message took, with map overlay | ### Contacts / Neighbors | Screen | Purpose | |---|---| | `contacts_screen.dart` | Full contacts list with previews and management | | `neighbors_screen.dart` | Nodes directly heard by the connected radio (one-hop) | ### Repeater Management | Screen | Purpose | |---|---| | `repeater_hub_screen.dart` | Top-level repeater hub; navigates to sub-screens | | `repeater_status_screen.dart` | Live status of a managed repeater node | | `repeater_cli_screen.dart` | Raw command-line interface to a repeater | | `repeater_settings_screen.dart` | Full radio/node settings editor for a repeater | ### Map / Location | Screen | Purpose | |---|---| | `map_screen.dart` | Main map view of contacts/nodes with live GPS positions | | `line_of_sight_map_screen.dart` | Terrain LOS analysis between configurable endpoints | | `path_trace_map.dart` | Animates the hop path a direct message traveled | | `map_cache_screen.dart` | Download/clear offline map tile cache | | `community_qr_scanner_screen.dart` | Scan QR to join a mesh community/channel | ### Settings / Debug / Diagnostics | Screen | Purpose | |---|---| | `settings_screen.dart` | Connected device settings: radio params, identity, GPS | | `app_settings_screen.dart` | App preferences: theme, units, map source, notifications | | `app_debug_log_screen.dart` | In-app log viewer (app-layer messages) | | `ble_debug_log_screen.dart` | In-app log viewer (raw BLE frame traffic) | | `companion_radio_stats_screen.dart` | RF stats (RSSI, SNR, packet counts) for paired radio | | `telemetry_screen.dart` | Battery / sensor / environmental telemetry for a contact | ## Architecture ### State Management `Provider` with `ChangeNotifier`. `main.dart` wires a `MultiProvider` with the following: | Provider | Role | |---|---| | `MeshCoreConnector` | Active transport (BLE/TCP/USB), connection state, frame I/O | | `MessageRetryService` | ACK tracking and retry scheduling with backoff | | `PathHistoryService` | Per-contact routing history (LRU cache, 50 contacts) | | `AppSettingsService` | App preferences (theme, units, locale, notifications) | | `BleDebugLogService` | Raw BLE frame log buffer | | `AppDebugLogService` | Structured app log buffer | | `ChatTextScaleService` | Pinch-to-zoom text scale for chat screens | | `TranslationService` | On-device LLM translation (llamadart) | | `UiViewStateService` | Contacts/channels sort/filter/search state | | `TimeoutPredictionService` | ML linear regression for ACK timeout prediction | | `StorageService` | Path history + delivery observation persistence | | `MapTileCacheService` | OSM tile pre-cache | Screens consume these via `Consumer` (or `context.watch()` / `context.read()`) for reactive UI. ### Storage / Persistence All stores in `lib/storage/` use `PrefsManager` (a `SharedPreferences` singleton initialized in `main()`). Most stores **scope keys by the first 10 hex chars of the connected device's public key**, so per-radio data is isolated. | Store | Persists | |---|---| | `message_store`, `channel_message_store` | Direct + channel messages | | `contact_store`, `contact_discovery_store` | Known + discovered contacts | | `channel_store`, `channel_order_store`, `channel_settings_store` | Channels, display order, per-channel Smaz toggle | | `community_store` | Communities (32-byte shared secrets) | | `contact_group_store`, `contact_settings_store` | Groups, per-contact Smaz toggle | | `unread_store` | Per-contact unread counts (debounced writes) | GGUF translation models are stored as files (not SharedPreferences) via `translation_file_store`. ### Theming - Material 3 design (`useMaterial3: true`) - System-based dark/light mode (`ThemeMode.system`) - Blue color scheme seed - `lib/theme/mesh_theme.dart` defines a warm-dark `MeshPalette` (phosphor-green accents) but is **not currently wired** in `main.dart` — available for a future redesign ### Localization 18 locales supported via Flutter's standard ARB pipeline (`lib/l10n/`): en, de, es, fr, it, pt, ru, uk, bg, hu, ja, ko, nl, pl, sk, sl, sv, zh. Language override comes from `AppSettingsService.settings.languageOverride`. Use the `context.l10n` extension (`lib/l10n/l10n.dart`) for translated strings; contact-type names live in `contact_localization.dart`. ## Transports `MeshCoreConnector` unifies all three transports under one `ChangeNotifier`. There is **no shared base class** — selection is via the `MeshCoreTransportType { bluetooth, usb, tcp }` enum, and BLE/TCP/USB share the same connection-state enum, send/receive API, and frame protocol. ### Connection State ```dart enum MeshCoreConnectionState { disconnected, scanning, connecting, connected, disconnecting, } ``` ### Frame I/O (all transports) - **Send**: `MeshCoreConnector.sendFrame(Uint8List data, {String? channelSendQueueId, bool expectsGenericAck})` - **Receive**: `Stream get receivedFrames` - **Protocol constants** (`meshcore_protocol.dart`): `maxFrameSize = 172`, `maxTextPayloadBytes = 160`, `appProtocolVersion = 4` ### BLE — Nordic UART Service (NUS) - **Service UUID**: `6e400001-b5a3-f393-e0a9-e50e24dcca9e` - **RX Characteristic** (write to device): `6e400002-b5a3-f393-e0a9-e50e24dcca9e` - **TX Characteristic** (notify from device): `6e400003-b5a3-f393-e0a9-e50e24dcca9e` - **Discovery**: scans for devices whose name starts with `MeshCore-`, `Whisper-`, `WisCore-`, `Seeed`, `Lilygo`, `HT-`, or `LowMesh_MC_` (filters on both `platformName` and `advertisementData.advName`) - **Linux**: `linux_ble_pairing_service.dart` falls back to `bluetoothctl` when BlueZ agent prompts fail ### TCP - Manual host/port entry, persisted via `AppSettingsService` (`tcpServerAddress`, `tcpServerPort`) - UI hint: `192.168.40.10` / port `5000` - Disabled on web (`PlatformInfo.isWeb`) - API: `MeshCoreConnector.connectTcp(host: ..., port: ...)` ### USB Serial (flserial) - Default baud rate: `115200` - Port enumeration: `MeshCoreConnector.listUsbPorts()` - COBS-framed packets via `usb_serial_frame_codec.dart` - macOS device-name resolution via `ioreg` (`utils/macos_usb_device_names.dart`) - API: `MeshCoreConnector.connectUsb(portName: ..., baudRate: 115200)` ## Dependencies App version: `8.0.0+11` — Dart SDK constraint: `^3.9.2` **Connectivity** | Package | Version | Purpose | |---------|---------|---------| | flutter_blue_plus | ^2.1.0 | BLE scanning, connecting, and UART data transfer | | flutter_blue_plus_platform_interface | ^8.2.1 | Platform-interface layer required by flutter_blue_plus | | flserial | git (MeshEnvy fork) | USB serial transport for wired device connections (TODO: upstream pending) | **State / Storage** | Package | Version | Purpose | |---------|---------|---------| | provider | ^6.1.5+1 | ChangeNotifier-based state management across screens | | shared_preferences | ^2.2.2 | Persistent key-value storage for user settings | | path_provider | ^2.1.5 | Locates platform-appropriate directories for file I/O | **Crypto** | Package | Version | Purpose | |---------|---------|---------| | crypto | ^3.0.3 | SHA/HMAC hashing used in message authentication | | pointycastle | ^4.0.0 | AES encryption/decryption for channel and direct messages | | uuid | ^4.3.3 | Generates UUIDs for message and contact identity | **Maps & Location** | Package | Version | Purpose | |---------|---------|---------| | flutter_map | ^8.2.2 | Interactive tile map for node positions and path traces | | latlong2 | ^0.9.1 | LatLng coordinate type used throughout map and GPS code | | gpx | ^2.3.0 | Export node paths as GPX track files | **UI** | Package | Version | Purpose | |---------|---------|---------| | material_symbols_icons | ^4.2906.0 | Extended Material Symbols icon set (line-of-sight, etc.) | | flutter_svg | ^2.0.10+1 | Renders SVG assets (custom icons such as LoS indicator) | | cached_network_image | ^3.4.1 | Caches map tile images downloaded over the network | | flutter_cache_manager | ^3.4.1 | Underlying cache manager used by cached_network_image | | flutter_linkify | ^6.0.0 | Auto-detects and makes URLs tappable in chat messages | | mobile_scanner | ^7.1.4 | QR/barcode scanning for contact and channel import | | qr_flutter | ^4.1.0 | Generates QR codes for sharing contacts and channels | | cupertino_icons | ^1.0.8 | iOS-style icon font (bundled for completeness) | | characters | ^1.4.0 | Unicode-aware string operations for message text handling | **Notifications / Background** | Package | Version | Purpose | |---------|---------|---------| | flutter_local_notifications | ^20.1.0 | Shows local push notifications for incoming messages | | flutter_foreground_task | ^9.2.0 | Keeps the app alive in background to maintain BLE/USB connection | **ML / AI** | Package | Version | Purpose | |---------|---------|---------| | ml_algo | ^16.0.0 | OLS regression used in `timeout_prediction_service.dart` to predict message ACK timeouts | | ml_dataframe | ^1.0.0 | DataFrame input format required by ml_algo | | llamadart | >=0.6.8 <0.7.0 | On-device LLM inference used in `translation_service.dart` for message translation | **Misc** | Package | Version | Purpose | |---------|---------|---------| | http | ^1.2.0 | Fetches tile URLs and any remote API calls | | url_launcher | ^6.3.0 | Opens URLs in the system browser from linkified chat text | | share_plus | ^12.0.1 | Shares files (e.g. exported GPX tracks) via the system share sheet | | package_info_plus | ^9.0.0 | Reads app version/build number displayed in settings | | web | ^1.1.1 | Web-platform APIs for USB serial and browser detection on Flutter Web | | intl | any | Internationalization and locale formatting (required by flutter_localizations) | | build_pipe | ^0.3.1 | CI/CD build pipeline configuration (web release builds with versioned assets) | ## Platform Configuration ### Android (`android/app/src/main/AndroidManifest.xml`) - `INTERNET` (map tiles, translation model downloads) - `BLUETOOTH`, `BLUETOOTH_ADMIN` (API ≤ 30) - `BLUETOOTH_SCAN` (with `neverForLocation`), `BLUETOOTH_CONNECT`, `BLUETOOTH_ADVERTISE` (API 31+) - `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION` (BLE scanning on API ≤ 30) - `POST_NOTIFICATIONS` (API 33+) - `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_CONNECTED_DEVICE` (background BLE/USB connection) - `WAKE_LOCK` - `CAMERA` (QR scanning, declared as optional feature) - USB host hardware feature (optional) `flutter_foreground_task` registers a `ForegroundService` with `foregroundServiceType="connectedDevice"` and `stopWithTask="false"`. **Build config (`android/app/build.gradle.kts`)**: `applicationId = com.meshcore.meshcore_open`, NDK `29.0.14206865`, Java 8 core-library desugaring (`desugar_jdk_libs:2.1.4`), release signing via `key.properties` (debug fallback). ### iOS (`ios/Runner/Info.plist`) - `NSBluetoothAlwaysUsageDescription`, `NSBluetoothPeripheralUsageDescription` - `NSCameraUsageDescription` (QR scanning to join communities) - Background modes: `bluetooth-central` - `LSApplicationQueriesSchemes`: `http`, `https` ### Web (`web/`) PWA scaffold present but boilerplate (`manifest.json` and `index.html` are unmodified Flutter defaults). BLE is unsupported in browsers; TCP and Web Serial USB may work in Chrome only. `ChromeRequiredScreen` gates non-Chrome web users. Versioned releases are produced via `build_pipe` (`?v=` cache busting, no service worker). ### Desktop `linux/`, `windows/`, and `macos/` directories are present as Flutter scaffolds. No app-specific native config has been added; BLE on desktop has not been validated. ## Coding Conventions ### Code Philosophy - **Minimal**: Only write code that is necessary. Avoid over-engineering. - **Organized**: Keep related code together. One responsibility per file. - **Maintainable**: Favor readability over cleverness. Simple is better. ### Style - Use `StatelessWidget` with `Consumer` for state-dependent UI - Use `const` constructors where possible - Prefix private methods/fields with `_` - Center app bar titles (`centerTitle: true`) - **Material widgets only** - no Cupertino or custom widgets - Handle disconnection gracefully (auto-navigate back to scanner) ### Avoid - Premature abstractions - don't create helpers until needed in 3+ places - Unnecessary comments - code should be self-explanatory - Feature flags or backwards-compatibility shims - Over-engineered error handling for impossible scenarios ## Key Files | File | Purpose | |------|---------| | `lib/main.dart` | App configuration, MultiProvider setup, theme, locale, initial route | | `lib/connector/meshcore_connector.dart` | Unified BLE/TCP/USB transport state holder | | `lib/connector/meshcore_protocol.dart` | Frame size limits and protocol version | | `lib/connector/meshcore_uuids.dart` | NUS UUIDs and BLE scan name prefixes | | `lib/services/app_settings_service.dart` | App-wide settings (`AppSettings` JSON in SharedPreferences) | | `lib/services/storage_service.dart` | Path history + delivery observation persistence | | `lib/services/message_retry_service.dart` | ACK tracking + retry scheduling | | `lib/services/translation_service.dart` | On-device LLM translation (llamadart) | | `lib/storage/prefs_manager.dart` | SharedPreferences singleton initialized in `main()` | | `lib/screens/scanner_screen.dart` | Home screen — BLE scan and connect | | `pubspec.yaml` | Dependencies and project metadata (current version `8.0.0+11`) |