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.
# 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 testlib/
├── 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
All screens are fully implemented (no remaining placeholders).
| 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) |
| 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 |
| Screen | Purpose |
|---|---|
contacts_screen.dart |
Full contacts list with previews and management |
neighbors_screen.dart |
Nodes directly heard by the connected radio (one-hop) |
| 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 |
| 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 |
| 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 |
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<T> (or context.watch<T>() / context.read<T>()) for reactive UI.
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.
- Material 3 design (
useMaterial3: true) - System-based dark/light mode (
ThemeMode.system) - Blue color scheme seed
lib/theme/mesh_theme.dartdefines a warm-darkMeshPalette(phosphor-green accents) but is not currently wired inmain.dart— available for a future redesign
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.
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.
enum MeshCoreConnectionState {
disconnected,
scanning,
connecting,
connected,
disconnecting,
}- Send:
MeshCoreConnector.sendFrame(Uint8List data, {String? channelSendQueueId, bool expectsGenericAck}) - Receive:
Stream<Uint8List> get receivedFrames - Protocol constants (
meshcore_protocol.dart):maxFrameSize = 172,maxTextPayloadBytes = 160,appProtocolVersion = 4
- 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-, orLowMesh_MC_(filters on bothplatformNameandadvertisementData.advName) - Linux:
linux_ble_pairing_service.dartfalls back tobluetoothctlwhen BlueZ agent prompts fail
- Manual host/port entry, persisted via
AppSettingsService(tcpServerAddress,tcpServerPort) - UI hint:
192.168.40.10/ port5000 - Disabled on web (
PlatformInfo.isWeb) - API:
MeshCoreConnector.connectTcp(host: ..., port: ...)
- 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)
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) |
INTERNET(map tiles, translation model downloads)BLUETOOTH,BLUETOOTH_ADMIN(API ≤ 30)BLUETOOTH_SCAN(withneverForLocation),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_LOCKCAMERA(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).
NSBluetoothAlwaysUsageDescription,NSBluetoothPeripheralUsageDescriptionNSCameraUsageDescription(QR scanning to join communities)- Background modes:
bluetooth-central LSApplicationQueriesSchemes:http,https
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=<pubspec version> cache busting, no service worker).
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.
- 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.
- Use
StatelessWidgetwithConsumerfor state-dependent UI - Use
constconstructors 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)
- 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
| 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) |