Skip to content

Commit 654b5f3

Browse files
committed
Update build configuration and enhance Bluetooth device handling
- Change Flutter build mode to release and update version code - Simplify target path in configuration files - Refactor heartbeat logic to schedule based on session expiry - Preserve scanned device names for better connection handling in Bluetooth services - Improve logging for device connection and scanning processes
1 parent b85a5b1 commit 654b5f3

File tree

6 files changed

+121
-51
lines changed

6 files changed

+121
-51
lines changed

android/local.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
sdk.dir=/opt/homebrew/share/android-commandlinetools
22
flutter.sdk=/opt/homebrew/share/flutter
3-
flutter.buildMode=debug
3+
flutter.buildMode=release
44
flutter.versionName=1.0.0
5-
flutter.versionCode=1
5+
flutter.versionCode=1769234591

ios/Flutter/Generated.xcconfig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
FLUTTER_ROOT=/opt/homebrew/share/flutter
33
FLUTTER_APPLICATION_PATH=/Users/schnobbc/Documents/Github/MeshMapper_Flutter_App
44
COCOAPODS_PARALLEL_CODE_SIGN=true
5-
FLUTTER_TARGET=/Users/schnobbc/Documents/Github/MeshMapper_Flutter_App/lib/main.dart
5+
FLUTTER_TARGET=lib/main.dart
66
FLUTTER_BUILD_DIR=build
77
FLUTTER_BUILD_NAME=1.0.0
8-
FLUTTER_BUILD_NUMBER=1
8+
FLUTTER_BUILD_NUMBER=1769234591
99
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
1010
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
11-
DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzguNw==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049M2I2MmVmYzJhMw==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NzhmYzMwMTJlNA==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC43
11+
DART_DEFINES=QVBQX1ZFUlNJT049QVBQLTE3NjkyMzQ1OTE=,RkxVVFRFUl9WRVJTSU9OPTMuMzguNw==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049M2I2MmVmYzJhMw==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NzhmYzMwMTJlNA==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC43
1212
DART_OBFUSCATION=false
13-
TRACK_WIDGET_CREATION=true
14-
TREE_SHAKE_ICONS=false
13+
TRACK_WIDGET_CREATION=false
14+
TREE_SHAKE_ICONS=true
1515
PACKAGE_CONFIG=/Users/schnobbc/Documents/Github/MeshMapper_Flutter_App/.dart_tool/package_config.json

ios/Flutter/flutter_export_environment.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
export "FLUTTER_ROOT=/opt/homebrew/share/flutter"
44
export "FLUTTER_APPLICATION_PATH=/Users/schnobbc/Documents/Github/MeshMapper_Flutter_App"
55
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
6-
export "FLUTTER_TARGET=/Users/schnobbc/Documents/Github/MeshMapper_Flutter_App/lib/main.dart"
6+
export "FLUTTER_TARGET=lib/main.dart"
77
export "FLUTTER_BUILD_DIR=build"
88
export "FLUTTER_BUILD_NAME=1.0.0"
9-
export "FLUTTER_BUILD_NUMBER=1"
10-
export "DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzguNw==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049M2I2MmVmYzJhMw==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NzhmYzMwMTJlNA==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC43"
9+
export "FLUTTER_BUILD_NUMBER=1769234591"
10+
export "DART_DEFINES=QVBQX1ZFUlNJT049QVBQLTE3NjkyMzQ1OTE=,RkxVVFRFUl9WRVJTSU9OPTMuMzguNw==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049M2I2MmVmYzJhMw==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NzhmYzMwMTJlNA==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC43"
1111
export "DART_OBFUSCATION=false"
12-
export "TRACK_WIDGET_CREATION=true"
13-
export "TREE_SHAKE_ICONS=false"
12+
export "TRACK_WIDGET_CREATION=false"
13+
export "TREE_SHAKE_ICONS=true"
1414
export "PACKAGE_CONFIG=/Users/schnobbc/Documents/Github/MeshMapper_Flutter_App/.dart_tool/package_config.json"

