From 7d47ebc8a1c04ce4a0d6c6417fd10668bf19b0ca Mon Sep 17 00:00:00 2001 From: Eelco Date: Mon, 27 Oct 2025 17:43:27 +0100 Subject: [PATCH 01/19] Expose stream controllers for connection and packets --- lib/src/meshtastic_client.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index 9166d31..e5c6578 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -64,6 +64,11 @@ class MeshtasticClient { Stream get packetStream => _packetController.stream; Stream get nodeStream => _nodeController.stream; + // Controllers + StreamController get connectionController = _connectionController; + StreamController get packetController = _packetController; + StreamController get nodeController = _nodeController; + // Getters for current state Map get nodes => Map.unmodifiable(_nodes); MyNodeInfo? get myNodeInfo => _myNodeInfo; From af8782d9bd84fcbf461ea6923bb5a1222f7f4714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Moreau?= Date: Fri, 2 Jan 2026 14:42:00 +0100 Subject: [PATCH 02/19] Improves position data --- README.md | 40 ++++++++++++++++++++++- lib/src/models/mesh_packet_wrapper.dart | 43 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e15b14c..ac69a46 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ dependencies: meshtastic_flutter: ^0.0.1 ``` -Complete the setup for ```permission_handler``` plugin as given [here](https://pub.dev/packages/permission_handler) +Complete the setup for `permission_handler` plugin as given [here](https://pub.dev/packages/permission_handler) ## Permissions @@ -125,6 +125,16 @@ Wrapper class for MeshPacket with convenience methods. - `isEncrypted` / `isDecoded` - Encryption status - `packetTypeDescription` - Human-readable packet type +#### Position Data (from packet payload) + +The following properties decode position data directly from the packet payload, ensuring you get the exact position from each specific packet rather than the node's current position: + +- `positionData` - Decoded `Position` protobuf object from packet payload +- `latitude` - Latitude in decimal degrees (null if not a position packet) +- `longitude` - Longitude in decimal degrees (null if not a position packet) +- `altitude` - Altitude in meters (null if not available) +- `timestamp` - Timestamp from position data (null if not available) + ### NodeInfoWrapper Wrapper class for NodeInfo with enhanced functionality. @@ -174,6 +184,34 @@ await client.sendTextMessage('Private message', destinationId: 0x12345678); await client.sendTextMessage('Channel message', channel: 1); ``` +### Position Tracking + +```dart +// Listen for position packets and extract position data from each packet +client.packetStream.listen((packet) { + if (packet.isPosition) { + // Extract position directly from packet payload + // This gives you the exact position from THIS packet, not the node's current position + if (packet.latitude != null && packet.longitude != null) { + print('Position from ${packet.from.toRadixString(16)}:'); + print(' Lat: ${packet.latitude}'); + print(' Lon: ${packet.longitude}'); + print(' Alt: ${packet.altitude}m'); + print(' Time: ${packet.timestamp}'); + + // Save to database with packet-specific position + savePositionToDatabase( + deviceId: packet.from, + latitude: packet.latitude!, + longitude: packet.longitude!, + altitude: packet.altitude, + timestamp: packet.timestamp, + ); + } + } +}); +``` + ### Node Monitoring ```dart diff --git a/lib/src/models/mesh_packet_wrapper.dart b/lib/src/models/mesh_packet_wrapper.dart index f5d2df9..698473e 100644 --- a/lib/src/models/mesh_packet_wrapper.dart +++ b/lib/src/models/mesh_packet_wrapper.dart @@ -77,6 +77,49 @@ class MeshPacketWrapper { } } + /// Get the position data from this packet (if this is a position packet) + /// Returns a Position object decoded directly from the packet payload + Position? get positionData { + if (!isPosition || decoded == null || decoded!.payload.isEmpty) return null; + try { + return Position.fromBuffer(decoded!.payload); + } catch (e) { + return null; + } + } + + /// Get latitude in decimal degrees from position packet + /// Returns null if not a position packet or position data unavailable + double? get latitude { + final pos = positionData; + if (pos == null || !pos.hasLatitudeI()) return null; + return pos.latitudeI / 1e7; + } + + /// Get longitude in decimal degrees from position packet + /// Returns null if not a position packet or position data unavailable + double? get longitude { + final pos = positionData; + if (pos == null || !pos.hasLongitudeI()) return null; + return pos.longitudeI / 1e7; + } + + /// Get altitude in meters from position packet + /// Returns null if not a position packet or altitude unavailable + int? get altitude { + final pos = positionData; + if (pos == null || !pos.hasAltitude()) return null; + return pos.altitude; + } + + /// Get timestamp from position packet + /// Returns null if not a position packet or time unavailable + int? get timestamp { + final pos = positionData; + if (pos == null || !pos.hasTime()) return null; + return pos.time; + } + /// Get the JSON payload as a string (if applicable) String? get jsonPayload { if (decoded == null) return null; From 2cf76014b7e7fb75102342552ac2603aeca43162 Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 11:22:34 -0600 Subject: [PATCH 03/19] =?UTF-8?q?fix:=20iOS=20compatibility=20=E2=80=94=20?= =?UTF-8?q?platform-guard=20requestMtu=20and=20Android-only=20BLE=20permis?= =?UTF-8?q?sions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit requestMtu(512) is Android-only in flutter_blue_plus — throws on iOS. iOS negotiates MTU automatically, so the call is unnecessary. bluetoothConnect and bluetoothScan permissions are Android 12+ only. Requesting them on iOS always returns denied. --- lib/src/meshtastic_client.dart | 38 ++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index 9166d31..25a8f87 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io' show Platform; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -102,19 +103,28 @@ class MeshtasticClient { /// Request necessary permissions for BLE Future _requestPermissions() async { - final permissions = [ - Permission.bluetooth, - Permission.bluetoothConnect, - Permission.bluetoothScan, - Permission.locationWhenInUse, - ]; - - for (final permission in permissions) { - final status = await permission.request(); - if (!status.isGranted) { - throw PermissionException('Permission denied: $permission'); + // Bluetooth permission is required on all platforms + final bluetoothStatus = await Permission.bluetooth.request(); + if (!bluetoothStatus.isGranted) { + throw const PermissionException('Permission denied: Permission.bluetooth'); + } + + // bluetoothConnect and bluetoothScan are Android-only (API 31+) + if (!kIsWeb && Platform.isAndroid) { + final connectStatus = await Permission.bluetoothConnect.request(); + if (!connectStatus.isGranted) { + throw const PermissionException('Permission denied: Permission.bluetoothConnect'); + } + final scanStatus = await Permission.bluetoothScan.request(); + if (!scanStatus.isGranted) { + throw const PermissionException('Permission denied: Permission.bluetoothScan'); } } + + final locationStatus = await Permission.locationWhenInUse.request(); + if (!locationStatus.isGranted) { + throw const PermissionException('Permission denied: Permission.locationWhenInUse'); + } } /// Scan for nearby Meshtastic devices @@ -229,8 +239,10 @@ class MeshtasticClient { 'notify=${_fromNumChar!.properties.notify}', ); - // Set MTU to 512 - await device.requestMtu(512); + // Set MTU to 512 (Android only — iOS negotiates MTU automatically) + if (!kIsWeb && Platform.isAndroid) { + await device.requestMtu(512); + } // Enable notifications on FromNum await _fromNumChar!.setNotifyValue(true); From 497b9fc2dac532b28a5fbf43e8bb66f15ba01532 Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 12:18:51 -0600 Subject: [PATCH 04/19] fix: Android 12+ uses bluetoothConnect/bluetoothScan, not legacy Permission.bluetooth --- lib/src/meshtastic_client.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index 25a8f87..e6d3d9f 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -103,14 +103,8 @@ class MeshtasticClient { /// Request necessary permissions for BLE Future _requestPermissions() async { - // Bluetooth permission is required on all platforms - final bluetoothStatus = await Permission.bluetooth.request(); - if (!bluetoothStatus.isGranted) { - throw const PermissionException('Permission denied: Permission.bluetooth'); - } - - // bluetoothConnect and bluetoothScan are Android-only (API 31+) if (!kIsWeb && Platform.isAndroid) { + // Android 12+ (API 31+): use specific BLE permissions, not legacy Permission.bluetooth final connectStatus = await Permission.bluetoothConnect.request(); if (!connectStatus.isGranted) { throw const PermissionException('Permission denied: Permission.bluetoothConnect'); @@ -119,6 +113,12 @@ class MeshtasticClient { if (!scanStatus.isGranted) { throw const PermissionException('Permission denied: Permission.bluetoothScan'); } + } else if (!kIsWeb && Platform.isIOS) { + // iOS: Permission.bluetooth maps to CBManagerAuthorization + final bluetoothStatus = await Permission.bluetooth.request(); + if (!bluetoothStatus.isGranted) { + throw const PermissionException('Permission denied: Permission.bluetooth'); + } } final locationStatus = await Permission.locationWhenInUse.request(); From c0ed2fcb9495b4e6c2bebeda2a1a94239b78099b Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 13:05:11 -0600 Subject: [PATCH 05/19] feat: add sendAdminConfig for device configuration (GPS disable, etc) --- lib/meshtastic_flutter.dart | 2 ++ lib/src/meshtastic_client.dart | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/meshtastic_flutter.dart b/lib/meshtastic_flutter.dart index 8ee4327..179c9f9 100644 --- a/lib/meshtastic_flutter.dart +++ b/lib/meshtastic_flutter.dart @@ -8,3 +8,5 @@ export 'generated/mesh.pb.dart'; export 'generated/mesh.pbenum.dart'; export 'generated/config.pb.dart'; export 'generated/module_config.pb.dart'; +export 'generated/admin.pb.dart'; +export 'generated/config.pbenum.dart'; diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index e6d3d9f..fff09bf 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -10,6 +10,7 @@ import 'package:permission_handler/permission_handler.dart'; import '../generated/mesh.pb.dart'; import '../generated/config.pb.dart'; import '../generated/module_config.pb.dart'; +import '../generated/admin.pb.dart'; import '../generated/channel.pb.dart'; import '../generated/portnums.pb.dart'; import 'models/connection_state.dart'; @@ -376,6 +377,32 @@ class MeshtasticClient { await _sendPacket(packet); } + /// Send an admin config message to the device (e.g., to disable device GPS) + Future sendAdminConfig(Config config) async { + if (!isConnected) { + throw const ConnectionException('Not connected to a device'); + } + + final adminMessage = AdminMessage(setConfig: config); + final packetId = DateTime.now().millisecondsSinceEpoch & 0xFFFFFFFF; + + final packet = MeshPacket( + from: _myNodeInfo?.myNodeNum ?? 0, + to: _myNodeInfo?.myNodeNum ?? 0, // Send to self (local device) + id: packetId, + decoded: Data( + portnum: PortNum.ADMIN_APP, + payload: adminMessage.writeToBuffer(), + wantResponse: true, + ), + hopLimit: 0, // Local only + priority: MeshPacket_Priority.RELIABLE, + ); + + _logger.info('Sending admin config'); + await _sendPacket(packet); + } + /// Send a packet to the device Future _sendPacket(MeshPacket packet) async { if (_toRadioChar == null) { From 8a88b322ee988a021625a2fb38a86c09c1b21dcd Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 14:55:13 -0600 Subject: [PATCH 06/19] fix: use write-with-response for BLE packets and non-zero wantConfigId Write-without-response can silently drop packets. The official Python library uses response=True for all ToRadio writes. Also use a non-zero wantConfigId so the firmware sends back a matching configCompleteId. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/meshtastic_client.dart | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index fff09bf..9c8bf42 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -421,13 +421,11 @@ class MeshtasticClient { 'id=${packet.id}, portnum=${packet.decoded.portnum}, size=${data.length} bytes', ); - // Check if characteristic supports write without response - final supportsWriteWithoutResponse = - _toRadioChar!.properties.writeWithoutResponse; - + // Always write WITH response — write-without-response can silently drop packets. + // The official Python library uses response=True for all ToRadio writes. await _toRadioChar!.write( data, - withoutResponse: supportsWriteWithoutResponse, + withoutResponse: false, ); _logger.fine('Packet sent successfully'); @@ -438,13 +436,12 @@ class MeshtasticClient { _logger.info('Starting configuration process'); // Send wantConfigId to start configuration download - final wantConfig = ToRadio(wantConfigId: 0); - // Check if characteristic supports write without response - final supportsWriteWithoutResponse = - _toRadioChar!.properties.writeWithoutResponse; + // Use a non-zero random ID — firmware sends back matching configCompleteId + final configId = DateTime.now().millisecondsSinceEpoch & 0xFFFFFFFF; + final wantConfig = ToRadio(wantConfigId: configId); await _toRadioChar!.write( wantConfig.writeToBuffer(), - withoutResponse: supportsWriteWithoutResponse, + withoutResponse: false, ); // Start reading configuration data From ef25d6b723f18887566ef20537092f50c2891055 Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 15:10:24 -0600 Subject: [PATCH 07/19] fix: mark config complete even on read error BLE connection is established with services and notifications ready. Config read errors during handshake shouldn't block packet sending. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/meshtastic_client.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index 9c8bf42..9e4bec4 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -468,6 +468,10 @@ class MeshtasticClient { await Future.delayed(const Duration(milliseconds: 50)); } catch (e) { _logger.warning('Error reading configuration: $e'); + // Mark config as complete anyway — BLE is connected, services discovered, + // notifications enabled. Config read errors shouldn't block packet sending. + _configComplete = true; + _emitConnectionState(MeshtasticConnectionState.connected); break; } } From ea1be4b2622311ba72875807f13a05bd525325ac Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 15:55:49 -0600 Subject: [PATCH 08/19] feat: add sendData method for custom port packets Allows sending raw data on PRIVATE_APP port which bypasses firmware position rate limiting and merging. Used for phone GPS relay. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/meshtastic_client.dart | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index 9e4bec4..2b9e0f4 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -335,8 +335,33 @@ class MeshtasticClient { await _sendPacket(packet); } + /// Send raw data on a custom port (e.g., PRIVATE_APP) — not intercepted by firmware modules + Future sendData( + List payload, { + int portnum = 256, // PRIVATE_APP + }) async { + if (!isConnected) { + throw const ConnectionException('Not connected to a device'); + } + + final packetId = DateTime.now().millisecondsSinceEpoch & 0xFFFFFFFF; + + final packet = MeshPacket( + to: 0xFFFFFFFF, // Broadcast + id: packetId, + decoded: Data( + portnum: PortNum.valueOf(portnum) ?? PortNum.PRIVATE_APP, + payload: payload, + ), + hopLimit: 3, + priority: MeshPacket_Priority.RELIABLE, + ); + + _logger.info('Sending custom data: ${payload.length} bytes on port $portnum'); + await _sendPacket(packet); + } + /// Send a position update - Future sendPosition( double latitude, double longitude, { int? altitude, From 3ca3a6504b05777989c25cd1c4bee3f58ede3613 Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 15:59:32 -0600 Subject: [PATCH 09/19] fix: restore sendPosition signature, export PortNum Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/meshtastic_flutter.dart | 1 + lib/src/meshtastic_client.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/meshtastic_flutter.dart b/lib/meshtastic_flutter.dart index 179c9f9..6227fef 100644 --- a/lib/meshtastic_flutter.dart +++ b/lib/meshtastic_flutter.dart @@ -10,3 +10,4 @@ export 'generated/config.pb.dart'; export 'generated/module_config.pb.dart'; export 'generated/admin.pb.dart'; export 'generated/config.pbenum.dart'; +export 'generated/portnums.pbenum.dart'; diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index 2b9e0f4..fb135fa 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -362,6 +362,7 @@ class MeshtasticClient { } /// Send a position update + Future sendPosition( double latitude, double longitude, { int? altitude, From f8e006162536f25aaffcdf7aaa4b9ab38bb96abc Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 16:08:06 -0600 Subject: [PATCH 10/19] fix: sendData checks _toRadioChar instead of isConnected The isConnected/isConfigured flags are unreliable after reconnect. Just check if the BLE characteristic exists and try to write. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/meshtastic_client.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index fb135fa..7864bbe 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -336,11 +336,13 @@ class MeshtasticClient { } /// Send raw data on a custom port (e.g., PRIVATE_APP) — not intercepted by firmware modules + /// No isConnected/isConfigured checks — just try to write. If BLE is down, + /// the write throws a platform exception which the caller handles. Future sendData( List payload, { int portnum = 256, // PRIVATE_APP }) async { - if (!isConnected) { + if (_toRadioChar == null) { throw const ConnectionException('Not connected to a device'); } From ba243b172a1f356adf28c60349c64c0c1eea7768 Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 16:54:12 -0600 Subject: [PATCH 11/19] fix: sendData returns silently when not ready instead of throwing Throwing 'Not connected' caused the caller's error handler to trigger reconnect, destroying the working connection. Return silently instead and let the BLE write surface actual disconnect errors. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/meshtastic_client.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index 7864bbe..cfbddc9 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -336,15 +336,13 @@ class MeshtasticClient { } /// Send raw data on a custom port (e.g., PRIVATE_APP) — not intercepted by firmware modules - /// No isConnected/isConfigured checks — just try to write. If BLE is down, - /// the write throws a platform exception which the caller handles. + /// Returns silently if not ready. If BLE is down, the write throws a + /// platform exception ("device is disconnected") which the caller handles. Future sendData( List payload, { int portnum = 256, // PRIVATE_APP }) async { - if (_toRadioChar == null) { - throw const ConnectionException('Not connected to a device'); - } + if (_toRadioChar == null) return; // Not ready yet — skip silently final packetId = DateTime.now().millisecondsSinceEpoch & 0xFFFFFFFF; From 1ffa9d031f0a5fe109e9f59ca920536cc49703f9 Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 17:14:26 -0600 Subject: [PATCH 12/19] debug: add print logging to sendData to trace BLE writes Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/meshtastic_client.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index cfbddc9..d3f06ae 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -342,7 +342,10 @@ class MeshtasticClient { List payload, { int portnum = 256, // PRIVATE_APP }) async { - if (_toRadioChar == null) return; // Not ready yet — skip silently + if (_toRadioChar == null) { + print('🔴 MESH sendData: _toRadioChar is null — SKIPPING'); + return; + } final packetId = DateTime.now().millisecondsSinceEpoch & 0xFFFFFFFF; @@ -357,8 +360,9 @@ class MeshtasticClient { priority: MeshPacket_Priority.RELIABLE, ); - _logger.info('Sending custom data: ${payload.length} bytes on port $portnum'); + print('🟢 MESH sendData: writing ${payload.length} bytes to BLE'); await _sendPacket(packet); + print('🟢 MESH sendData: write SUCCESS'); } /// Send a position update From 44999ca1a3608064e9d8fd159344c4eb9b0a0d17 Mon Sep 17 00:00:00 2001 From: seanconnell Date: Thu, 9 Apr 2026 21:33:02 -0600 Subject: [PATCH 13/19] debug: log _toRadioChar state in connectToDevice Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/meshtastic_client.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index d3f06ae..fcfcb6c 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -226,6 +226,8 @@ class MeshtasticClient { throw const ConnectionException('FromNum characteristic not found'), ); + print('🔧 connectToDevice: _toRadioChar SET = ${_toRadioChar != null}'); + // Log characteristic properties for debugging _logger.info( 'ToRadio properties: write=${_toRadioChar!.properties.write}, ' @@ -256,6 +258,7 @@ class MeshtasticClient { // Start configuration process await _startConfiguration(); + print('🔧 connectToDevice: DONE. _toRadioChar=${_toRadioChar != null}, _configComplete=$_configComplete'); _logger.info('Successfully connected to device'); } catch (e) { _logger.severe('Failed to connect to device: $e'); From 39ef6082354d1227f4d58ccd9058c38319bce82e Mon Sep 17 00:00:00 2001 From: seanconnell Date: Fri, 10 Apr 2026 11:31:51 -0600 Subject: [PATCH 14/19] cleanup: remove debug print() statements from release path Removed emoji-tagged debug prints that would ship to production. Config and send paths now use _logger consistently. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/meshtastic_client.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index fcfcb6c..78e62b7 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -226,8 +226,6 @@ class MeshtasticClient { throw const ConnectionException('FromNum characteristic not found'), ); - print('🔧 connectToDevice: _toRadioChar SET = ${_toRadioChar != null}'); - // Log characteristic properties for debugging _logger.info( 'ToRadio properties: write=${_toRadioChar!.properties.write}, ' @@ -258,7 +256,6 @@ class MeshtasticClient { // Start configuration process await _startConfiguration(); - print('🔧 connectToDevice: DONE. _toRadioChar=${_toRadioChar != null}, _configComplete=$_configComplete'); _logger.info('Successfully connected to device'); } catch (e) { _logger.severe('Failed to connect to device: $e'); @@ -345,10 +342,7 @@ class MeshtasticClient { List payload, { int portnum = 256, // PRIVATE_APP }) async { - if (_toRadioChar == null) { - print('🔴 MESH sendData: _toRadioChar is null — SKIPPING'); - return; - } + if (_toRadioChar == null) return; // Not ready — skip silently final packetId = DateTime.now().millisecondsSinceEpoch & 0xFFFFFFFF; @@ -363,9 +357,8 @@ class MeshtasticClient { priority: MeshPacket_Priority.RELIABLE, ); - print('🟢 MESH sendData: writing ${payload.length} bytes to BLE'); + _logger.info('Sending custom data: ${payload.length} bytes on port $portnum'); await _sendPacket(packet); - print('🟢 MESH sendData: write SUCCESS'); } /// Send a position update From ac3e4283c68e0ce05e4d0c89e1fef9f72a74d981 Mon Sep 17 00:00:00 2001 From: Eelco Date: Fri, 5 Jun 2026 13:42:59 +0200 Subject: [PATCH 15/19] Downgrade protobuf dependency version to 3.7.0 for compatibility downgrade to 3.7 protobuf package --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 622a696..120d6f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - protobuf: ^4.2.0 + protobuf: ^3.7.0 flutter_blue_plus: ^1.35.5 permission_handler: ^12.0.1 logging: ^1.3.0 From 04b2c6c16545ae7332965fb7ae92f61da9c9b73b Mon Sep 17 00:00:00 2001 From: Eelco Date: Fri, 5 Jun 2026 13:48:06 +0200 Subject: [PATCH 16/19] Update protobuf dependency version to 3.7.3 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 120d6f9..c75fca8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - protobuf: ^3.7.0 + protobuf: ^3.7.3 flutter_blue_plus: ^1.35.5 permission_handler: ^12.0.1 logging: ^1.3.0 From 6e8bc59c74cb33f587e4eda7cf3c6ebee3d768df Mon Sep 17 00:00:00 2001 From: Eelco Date: Fri, 5 Jun 2026 13:55:07 +0200 Subject: [PATCH 17/19] Downgrade protobuf dependency version to 3.1.0 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c75fca8..29268d8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - protobuf: ^3.7.3 + protobuf: ^3.1.0 flutter_blue_plus: ^1.35.5 permission_handler: ^12.0.1 logging: ^1.3.0 From bb0a64c01170b8dbe4daa46f306381beeacfcb1a Mon Sep 17 00:00:00 2001 From: Eelco Date: Fri, 5 Jun 2026 15:29:47 +0200 Subject: [PATCH 18/19] Fix getter syntax for stream controllers --- lib/src/meshtastic_client.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/meshtastic_client.dart b/lib/src/meshtastic_client.dart index 8b0007b..335b4f7 100644 --- a/lib/src/meshtastic_client.dart +++ b/lib/src/meshtastic_client.dart @@ -67,9 +67,9 @@ class MeshtasticClient { Stream get nodeStream => _nodeController.stream; // Controllers - StreamController get connectionController = _connectionController; - StreamController get packetController = _packetController; - StreamController get nodeController = _nodeController; + StreamController get connectionController => _connectionController; + StreamController get packetController => _packetController; + StreamController get nodeController => _nodeController; // Getters for current state Map get nodes => Map.unmodifiable(_nodes); From 36a36dc875c6602a3871ff09ad35b8932c9106c0 Mon Sep 17 00:00:00 2001 From: Eelco Date: Fri, 5 Jun 2026 15:41:21 +0200 Subject: [PATCH 19/19] Update protobuf dependency to version 4.2.0 revert back to version 4.2.0 for this branch. the V3.1.0 protobufs are found in the `protobufs3` branch. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 29268d8..622a696 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - protobuf: ^3.1.0 + protobuf: ^4.2.0 flutter_blue_plus: ^1.35.5 permission_handler: ^12.0.1 logging: ^1.3.0