MeshCore PT (lusoapp) is a Flutter companion app for MeshCore radios, built by and for the Portuguese amateur radio and mesh networking community.
- Channel Messaging — Send and receive messages on MeshCore channels
- Private Chat — End-to-end encrypted 1:1 messaging via Ed25519/X25519
- Radio Configuration — Full LoRa parameter control (frequency, bandwidth, SF, CR, TX power)
- Contact Management — View and manage discovered mesh nodes (chat, repeaters, rooms, sensors)
- BLE Connection — Connect to ESP32 and nRF52 MeshCore radios via Bluetooth Low Energy
- Serial Connection — Connect via USB OTG serial (115200 8N1)
- EU868 Presets — Quick radio presets compliant with Portuguese/EU regulations
- Portuguese UI — Full Portuguese (Portugal) interface
- Map View — Live GPS map of all contacts and mesh nodes (OpenStreetMap, no API key required)
- Offline Map — Browse-cached tiles via
flutter_map_tile_caching; tiles are stored automatically as you pan/zoom and served from disk when offline
Note on offline map caching: Tile caching is purely browse-based — tiles are saved as you explore the map while online and replayed when offline. There is no "download this area" bulk pre-download feature. This is intentional: OSM's tile server policy forbids bulk pre-downloading of regions.
- Flutter SDK >= 3.29.0
- Android Studio or VS Code with Flutter extensions
- A MeshCore radio (ESP32 or nRF52 based)
# Clone the repository
git clone <repo-url> lusoapp
cd lusoapp
# Get dependencies
flutter pub get
# Run on connected device
flutter run
# Build release APK
flutter build apk --release| Platform | Transport | Status |
|---|---|---|
| Android | BLE + Serial | Primary target |
| iOS | BLE | Planned |
| Windows | Serial | Planned |
| Linux | Serial | Planned |
lib/
├── main.dart # App entry point
├── protocol/ # MeshCore protocol implementation
│ ├── kiss.dart # KISS TNC framing
│ ├── commands.dart # Command/response constants
│ ├── models.dart # Data models (Contact, Message, RadioConfig)
│ ├── companion_encoder.dart # App→Radio frame encoder
│ └── companion_decoder.dart # Radio→App frame decoder
├── transport/ # Communication layer
│ ├── radio_transport.dart # Abstract transport interface
│ ├── ble_transport.dart # BLE (Nordic UART) transport
│ └── serial_transport.dart # USB Serial transport
├── services/
│ └── radio_service.dart # High-level radio communication coordinator
├── providers/
│ └── radio_providers.dart # Riverpod state management
└── ui/
├── theme.dart # Material 3 dark/light theme
├── router.dart # GoRouter navigation
└── screens/
├── connect_screen.dart # Device scan & connect
├── home_screen.dart # Main shell with bottom nav
├── channel_chat_screen.dart # Channel messaging
├── private_chat_screen.dart # 1:1 private chat
├── contacts_screen.dart # Contact list
├── radio_config_screen.dart # LoRa configuration
└── settings_screen.dart # App settings
The app implements the MeshCore Companion Radio Protocol v3:
- All App→Radio commands (APP_START, SEND_MSG, SEND_CHAN_MSG, GET_CONTACTS, SET_RADIO_PARAMS, etc.)
- All Radio→App responses (SELF_INFO, CONTACT, CHANNEL_MSG_RECV_V3, etc.)
- Unsolicited push notifications (ADVERT, MSG_WAITING, SEND_CONFIRMED, etc.)
- BLE: Nordic UART Service (
6E400001-B5A3-F393-E0A9-E50E24DCCA9E) - Serial: 115200 baud, 8N1, DTR+RTS
Default LoRa parameters comply with EU868 SRD regulations:
- Frequency: 869.618 MHz
- Bandwidth: 62.5 kHz
- TX Power: 14 dBm ERP max
- Duty Cycle: Users must respect 10% limits
Users are responsible for compliance with ANACOM regulations and amateur radio licence conditions.
Contributions are welcome! Please see the ROADMAP.md for planned features and the project direction.
The app uses Flutter's built-in flutter_localizations / intl ARB pipeline.
lib/l10n/
├── app_pt.arb ← Template (source of truth, Portuguese PT)
├── app_en.arb ← English translation
├── app_es.arb ← Spanish translation
├── app_localizations.dart ← Generated — do NOT edit by hand
├── app_localizations_pt.dart ← Generated
├── app_localizations_en.dart ← Generated
├── app_localizations_es.dart ← Generated
└── l10n.dart ← BuildContext extension: context.l10n.<key>
Configuration lives in l10n.yaml (project root):
arb-dir: lib/l10n
template-arb-file: app_pt.arb # PT is the template locale
output-localization-file: app_localizations.dart
output-dir: lib/l10n
nullable-getter: false-
Edit
lib/l10n/app_pt.arb— add the new key and its Portuguese value.
Keys follow the naming convention<screenOrGroup><PascalCaseName>, e.g.commonSave,navChannels,settingsTitle."myNewKey": "Texto em português", "@myNewKey": {}
-
Repeat for every other ARB file (
app_en.arb,app_es.arb, …) adding the translated text for each locale. -
Regenerate the Dart classes:
flutter gen-l10n
This regenerates
app_localizations.dartand allapp_localizations_<locale>.dartfiles.
Never edit those files manually — they are overwritten on every run. -
Use the string in code:
import '../../l10n/l10n.dart'; // Inside a Widget build method: Text(context.l10n.myNewKey)
- Create
lib/l10n/app_<locale>.arb— copyapp_en.arbas a starting point and translate all values. - Run
flutter gen-l10n— the new locale is picked up automatically. - No changes to
pubspec.yamlormain.dartare needed;AppLocalizations.supportedLocalesis generated from the ARB files.
Flutter ARB supports ICU message syntax. Example with a parameter:
"unreadCount": "{count} mensagens não lidas",
"@unreadCount": {
"placeholders": {
"count": { "type": "int" }
}
}Usage: context.l10n.unreadCount(42)
# Regenerate translations (run from project root)
flutter gen-l10nMIT License — see LICENSE for details.