lib/services/api_service.dart

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ class ApiService {
2525
/// API key (matching wardrive.js)
2626
static const String apiKey = '59C7754DABDF5C11CA5F5D8368F89';
2727

28-
/// Heartbeat idle timeout (send keepalive after 3 minutes of no API activity)
29-
static const Duration heartbeatIdleTimeout = Duration(minutes: 3);
28+
/// Heartbeat buffer - schedule heartbeat 1 minute before session expiry
29+
static const Duration heartbeatBuffer = Duration(minutes: 1);
3030

3131
final http.Client _client;
3232
bool _heartbeatEnabled = false; // Track if heartbeat mode is active
@@ -215,12 +215,10 @@ class ApiService {
215215

216216
final data = json.decode(response.body) as Map<String, dynamic>;
217217

218-
// Update expires_at if provided and reset heartbeat idle timer
219-
if (data['success'] == true) {
220-
if (data['expires_at'] != null) {
221-
_sessionExpiresAt = data['expires_at'] as int;
222-
}
223-
_resetHeartbeatTimer(); // Resets 3-min idle timer on each successful upload
218+
// Update expires_at and schedule heartbeat if provided
219+
if (data['success'] == true && data['expires_at'] != null) {
220+
_sessionExpiresAt = data['expires_at'] as int;
221+
scheduleHeartbeat(_sessionExpiresAt!);
224222
}
225223

226224
return data;
@@ -236,6 +234,7 @@ class ApiService {
236234
double? lon,
237235
}) async {
238236
if (_sessionId == null) {
237+
debugLog('[HEARTBEAT] Cannot send heartbeat: no session_id');
239238
return null;
240239
}
241240

@@ -254,6 +253,10 @@ class ApiService {
254253
};
255254
}
256255

256+
// Log the full request payload
257+
debugLog('[HEARTBEAT] POST $wardriveEndpoint');
258+
debugLog('[HEARTBEAT] Payload: ${json.encode(payload)}');
259+
257260
final response = await _client.post(
258261
Uri.parse(wardriveEndpoint),
259262
headers: {'Content-Type': 'application/json'},
@@ -262,25 +265,34 @@ class ApiService {
262265

263266
final data = json.decode(response.body) as Map<String, dynamic>;
264267

265-
// Update expires_at if provided (timer reset handled by _resetHeartbeatTimer in caller)
268+
// Log the response (matching wardrive.js logging)
269+
debugLog('[HEARTBEAT] Response status: ${response.statusCode}');
270+
debugLog('[HEARTBEAT] Response data: ${json.encode(data)}');
271+
272+
// Update expires_at and schedule next heartbeat if provided
266273
if (data['success'] == true && data['expires_at'] != null) {
267274
_sessionExpiresAt = data['expires_at'] as int;
275+
scheduleHeartbeat(_sessionExpiresAt!);
268276
}
269277

270278
return data;
271279
} catch (e) {
280+
debugError('[HEARTBEAT] Request failed: $e');
272281
return null;
273282
}
274283
}
275284

276285
/// Enable heartbeat mode (called when auto mode starts)
277-
/// Heartbeat fires after 3 minutes of API inactivity to keep session alive
286+
/// Heartbeat is scheduled based on expires_at from API responses
278287
/// @param gpsProvider Callback to get current GPS coordinates for heartbeat
279288
void enableHeartbeat({({double lat, double lon})? Function()? gpsProvider}) {
280289
_heartbeatEnabled = true;
281290
_gpsProvider = gpsProvider;
282-
_resetHeartbeatTimer();
283-
debugLog('[API] Heartbeat mode enabled (3 min idle timeout)');
291+
// Schedule initial heartbeat if we have an expiry time
292+
if (_sessionExpiresAt != null) {
293+
scheduleHeartbeat(_sessionExpiresAt!);
294+
}
295+
debugLog('[HEARTBEAT] Heartbeat mode enabled');
284296
}
285297

286298
/// Disable heartbeat mode (called when auto mode stops)
@@ -289,32 +301,54 @@ class ApiService {
289301
_gpsProvider = null;
290302
_heartbeatTimer?.cancel();
291303
_heartbeatTimer = null;
292-
debugLog('[API] Heartbeat mode disabled');
304+
debugLog('[HEARTBEAT] Heartbeat mode disabled');
293305
}
294306

295-
/// Reset the heartbeat timer (call after any API activity)
296-
/// Timer fires 3 minutes after last API post to send keepalive
297-
void _resetHeartbeatTimer() {
307+
/// Schedule heartbeat to fire before session expires
308+
/// Matches scheduleHeartbeat() in wardrive.js
309+
/// @param expiresAt Unix timestamp when session expires
310+
void scheduleHeartbeat(int expiresAt) {
311+
// Cancel any existing heartbeat timer
312+
_heartbeatTimer?.cancel();
313+
_heartbeatTimer = null;
314+
298315
if (!_heartbeatEnabled) return;
299316

300-
_heartbeatTimer?.cancel();
301-
_heartbeatTimer = Timer(heartbeatIdleTimeout, () async {
302-
debugLog('[API] Heartbeat timer fired (3 min idle), sending keepalive');
317+
// Calculate when to send heartbeat (1 minute before expiry)
318+
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
319+
final secondsUntilExpiry = expiresAt - now;
320+
final secondsUntilHeartbeat = secondsUntilExpiry - heartbeatBuffer.inSeconds;
303321

304-
// Get GPS coordinates from provider (matching wardrive.js behavior)
305-
final coords = _gpsProvider?.call();
306-
final result = await sendHeartbeat(lat: coords?.lat, lon: coords?.lon);
322+
if (secondsUntilHeartbeat <= 0) {
323+
// Session is about to expire or already expired - send heartbeat immediately
324+
debugWarn('[HEARTBEAT] Session expires in ${secondsUntilExpiry}s, sending immediately');
325+
_sendScheduledHeartbeat();
326+
return;
327+
}
307328

308-
if (result?['success'] == true) {
309-
debugLog('[API] Heartbeat successful');
310-
_resetHeartbeatTimer(); // Schedule next heartbeat
311-
} else {
312-
debugWarn('[API] Heartbeat failed: ${result?['message']}');
313-
_onSessionExpiring?.call();
314-
}
329+
debugLog('[HEARTBEAT] Scheduling in ${secondsUntilHeartbeat}s (session expires in ${secondsUntilExpiry}s)');
330+
331+
_heartbeatTimer = Timer(Duration(seconds: secondsUntilHeartbeat), () {
332+
debugLog('[HEARTBEAT] Timer fired, sending keepalive');
333+
_sendScheduledHeartbeat();
315334
});
316335
}
317336

337+
/// Send scheduled heartbeat with GPS coordinates
338+
Future<void> _sendScheduledHeartbeat() async {
339+
// Get GPS coordinates from provider (matching wardrive.js behavior)
340+
final coords = _gpsProvider?.call();
341+
final result = await sendHeartbeat(lat: coords?.lat, lon: coords?.lon);
342+
343+
if (result?['success'] == true) {
344+
debugLog('[HEARTBEAT] Heartbeat successful');
345+
// Next heartbeat will be scheduled when we get new expires_at
346+
} else {
347+
debugWarn('[HEARTBEAT] Heartbeat failed: ${result?['message']}');
348+
_onSessionExpiring?.call();
349+
}
350+
}
351+
318352
/// Clear session data and cancel heartbeat timer
319353
void _clearSession() {
320354
_sessionId = null;

lib/services/bluetooth/mobile_bluetooth.dart

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ class MobileBluetoothService implements BluetoothService {
4646
StreamSubscription? _notificationSubscription;
4747
StreamSubscription? _scanSubscription;
4848

49+
// Store scanned device info for use in connect()
50+
// This preserves the device name from scan results
51+
final Map<String, DiscoveredDevice> _scannedDevices = {};
52+
4953
@override
5054
Stream<ConnectionStatus> get connectionStream {
5155
_ensureControllers();
@@ -165,13 +169,18 @@ class MobileBluetoothService implements BluetoothService {
165169
// Listen for scan results
166170
_scanSubscription = fbp.FlutterBluePlus.scanResults.listen((results) {
167171
for (final result in results) {
172+
final hasName = result.device.platformName.isNotEmpty;
173+
final deviceName = hasName ? result.device.platformName : 'MeshCore Device';
174+
if (!hasName) {
175+
print('[BLE] WARNING: Device ${result.device.remoteId.str} has no platformName during scan, using fallback "MeshCore Device"');
176+
}
168177
final device = DiscoveredDevice(
169178
id: result.device.remoteId.str,
170-
name: result.device.platformName.isNotEmpty
171-
? result.device.platformName
172-
: 'MeshCore Device',
179+
name: deviceName,
173180
rssi: result.rssi,
174181
);
182+
// Store device info for use in connect() - preserves name from scan
183+
_scannedDevices[device.id] = device;
175184
controller.add(device);
176185
}
177186
});
@@ -308,12 +317,26 @@ class MobileBluetoothService implements BluetoothService {
308317
});
309318
print('[BLE] Notifications enabled');
310319

320+
// Use device name from scan results if available, fallback to platformName
321+
final scannedDevice = _scannedDevices[deviceId];
322+
String deviceName;
323+
if (scannedDevice != null) {
324+
deviceName = scannedDevice.name;
325+
} else if (_bleDevice!.platformName.isNotEmpty) {
326+
deviceName = _bleDevice!.platformName;
327+
} else {
328+
deviceName = 'MeshCore Device';
329+
print('[BLE] WARNING: No device name available for $deviceId during connect - no scan cache, empty platformName. Using fallback "MeshCore Device"');
330+
}
311331
_connectedDevice = DiscoveredDevice(
312332
id: deviceId,
313-
name: _bleDevice!.platformName.isNotEmpty
314-
? _bleDevice!.platformName
315-
: 'MeshCore Device',
333+
name: deviceName,
316334
);
335+
if (deviceName == 'MeshCore Device') {
336+
print('[BLE] WARNING: Connected device name is "MeshCore Device" (from scan: ${scannedDevice != null}, scanName: ${scannedDevice?.name}, platformName: ${_bleDevice!.platformName})');
337+
} else {
338+
print('[BLE] Device name: $deviceName (from scan: ${scannedDevice != null}, platformName: ${_bleDevice!.platformName})');
339+
}
317340

318341
print('[BLE] Connection complete');
319342
_updateStatus(ConnectionStatus.connected);
@@ -358,6 +381,7 @@ class MobileBluetoothService implements BluetoothService {
358381
_bleDevice = null;
359382
_rxCharacteristic = null;
360383
_txCharacteristic = null;
384+
_scannedDevices.clear(); // Clear scan cache on disconnect
361385
_notificationSubscription?.cancel();
362386
_notificationSubscription = null;
363387
_connectionStateSubscription?.cancel();

lib/services/bluetooth/web_bluetooth.dart

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,14 @@ class WebBluetoothService implements BluetoothService {
7979
// Store the device for later connection (avoid requesting twice)
8080
_pendingDevice = device;
8181
debugLog('[BLE] Device selected: ${device.name ?? device.id}');
82-
82+
83+
final deviceName = device.name ?? 'MeshCore Device';
84+
if (device.name == null) {
85+
debugWarn('[BLE] WARNING: Device ${device.id} has no name during scan, using fallback "MeshCore Device"');
86+
}
8387
yield DiscoveredDevice(
8488
id: device.id,
85-
name: device.name ?? 'MeshCore Device',
89+
name: deviceName,
8690
);
8791
}
8892
} catch (e) {
@@ -197,13 +201,21 @@ class WebBluetoothService implements BluetoothService {
197201
throw Exception('Failed to enable BLE notifications: $e');
198202
}
199203

204+
final deviceName = _device!.name ?? 'MeshCore Device';
205+
if (_device!.name == null) {
206+
debugWarn('[BLE] WARNING: Device ${_device!.id} has no name during connect, using fallback "MeshCore Device"');
207+
}
200208
_connectedDevice = DiscoveredDevice(
201209
id: _device!.id,
202-
name: _device!.name ?? 'MeshCore Device',
210+
name: deviceName,
203211
);
204212

205213
_updateStatus(ConnectionStatus.connected);
206-
debugLog('[BLE] Connected successfully!');
214+
if (deviceName == 'MeshCore Device') {
215+
debugWarn('[BLE] WARNING: Connected device name is "MeshCore Device"');
216+
} else {
217+
debugLog('[BLE] Connected successfully as: $deviceName');
218+
}
207219
} catch (e) {
208220
debugError('[BLE] Connection failed: $e');
209221
_updateStatus(ConnectionStatus.error);

0 commit comments

Comments
 (0)