diff --git a/lib/communication/packet_handler.dart b/lib/communication/packet_handler.dart index 1c78c574a..16c10cb54 100644 --- a/lib/communication/packet_handler.dart +++ b/lib/communication/packet_handler.dart @@ -79,10 +79,8 @@ class PacketHandler { return ""; } - Future queryScpiBinary(String command) async { - Uint8List data = - await rust_api.queryScpiBinaryRust(command: command, timeoutMs: 1000); - return data; + Future sendScpiRaw(Uint8List command) async { + rust_api.sendScpiRawRust(command: command); } void sendByte(int val) { @@ -236,4 +234,21 @@ class PacketHandler { _mCommunicationHandler.write(data, _timeout); } } + + Future queryScpiBinary(String command) async { + Uint8List data = + await rust_api.queryScpiBinaryRust(command: command, timeoutMs: 1000); + return data; + } + + void sendScpiRawCmd(Uint8List command) { + rust_api.sendScpiRawRust(command: command); + } + + Future queryScpiBinaryRawCmd(Uint8List command, + {int timeoutMs = 1000}) async { + final result = await rust_api.queryScpiBinaryRawRust( + command: command, timeoutMs: timeoutMs); + return Uint8List.fromList(result); + } } diff --git a/lib/communication/peripherals/i2c.dart b/lib/communication/peripherals/i2c.dart index 12b8ec51d..6142e057c 100644 --- a/lib/communication/peripherals/i2c.dart +++ b/lib/communication/peripherals/i2c.dart @@ -20,12 +20,22 @@ class I2C { buffer = List.filled(10000, 0); commandsProto = CommandsProto(); } - - String _buildScpiBlock(String prefix, List data) { + Uint8List _buildScpiBlockBytes(String prefix, List data, + {String suffix = ""}) { String dataLen = data.length.toString(); String numDigits = dataLen.length.toString(); String header = "$prefix #$numDigits$dataLen"; - return header + String.fromCharCodes(data); + + List cmdBytes = header.codeUnits.toList(); + cmdBytes.addAll(data); + + if (suffix.isNotEmpty) { + cmdBytes.addAll(suffix.codeUnits); + } + + Uint8List finalBytes = Uint8List.fromList(cmdBytes); + logger.i("SCPI COMMAND BUILT -> Raw array: $finalBytes"); + return finalBytes; } Future init() async { @@ -159,11 +169,25 @@ class I2C { int deviceAddress, int registerAddress, int bytesToRead) async { if (PacketHandler.boardType == BoardType.scpi) { await packetHandler.sendScpi("BUS:I2C:CONF:ADDR $deviceAddress"); - String blockCmd = "${_buildScpiBlock("BUS:I2C:TRAN?", [ - registerAddress - ])}, $bytesToRead"; + await Future.delayed(const Duration(milliseconds: 2)); + + String prefix = "BUS:I2C:TRAN? $bytesToRead,"; + Uint8List blockCmd = _buildScpiBlockBytes(prefix, [registerAddress]); + + int attempts = 0; + Uint8List rxData = Uint8List(0); + + while (attempts < 5) { + rxData = await packetHandler.queryScpiBinaryRawCmd(blockCmd); + + if (rxData.length == bytesToRead) { + return rxData.toList(); + } + + attempts++; + await Future.delayed(const Duration(milliseconds: 10)); + } - Uint8List rxData = await packetHandler.queryScpiBinary(blockCmd); return rxData.toList(); } @@ -202,8 +226,9 @@ class I2C { Future writeBulk(int deviceAddress, List data) async { if (PacketHandler.boardType == BoardType.scpi) { await packetHandler.sendScpi("BUS:I2C:CONF:ADDR $deviceAddress"); - String blockCmd = _buildScpiBlock("BUS:I2C:WRIT", data); - await packetHandler.sendScpi(blockCmd); + await Future.delayed(const Duration(milliseconds: 5)); + Uint8List blockCmd = _buildScpiBlockBytes("BUS:I2C:WRIT", data); + packetHandler.sendScpiRawCmd(blockCmd); return; } diff --git a/lib/providers/ads1115_provider.dart b/lib/providers/ads1115_provider.dart index 08c8e68c1..3dd7e73c3 100644 --- a/lib/providers/ads1115_provider.dart +++ b/lib/providers/ads1115_provider.dart @@ -13,6 +13,10 @@ class ADS1115Provider extends ChangeNotifier { ADS1115? _ads1115; Timer? _dataTimer; + + bool _isDisposed = false; + bool _isFetching = false; + double _voltage = 0.0; String _currentGain = "GAIN_ONE"; String _currentChannel = "UNI_0"; @@ -82,7 +86,7 @@ class ADS1115Provider extends ChangeNotifier { _ads1115 = await ADS1115.create(i2c, scienceLab); _updateCurrentSettings(); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error initializing ADS1115: $e'); } @@ -100,7 +104,7 @@ class ADS1115Provider extends ChangeNotifier { if (_ads1115 != null) { _ads1115!.setGain(gain); _currentGain = gain; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } } @@ -108,7 +112,7 @@ class ADS1115Provider extends ChangeNotifier { if (_ads1115 != null) { _ads1115!.setChannel(channel); _currentChannel = channel; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } } @@ -116,7 +120,7 @@ class ADS1115Provider extends ChangeNotifier { if (_ads1115 != null) { _ads1115!.setDataRate(rate); _currentRate = rate; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } } @@ -133,8 +137,13 @@ class ADS1115Provider extends ChangeNotifier { _isRunning = true; _collectedReadings = 0; + _isFetching = false; + _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -147,21 +156,28 @@ class ADS1115Provider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching sensor data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('ADS1115 dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error fetching sensor data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _dataTimer?.cancel(); _dataTimer = null; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchSensorData() async { - if (_ads1115 == null) return; + if (_ads1115 == null || _isDisposed) return; try { final rawData = await _ads1115!.getRawData(); @@ -169,7 +185,7 @@ class ADS1115Provider extends ChangeNotifier { _currentTime += _timegapMs / 1000.0; _addDataPoint(_voltageData, _voltage); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _fetchSensorData: $e'); rethrow; @@ -193,24 +209,23 @@ class ADS1115Provider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int timegapMs) { - if (_timegapMs == timegapMs) { - return; - } + if (_timegapMs == timegapMs) return; + _timegapMs = timegapMs; if (_isRunning) { _stopDataCollection(); _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int numberOfReadings) { _numberOfReadings = numberOfReadings; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -218,7 +233,7 @@ class ADS1115Provider extends ChangeNotifier { _voltage = 0.0; _currentTime = 0.0; _collectedReadings = 0; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } bool get isCollectionComplete { @@ -227,6 +242,7 @@ class ADS1115Provider extends ChangeNotifier { @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/providers/apds9960_provider.dart b/lib/providers/apds9960_provider.dart index 130168562..a9380ff8a 100644 --- a/lib/providers/apds9960_provider.dart +++ b/lib/providers/apds9960_provider.dart @@ -14,6 +14,9 @@ class APDS9960Provider extends ChangeNotifier { APDS9960? _apds9960; Timer? _dataTimer; + bool _isDisposed = false; + bool _isFetching = false; + int _red = 0; int _green = 0; int _blue = 0; @@ -76,7 +79,7 @@ class APDS9960Provider extends ChangeNotifier { } _apds9960 = await APDS9960.create(i2c, scienceLab); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error initializing APDS9960: $e'); } @@ -96,7 +99,7 @@ class APDS9960Provider extends ChangeNotifier { _gestureString = ''; } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } } @@ -113,9 +116,13 @@ class APDS9960Provider extends ChangeNotifier { _isRunning = true; _collectedReadings = 0; + _isFetching = false; _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -128,21 +135,28 @@ class APDS9960Provider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching sensor data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('APDS9960 dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error fetching sensor data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _dataTimer?.cancel(); _dataTimer = null; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchSensorData() async { - if (_apds9960 == null) return; + if (_apds9960 == null || _isDisposed) return; try { final rawData = await _apds9960!.getRawData(_mode); @@ -168,7 +182,7 @@ class APDS9960Provider extends ChangeNotifier { _gestureString = _apds9960!.getGestureString(_gesture); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _fetchSensorData: $e'); rethrow; @@ -194,7 +208,7 @@ class APDS9960Provider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int timegapMs) { @@ -205,12 +219,12 @@ class APDS9960Provider extends ChangeNotifier { _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int numberOfReadings) { _numberOfReadings = numberOfReadings; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -226,7 +240,7 @@ class APDS9960Provider extends ChangeNotifier { _gestureString = ''; _currentTime = 0.0; _collectedReadings = 0; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } bool get isCollectionComplete { @@ -235,6 +249,7 @@ class APDS9960Provider extends ChangeNotifier { @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/providers/bmp180_provider.dart b/lib/providers/bmp180_provider.dart index 5ad6cf67c..75ac00b6a 100644 --- a/lib/providers/bmp180_provider.dart +++ b/lib/providers/bmp180_provider.dart @@ -24,6 +24,8 @@ class BMP180Provider extends ChangeNotifier { bool _isRunning = false; bool _isLooping = false; + bool _isFetching = false; + int _timegapMs = 1000; int _numberOfReadings = 100; int _collectedReadings = 0; @@ -86,9 +88,13 @@ class BMP180Provider extends ChangeNotifier { _isRunning = true; _collectedReadings = 0; + _isFetching = false; _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -101,7 +107,14 @@ class BMP180Provider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching sensor data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('Sensor dropped a frame. Skipping tick gracefully...'); + } else { + logger.e('Error fetching sensor data: $e'); + } + } finally { + _isFetching = false; } }); notifyListeners(); diff --git a/lib/providers/ccs811_provider.dart b/lib/providers/ccs811_provider.dart index 276124d37..5d9daf4b6 100644 --- a/lib/providers/ccs811_provider.dart +++ b/lib/providers/ccs811_provider.dart @@ -14,6 +14,9 @@ class CCS811Provider extends ChangeNotifier { CCS811? _ccs811; Timer? _dataTimer; + bool _isDisposed = false; + bool _isFetching = false; + int _eCO2 = 0; int _tvoc = 0; @@ -62,7 +65,7 @@ class CCS811Provider extends ChangeNotifier { } _ccs811 = await CCS811.create(i2c, scienceLab); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error initializing CCS811: $e'); } @@ -81,9 +84,13 @@ class CCS811Provider extends ChangeNotifier { _isRunning = true; _collectedReadings = 0; + _isFetching = false; _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -96,21 +103,28 @@ class CCS811Provider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching sensor data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('CCS811 dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error fetching sensor data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _dataTimer?.cancel(); _dataTimer = null; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchSensorData() async { - if (_ccs811 == null) return; + if (_ccs811 == null || _isDisposed) return; try { final rawData = await _ccs811!.getRawData(); @@ -123,7 +137,7 @@ class CCS811Provider extends ChangeNotifier { _addDataPoint(_eCO2Data, _eCO2.toDouble()); _addDataPoint(_tvocData, _tvoc.toDouble()); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _fetchSensorData: $e'); rethrow; @@ -149,7 +163,7 @@ class CCS811Provider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int timegapMs) { @@ -160,12 +174,12 @@ class CCS811Provider extends ChangeNotifier { _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int numberOfReadings) { _numberOfReadings = numberOfReadings; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -175,7 +189,7 @@ class CCS811Provider extends ChangeNotifier { _tvoc = 0; _currentTime = 0.0; _collectedReadings = 0; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } bool get isCollectionComplete { @@ -184,6 +198,7 @@ class CCS811Provider extends ChangeNotifier { @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/providers/hmc5883l_provider.dart b/lib/providers/hmc5883l_provider.dart index 415163e82..b50b73fbc 100644 --- a/lib/providers/hmc5883l_provider.dart +++ b/lib/providers/hmc5883l_provider.dart @@ -14,6 +14,9 @@ class HMC5883LProvider extends ChangeNotifier { HMC5883L? _hmc5883l; Timer? _dataTimer; + bool _isDisposed = false; + bool _isFetching = false; + double _bx = 0.0; double _by = 0.0; double _bz = 0.0; @@ -66,7 +69,7 @@ class HMC5883LProvider extends ChangeNotifier { } _hmc5883l = await HMC5883L.create(i2c, scienceLab); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error initializing HMC5883L: $e'); } @@ -85,9 +88,13 @@ class HMC5883LProvider extends ChangeNotifier { _isRunning = true; _collectedReadings = 0; + _isFetching = false; _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -100,21 +107,28 @@ class HMC5883LProvider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching sensor data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('HMC5883L dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error fetching sensor data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _dataTimer?.cancel(); _dataTimer = null; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchSensorData() async { - if (_hmc5883l == null) return; + if (_hmc5883l == null || _isDisposed) return; try { List data = await _hmc5883l!.getRaw(); @@ -129,15 +143,14 @@ class HMC5883LProvider extends ChangeNotifier { _addDataPoint(_byData, _by); _addDataPoint(_bzData, _bz); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _fetchSensorData: $e'); - _stopDataCollection(); + rethrow; } } void _addDataPoint(List dataList, double value) { - // Corrected instantiation format dataList.add(ChartDataPoint(_currentTime, value)); if (dataList.length > 50) { dataList.removeAt(0); @@ -157,7 +170,7 @@ class HMC5883LProvider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int timegapMs) { @@ -168,12 +181,12 @@ class HMC5883LProvider extends ChangeNotifier { _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int numberOfReadings) { _numberOfReadings = numberOfReadings; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -185,7 +198,7 @@ class HMC5883LProvider extends ChangeNotifier { _bz = 0; _currentTime = 0.0; _collectedReadings = 0; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } bool get isCollectionComplete { @@ -194,6 +207,7 @@ class HMC5883LProvider extends ChangeNotifier { @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/providers/max30102_provider.dart b/lib/providers/max30102_provider.dart index bef8395ed..a31e87dde 100644 --- a/lib/providers/max30102_provider.dart +++ b/lib/providers/max30102_provider.dart @@ -4,23 +4,29 @@ import 'package:flutter/material.dart'; import 'package:pslab/communication/peripherals/i2c.dart'; import 'package:pslab/communication/science_lab.dart'; import 'package:pslab/communication/sensors/max30102.dart'; -import 'package:pslab/constants.dart'; import 'package:pslab/others/logger_service.dart'; import 'package:pslab/models/chart_data_points.dart'; class MAX30102Provider extends ChangeNotifier { MAX30102? _sensor; bool _isInitialized = false; + bool _isDisposed = false; bool isRunning = false; bool isLooping = true; + bool _isFetching = false; int _timegapMs = 200; int get timegapMs => _timegapMs < 200 ? 200 : _timegapMs; + static const int internalSamplingsMs = 40; + static const int minSamplesForCalc = 10; + static const double fingerThreshold = 30000.0; + int numberOfReadings = 100; int _currentStep = 0; Timer? _timer; + int _startTimeMs = 0; double _redValue = 0.0; double _irValue = 0.0; @@ -34,23 +40,27 @@ class MAX30102Provider extends ChangeNotifier { List redData = []; List irData = []; - List bpmData = []; List spo2Data = []; + final List _sampleTimestampsMs = []; + + double _beatAvg = 0; + double _spo2Avg = 0; + Future initializeSensors({ required Function(String) onError, I2C? i2c, ScienceLab? scienceLab, }) async { if (i2c == null || scienceLab == null) { - onError(appLocalizations.notConnected); + onError("Not Connected"); return; } try { _sensor = await MAX30102.create(i2c, scienceLab); _isInitialized = true; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e("Error initializing MAX30102: $e"); onError(e.toString()); @@ -69,136 +79,199 @@ class MAX30102Provider extends ChangeNotifier { if (!_isInitialized || _sensor == null) return; isRunning = true; - _timer = Timer.periodic(Duration(milliseconds: timegapMs), (timer) { - _fetchData(); - }); + _isFetching = false; + _startTimeMs = DateTime.now().millisecondsSinceEpoch; + + _beatAvg = 0; + _spo2Avg = 0; + + _timer = Timer.periodic( + const Duration(milliseconds: internalSamplingsMs), + (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + + try { + await _fetchData(); + } catch (e) { + if (e.toString().contains("Expected")) { + logger.w('MAX30102 dropped frame. Skipping gracefully...'); + } else { + logger.e("Error fetching MAX30102 data: $e"); + } + } finally { + _isFetching = false; + } + }, + ); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void stopDataCollection() { isRunning = false; _timer?.cancel(); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchData() async { - try { - var data = await _sensor!.getRawData(); - _redValue = data['red'] ?? 0.0; - _irValue = data['ir'] ?? 0.0; + if (_isDisposed) return; - if (_redValue >= 262140 || _irValue >= 262140) { - return; - } + var data = await _sensor!.getRawData(); + _redValue = (data['red'] ?? 0.0).toDouble(); + _irValue = (data['ir'] ?? 0.0).toDouble(); - redData.add(ChartDataPoint(_currentStep.toDouble(), _redValue)); - irData.add(ChartDataPoint(_currentStep.toDouble(), _irValue)); + if (_redValue >= 262140 || _irValue >= 262140) return; - if (redData.length > numberOfReadings) { - redData.removeAt(0); - irData.removeAt(0); - } + int nowMs = DateTime.now().millisecondsSinceEpoch; + double currentTimeSec = (nowMs - _startTimeMs) / 1000.0; + + if (_irValue < fingerThreshold) { + _calculatedBPM = 0; + _calculatedSpO2 = 0; + _beatAvg = 0; + _spo2Avg = 0; + _sampleTimestampsMs.clear(); + redData.clear(); + irData.clear(); + if (!_isDisposed) notifyListeners(); + return; + } - _calculateMetrics(); + redData.add(ChartDataPoint(currentTimeSec, _redValue)); + irData.add(ChartDataPoint(currentTimeSec, _irValue)); + _sampleTimestampsMs.add(nowMs); - bpmData.add( - ChartDataPoint(_currentStep.toDouble(), _calculatedBPM.toDouble())); - spo2Data.add( - ChartDataPoint(_currentStep.toDouble(), _calculatedSpO2.toDouble())); + if (redData.length > numberOfReadings) { + redData.removeAt(0); + irData.removeAt(0); + _sampleTimestampsMs.removeAt(0); + } + if (irData.length >= minSamplesForCalc) { + _calculateSpO2AndWindowBPM(); + } - if (bpmData.length > numberOfReadings) { - bpmData.removeAt(0); - spo2Data.removeAt(0); - } + bpmData.add(ChartDataPoint(currentTimeSec, _calculatedBPM.toDouble())); + spo2Data.add(ChartDataPoint(currentTimeSec, _calculatedSpO2.toDouble())); - _currentStep++; + if (bpmData.length > numberOfReadings) { + bpmData.removeAt(0); + spo2Data.removeAt(0); + } - if (!isLooping && _currentStep >= numberOfReadings) { - stopDataCollection(); - } + _currentStep++; - notifyListeners(); - } catch (e) { - logger.e("Error fetching MAX30102 data: $e"); + if (!isLooping && _currentStep >= numberOfReadings) { + stopDataCollection(); } + + if (!_isDisposed) notifyListeners(); } - void _calculateMetrics() { - int requiredSamples = numberOfReadings ~/ 4; - if (irData.length < requiredSamples) { - logger.d( - "Buffer filling: ${irData.length}/$requiredSamples before math starts."); - return; - } + void _calculateSpO2AndWindowBPM() { + final int windowSize = min(irData.length, 30); + final int startIdx = irData.length - windowSize; - if (_irValue < 50000) { - logger.d(" No finger detected. IR Value: ${_irValue.toInt()}"); - _calculatedBPM = 0; - _calculatedSpO2 = 0; - return; + double dcRed = 0.0; + double dcIr = 0.0; + for (int i = startIdx; i < irData.length; i++) { + dcRed += redData[i].y; + dcIr += irData[i].y; + } + dcRed /= windowSize; + dcIr /= windowSize; + + double acRedSq = 0.0; + double acIrSq = 0.0; + for (int i = startIdx; i < irData.length; i++) { + double rDiff = redData[i].y - dcRed; + double irDiff = irData[i].y - dcIr; + acRedSq += rDiff * rDiff; + acIrSq += irDiff * irDiff; } - try { - double dcRed = - redData.map((e) => e.y).reduce((a, b) => a + b) / redData.length; - double dcIr = - irData.map((e) => e.y).reduce((a, b) => a + b) / irData.length; - - double maxRed = redData.map((e) => e.y).reduce(max); - double minRed = redData.map((e) => e.y).reduce(min); - double acRed = maxRed - minRed; - - double maxIr = irData.map((e) => e.y).reduce(max); - double minIr = irData.map((e) => e.y).reduce(min); - double acIr = maxIr - minIr; - - if (dcRed > 0 && dcIr > 0 && acRed > 0 && acIr > 0) { - double ratio = (acRed / dcRed) / (acIr / dcIr); - double spo2 = 110.0 - (25.0 * ratio); - _calculatedSpO2 = spo2.clamp(70.0, 99.0).toInt(); + double acRed = sqrt(acRedSq / windowSize); + double acIr = sqrt(acIrSq / windowSize); + + if (dcRed <= 0 || dcIr <= 0 || acRed <= 0 || acIr <= 0) return; + + double r = (acRed / dcRed) / (acIr / dcIr); + double spo2 = -45.060 * r * r + 30.354 * r + 94.845; + int newSpO2 = spo2.clamp(70.0, 100.0).round(); + + _spo2Avg = + (_spo2Avg == 0) ? newSpO2.toDouble() : (_spo2Avg * 0.7 + newSpO2 * 0.3); + _calculatedSpO2 = _spo2Avg.round(); + final List detrended = List.filled(windowSize, 0.0); + for (int i = 0; i < windowSize; i++) { + int left = max(0, i - 2); + int right = min(windowSize - 1, i + 2); + double localSum = 0.0; + for (int j = left; j <= right; j++) { + localSum += irData[startIdx + j].y; } + double localMean = localSum / (right - left + 1); + detrended[i] = -(irData[startIdx + i].y - localMean); + } - int peakCount = 0; - int lastPeakIndex = -1; - - for (int i = 1; i < irData.length - 1; i++) { - if (irData[i].y > irData[i - 1].y && irData[i].y > irData[i + 1].y) { - if (lastPeakIndex == -1 || (i - lastPeakIndex) >= 2) { - peakCount++; - lastPeakIndex = i; - } + final List peakIndices = []; + for (int i = 1; i < windowSize - 1; i++) { + if (detrended[i] > 0 && + detrended[i] > detrended[i - 1] && + detrended[i] >= detrended[i + 1]) { + peakIndices.add(i); + } + } + if (peakIndices.length < 2) { + peakIndices.clear(); + for (int i = 1; i < windowSize; i++) { + if (detrended[i - 1] <= 0 && detrended[i] > 0) { + peakIndices.add(i); } } + } - double timeWindowMinutes = (irData.length * timegapMs) / 60000.0; - int newBpm = 0; - if (timeWindowMinutes > 0) { - newBpm = (peakCount / timeWindowMinutes).toInt(); + if (peakIndices.length >= 2) { + int firstSampleIdx = startIdx + peakIndices.first; + int lastSampleIdx = startIdx + peakIndices.last; - if (newBpm >= 40 && newBpm <= 180) { - _calculatedBPM = newBpm; - logger.i("BPM ACCEPTED: $_calculatedBPM"); + int totalTimeMs = _sampleTimestampsMs[lastSampleIdx] - + _sampleTimestampsMs[firstSampleIdx]; + int numIntervals = peakIndices.length - 1; + + if (totalTimeMs > 0 && numIntervals > 0) { + double avgPeakIntervalMs = totalTimeMs / numIntervals; + double rawBpm = 60000.0 / avgPeakIntervalMs; + while (rawBpm > 10.0 && rawBpm < 55.0) { + rawBpm *= 2.0; + } + while (rawBpm > 165.0) { + rawBpm /= 2.0; } + + double validBpm = rawBpm.clamp(50.0, 160.0); + _beatAvg = + (_beatAvg == 0) ? validBpm : (_beatAvg * 0.7 + validBpm * 0.3); + _calculatedBPM = _beatAvg.round(); + logger.i( + "BPM Calculated: $_calculatedBPM (rawpeaks: ${peakIndices.length})"); } - } catch (e) { - logger.e("DSP Error: $e"); } } void toggleLooping() { isLooping = !isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int gap) { _timegapMs = gap < 200 ? 200 : gap; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int num) { numberOfReadings = num; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -206,16 +279,21 @@ class MAX30102Provider extends ChangeNotifier { irData.clear(); bpmData.clear(); spo2Data.clear(); + _sampleTimestampsMs.clear(); _currentStep = 0; _redValue = 0.0; _irValue = 0.0; _calculatedBPM = 0; _calculatedSpO2 = 0; - notifyListeners(); + _beatAvg = 0; + _spo2Avg = 0; + + if (!_isDisposed) notifyListeners(); } @override void dispose() { + _isDisposed = true; _timer?.cancel(); super.dispose(); } diff --git a/lib/providers/mlx90614_provider.dart b/lib/providers/mlx90614_provider.dart index 0181a8fc9..c850555c0 100644 --- a/lib/providers/mlx90614_provider.dart +++ b/lib/providers/mlx90614_provider.dart @@ -14,6 +14,9 @@ class MLX90614Provider extends ChangeNotifier { MLX90614? _mlx90614; Timer? _dataTimer; + bool _isDisposed = false; + bool _isFetching = false; + double _objectTemperature = 0.0; double _ambientTemperature = 0.0; @@ -62,7 +65,7 @@ class MLX90614Provider extends ChangeNotifier { } _mlx90614 = await MLX90614.create(i2c, scienceLab); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error initializing MLX90614: $e'); } @@ -81,9 +84,13 @@ class MLX90614Provider extends ChangeNotifier { _isRunning = true; _collectedReadings = 0; + _isFetching = false; _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -96,21 +103,28 @@ class MLX90614Provider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching sensor data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('MLX90614 dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error fetching sensor data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _dataTimer?.cancel(); _dataTimer = null; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchSensorData() async { - if (_mlx90614 == null) return; + if (_mlx90614 == null || _isDisposed) return; try { final rawData = await _mlx90614!.getRawData(); @@ -123,7 +137,7 @@ class MLX90614Provider extends ChangeNotifier { _addDataPoint(_objectTemperatureData, _objectTemperature); _addDataPoint(_ambientTemperatureData, _ambientTemperature); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _fetchSensorData: $e'); rethrow; @@ -149,7 +163,7 @@ class MLX90614Provider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int timegapMs) { @@ -160,12 +174,12 @@ class MLX90614Provider extends ChangeNotifier { _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int numberOfReadings) { _numberOfReadings = numberOfReadings; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -175,7 +189,7 @@ class MLX90614Provider extends ChangeNotifier { _ambientTemperature = 0.0; _currentTime = 0.0; _collectedReadings = 0; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } bool get isCollectionComplete { @@ -184,6 +198,7 @@ class MLX90614Provider extends ChangeNotifier { @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/providers/mpu6050_state_provider.dart b/lib/providers/mpu6050_state_provider.dart index 54790441b..12e50cff1 100644 --- a/lib/providers/mpu6050_state_provider.dart +++ b/lib/providers/mpu6050_state_provider.dart @@ -10,6 +10,9 @@ class MPU6050Provider extends ChangeNotifier { MPU6050? _mpu6050; Timer? _dataTimer; + bool _isDisposed = false; + bool _isFetching = false; + Map _currentValues = { 'ax': 0.0, 'ay': 0.0, @@ -36,7 +39,7 @@ class MPU6050Provider extends ChangeNotifier { int _selectedAccelRange = 16; int _selectedGyroRange = 2000; - double? _selectedFilter; // null means OFF + double? _selectedFilter; final String _selectedHighPassFilter = 'OFF'; Map get currentValues => _currentValues; @@ -68,7 +71,7 @@ class MPU6050Provider extends ChangeNotifier { return; } _mpu6050 = await MPU6050.create(i2c, scienceLab); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error initializing MPU6050: $e'); } @@ -77,13 +80,13 @@ class MPU6050Provider extends ChangeNotifier { Future updateAccelRange(int range) async { _selectedAccelRange = range; await _mpu6050?.setAccelerationRange(range); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future updateGyroRange(int range) async { _selectedGyroRange = range; await _mpu6050?.setGyroRange(range); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void toggleDataCollection() { @@ -94,9 +97,13 @@ class MPU6050Provider extends ChangeNotifier { if (_mpu6050 == null) return; _isRunning = true; _collectedReadings = 0; + _isFetching = false; _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -108,20 +115,27 @@ class MPU6050Provider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching MPU6050 data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('MPU6050 dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error fetching MPU6050 data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _dataTimer?.cancel(); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchSensorData() async { - if (_mpu6050 == null) return; + if (_mpu6050 == null || _isDisposed) return; try { _currentValues = await _mpu6050!.getRawData(); _currentTime += _timegapMs / 1000.0; @@ -133,9 +147,10 @@ class MPU6050Provider extends ChangeNotifier { _addDataPoint(_gyData, _currentValues['gy']!); _addDataPoint(_gzData, _currentValues['gz']!); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _fetchSensorData: $e'); + rethrow; } } @@ -156,7 +171,7 @@ class MPU6050Provider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int ms) { @@ -165,12 +180,12 @@ class MPU6050Provider extends ChangeNotifier { _stopDataCollection(); _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int val) { _numberOfReadings = val; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -183,11 +198,12 @@ class MPU6050Provider extends ChangeNotifier { _currentTime = 0.0; _collectedReadings = 0; _currentValues.updateAll((key, value) => 0.0); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/providers/mpu925x_provider.dart b/lib/providers/mpu925x_provider.dart index 3d60f39c9..23e34397f 100644 --- a/lib/providers/mpu925x_provider.dart +++ b/lib/providers/mpu925x_provider.dart @@ -10,6 +10,9 @@ class MPU925XProvider extends ChangeNotifier { MPU925X? _mpu925x; Timer? _dataTimer; + bool _isDisposed = false; + bool _isFetching = false; + final Map _currentValues = { 'ax': 0.0, 'ay': 0.0, @@ -77,7 +80,7 @@ class MPU925XProvider extends ChangeNotifier { return; } _mpu925x = await MPU925X.create(i2c, scienceLab); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error initializing MPU925X: $e'); } @@ -86,13 +89,13 @@ class MPU925XProvider extends ChangeNotifier { Future updateAccelRange(int range) async { _selectedAccelRange = range; await _mpu925x?.setAccelerationRange(range); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future updateGyroRange(int range) async { _selectedGyroRange = range; await _mpu925x?.setGyroRange(range); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void toggleDataCollection() { @@ -103,9 +106,13 @@ class MPU925XProvider extends ChangeNotifier { if (_mpu925x == null) return; _isRunning = true; _collectedReadings = 0; + _isFetching = false; _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -117,20 +124,27 @@ class MPU925XProvider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching MPU925X data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('MPU925X dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error fetching MPU925X data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _dataTimer?.cancel(); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchSensorData() async { - if (_mpu925x == null) return; + if (_mpu925x == null || _isDisposed) return; try { final rawData = await _mpu925x!.getRawData(); final magData = await _mpu925x!.getMagneticField(); @@ -157,9 +171,10 @@ class MPU925XProvider extends ChangeNotifier { _addDataPoint(_myData, _currentValues['my']!); _addDataPoint(_mzData, _currentValues['mz']!); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _fetchSensorData: $e'); + rethrow; } } @@ -185,7 +200,7 @@ class MPU925XProvider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int ms) { @@ -194,12 +209,12 @@ class MPU925XProvider extends ChangeNotifier { _stopDataCollection(); _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int val) { _numberOfReadings = val; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -215,11 +230,12 @@ class MPU925XProvider extends ChangeNotifier { _currentTime = 0.0; _collectedReadings = 0; _currentValues.updateAll((key, value) => 0.0); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/providers/sht21_provider.dart b/lib/providers/sht21_provider.dart index 628ff90af..f0e364d8f 100644 --- a/lib/providers/sht21_provider.dart +++ b/lib/providers/sht21_provider.dart @@ -16,6 +16,9 @@ class SHT21Provider extends ChangeNotifier { bool _isSensorAvailable = false; bool _isInitialized = false; + bool _isDisposed = false; + bool _isFetching = false; + bool isRunning = false; bool isLooping = false; int timegapMs = 500; @@ -27,7 +30,7 @@ class SHT21Provider extends ChangeNotifier { final List _timeData = []; final List _tempRawData = []; final List _humidityRawData = []; - bool _isFetching = false; + final List tempChartData = []; final List humidityChartData = []; double _startTime = 0; @@ -51,7 +54,7 @@ class SHT21Provider extends ChangeNotifier { _isSensorAvailable = true; _isInitialized = true; logger.d("$_tag: SHT21 initialized successfully!"); - notifyListeners(); + if (!_isDisposed) notifyListeners(); return; } } @@ -59,7 +62,7 @@ class SHT21Provider extends ChangeNotifier { _isSensorAvailable = false; _isInitialized = true; onError("SHT21 Sensor not found."); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void toggleDataCollection() { @@ -72,7 +75,7 @@ class SHT21Provider extends ChangeNotifier { void toggleLooping() { isLooping = !isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int newTimegap) { @@ -81,12 +84,12 @@ class SHT21Provider extends ChangeNotifier { _stopReading(); _startReading(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int readings) { numberOfReadings = readings; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -97,13 +100,15 @@ class SHT21Provider extends ChangeNotifier { humidityChartData.clear(); _currentTemp = 0.0; _currentHumidity = 0.0; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _startReading() { if (!_isSensorAvailable || _sensor == null) return; isRunning = true; + _isFetching = false; + if (_timeData.isEmpty) { _startTime = DateTime.now().millisecondsSinceEpoch / 1000.0; } else { @@ -115,36 +120,40 @@ class SHT21Provider extends ChangeNotifier { _readTimer?.cancel(); _readTimer = Timer.periodic(Duration(milliseconds: timegapMs), (timer) async { - if (!isRunning) return; - - if (_isFetching) return; + if (!isRunning || _isFetching || _isDisposed) return; _isFetching = true; try { _currentHumidity = await _sensor!.getHumidity(); await Future.delayed(const Duration(milliseconds: 250)); + if (_isDisposed) return; _currentTemp = await _sensor!.getTemperature(); _updateChartData(); - notifyListeners(); + if (!_isDisposed) notifyListeners(); if (!isLooping && tempChartData.length >= numberOfReadings) { _stopReading(); } } catch (e) { - logger.e("$_tag: Error reading sensor data: $e"); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('$_tag dropped a frame. Skipping gracefully...'); + } else { + logger.e("$_tag: Error reading sensor data: $e"); + } } finally { _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopReading() { isRunning = false; _readTimer?.cancel(); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _updateChartData() { @@ -173,6 +182,7 @@ class SHT21Provider extends ChangeNotifier { @override void dispose() { + _isDisposed = true; _stopReading(); super.dispose(); } diff --git a/lib/providers/tsl2561_provider.dart b/lib/providers/tsl2561_provider.dart index ace6f559c..3d1a4e764 100644 --- a/lib/providers/tsl2561_provider.dart +++ b/lib/providers/tsl2561_provider.dart @@ -14,6 +14,9 @@ class TSL2561Provider extends ChangeNotifier { TSL2561? _tsl2561; Timer? _dataTimer; + bool _isDisposed = false; + bool _isFetching = false; + double _fullSpectrum = 0.0; double _infrared = 0.0; double _visible = 0.0; @@ -66,7 +69,7 @@ class TSL2561Provider extends ChangeNotifier { } _tsl2561 = await TSL2561.create(i2c, scienceLab); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error initializing TSL2561: $e'); } @@ -85,9 +88,13 @@ class TSL2561Provider extends ChangeNotifier { _isRunning = true; _collectedReadings = 0; + _isFetching = false; _dataTimer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _fetchSensorData(); _collectedReadings++; @@ -100,21 +107,28 @@ class TSL2561Provider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error fetching sensor data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('TSL2561 dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error fetching sensor data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _dataTimer?.cancel(); _dataTimer = null; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _fetchSensorData() async { - if (_tsl2561 == null) return; + if (_tsl2561 == null || _isDisposed) return; try { final rawData = await _tsl2561!.getRawData(); @@ -129,7 +143,7 @@ class TSL2561Provider extends ChangeNotifier { _addDataPoint(_infraredData, _infrared); _addDataPoint(_visibleData, _visible); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _fetchSensorData: $e'); rethrow; @@ -156,7 +170,7 @@ class TSL2561Provider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int timegapMs) { @@ -165,12 +179,12 @@ class TSL2561Provider extends ChangeNotifier { _stopDataCollection(); _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int numberOfReadings) { _numberOfReadings = numberOfReadings; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -182,7 +196,7 @@ class TSL2561Provider extends ChangeNotifier { _visible = 0; _currentTime = 0.0; _collectedReadings = 0; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } bool get isCollectionComplete { @@ -191,6 +205,7 @@ class TSL2561Provider extends ChangeNotifier { @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/providers/vl53l0x_provider.dart b/lib/providers/vl53l0x_provider.dart index c5d9df545..c4c565867 100644 --- a/lib/providers/vl53l0x_provider.dart +++ b/lib/providers/vl53l0x_provider.dart @@ -15,6 +15,9 @@ class VL53L0XProvider extends ChangeNotifier { VL53L0X? _sensor; Timer? _timer; + bool _isDisposed = false; + bool _isFetching = false; + bool _isRunning = false; bool _isLooping = false; int _timegapMs = 500; @@ -69,8 +72,12 @@ class VL53L0XProvider extends ChangeNotifier { _isRunning = true; _collectedReadings = 0; + _isFetching = false; _timer = Timer.periodic(Duration(milliseconds: _timegapMs), (timer) async { + if (_isFetching || _isDisposed) return; + _isFetching = true; + try { await _collectData(); _collectedReadings++; @@ -83,21 +90,28 @@ class VL53L0XProvider extends ChangeNotifier { _removeOldestDataPoints(); } } catch (e) { - logger.e('Error collecting VL53L0X data: $e'); + String errorMsg = e.toString(); + if (errorMsg.contains("Expected")) { + logger.w('VL53L0X dropped a frame. Skipping gracefully...'); + } else { + logger.e('Error collecting VL53L0X data: $e'); + } + } finally { + _isFetching = false; } }); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void _stopDataCollection() { _isRunning = false; _timer?.cancel(); _timer = null; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } Future _collectData() async { - if (_sensor == null) return; + if (_sensor == null || _isDisposed) return; try { double newDistance = await _sensor!.getDistance(); @@ -107,7 +121,7 @@ class VL53L0XProvider extends ChangeNotifier { _addDataPoint(_distanceData, _distance); - notifyListeners(); + if (!_isDisposed) notifyListeners(); } catch (e) { logger.e('Error in _collectData: $e'); rethrow; @@ -131,7 +145,7 @@ class VL53L0XProvider extends ChangeNotifier { void toggleLooping() { _isLooping = !_isLooping; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setTimegap(int newTimegap) { @@ -142,12 +156,12 @@ class VL53L0XProvider extends ChangeNotifier { _startDataCollection(); } - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void setNumberOfReadings(int newNumber) { _numberOfReadings = newNumber; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } void clearData() { @@ -155,7 +169,7 @@ class VL53L0XProvider extends ChangeNotifier { _distance = 0.0; _currentTime = 0.0; _collectedReadings = 0; - notifyListeners(); + if (!_isDisposed) notifyListeners(); } bool get isCollectionComplete { @@ -164,6 +178,7 @@ class VL53L0XProvider extends ChangeNotifier { @override void dispose() { + _isDisposed = true; _stopDataCollection(); super.dispose(); } diff --git a/lib/src/rust/api/simple.dart b/lib/src/rust/api/simple.dart index 55a4ded87..e3ff2fbfa 100644 --- a/lib/src/rust/api/simple.dart +++ b/lib/src/rust/api/simple.dart @@ -71,3 +71,11 @@ Future queryScpiBinaryRust( {required String command, required int timeoutMs}) => RustLib.instance.api.crateApiSimpleQueryScpiBinaryRust( command: command, timeoutMs: timeoutMs); + +void sendScpiRawRust({required List command}) => + RustLib.instance.api.crateApiSimpleSendScpiRawRust(command: command); + +Future queryScpiBinaryRawRust( + {required List command, required int timeoutMs}) => + RustLib.instance.api.crateApiSimpleQueryScpiBinaryRawRust( + command: command, timeoutMs: timeoutMs); diff --git a/lib/src/rust/frb_generated.dart b/lib/src/rust/frb_generated.dart index cb6ff7869..543c65e53 100644 --- a/lib/src/rust/frb_generated.dart +++ b/lib/src/rust/frb_generated.dart @@ -70,7 +70,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.13.0'; @override - int get rustContentHash => 1058181045; + int get rustContentHash => 641606677; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -100,6 +100,9 @@ abstract class RustLibApi extends BaseApi { void crateApiSimplePushWebData({required List data}); + Future crateApiSimpleQueryScpiBinaryRawRust( + {required List command, required int timeoutMs}); + Future crateApiSimpleQueryScpiBinaryRust( {required String command, required int timeoutMs}); @@ -108,6 +111,8 @@ abstract class RustLibApi extends BaseApi { Uint8List crateApiSimpleReadWebData({required int bytesToRead}); + void crateApiSimpleSendScpiRawRust({required List command}); + void crateApiSimpleSendScpiRust({required String command}); void crateApiSimpleSetBaudRate({required int baudRate}); @@ -356,6 +361,33 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["data"], ); + @override + Future crateApiSimpleQueryScpiBinaryRawRust( + {required List command, required int timeoutMs}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(command, serializer); + sse_encode_u_32(timeoutMs, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 10, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: null, + ), + constMeta: kCrateApiSimpleQueryScpiBinaryRawRustConstMeta, + argValues: [command, timeoutMs], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiSimpleQueryScpiBinaryRawRustConstMeta => + const TaskConstMeta( + debugName: "query_scpi_binary_raw_rust", + argNames: ["command", "timeoutMs"], + ); + @override Future crateApiSimpleQueryScpiBinaryRust( {required String command, required int timeoutMs}) { @@ -365,7 +397,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(command, serializer); sse_encode_u_32(timeoutMs, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 10, port: port_); + funcId: 11, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_list_prim_u_8_strict, @@ -392,7 +424,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_32(bytesToRead, serializer); sse_encode_u_32(timeoutMs, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 11, port: port_); + funcId: 12, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_list_prim_u_8_strict, @@ -415,7 +447,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_u_32(bytesToRead, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 12)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!; }, codec: SseCodec( decodeSuccessData: sse_decode_list_prim_u_8_strict, @@ -432,13 +464,37 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["bytesToRead"], ); + @override + void crateApiSimpleSendScpiRawRust({required List command}) { + return handler.executeSync(SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(command, serializer); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!; + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiSimpleSendScpiRawRustConstMeta, + argValues: [command], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiSimpleSendScpiRawRustConstMeta => + const TaskConstMeta( + debugName: "send_scpi_raw_rust", + argNames: ["command"], + ); + @override void crateApiSimpleSendScpiRust({required String command}) { return handler.executeSync(SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(command, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 15)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -461,7 +517,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_u_32(baudRate, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -484,7 +540,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_bool(state, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 15)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -507,7 +563,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_bool(state, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 18)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -532,7 +588,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_StreamSink_list_prim_f_64_strict_Sse(sink, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 17, port: port_); + funcId: 19, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -557,7 +613,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 18, port: port_); + funcId: 20, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -584,7 +640,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(host, serializer); sse_encode_u_16(port, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 19, port: port_); + funcId: 21, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -606,7 +662,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return handler.executeSync(SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 20)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 22)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -633,7 +689,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_32(bytesToRead, serializer); sse_encode_u_32(timeoutMs, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 21, port: port_); + funcId: 23, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_list_prim_u_8_strict, @@ -656,7 +712,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(data, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 22)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 24)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -679,7 +735,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(data, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 23)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, diff --git a/rust/src/api/simple.rs b/rust/src/api/simple.rs index f6b9a59a7..3fe29a291 100644 --- a/rust/src/api/simple.rs +++ b/rust/src/api/simple.rs @@ -751,15 +751,54 @@ pub fn send_scpi_rust(command: String) { } pub fn query_scpi_binary_rust(command: String, timeout_ms: u32) -> Vec { - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] - if let Some(port) = SERIAL_PORT.lock().unwrap().as_mut() { - let _ = port.clear(serialport::ClearBuffer::Input); + send_scpi_rust(command); + + let mut raw_buffer = Vec::new(); + let start_time = std::time::Instant::now(); + let timeout = std::time::Duration::from_millis(timeout_ms as u64); + + while start_time.elapsed() < timeout { + let chunk = read_data(2048, 10); + + if !chunk.is_empty() { + raw_buffer.extend_from_slice(&chunk); + if let Some(hash_idx) = raw_buffer.iter().position(|&x| x == b'#') { + if hash_idx > 0 { + raw_buffer.drain(0..hash_idx); + } + + if raw_buffer.len() > 2 { + if let Some(num_digits) = (raw_buffer[1] as char).to_digit(10).map(|d| d as usize) { + let header_len = 2 + num_digits; + if raw_buffer.len() >= header_len { + if let Ok(len_str) = std::str::from_utf8(&raw_buffer[2..header_len]) { + if let Ok(data_len) = len_str.parse::() { + let total_frame_len = header_len + data_len; + + if raw_buffer.len() >= total_frame_len { + return raw_buffer[header_len..total_frame_len].to_vec(); + } + } + } + } + } + } + } + } } - #[cfg(target_os = "android")] - if let Ok(mut buffer) = ANDROID_RX_BUFFER.lock() { buffer.clear(); } - std::thread::sleep(std::time::Duration::from_millis(50)); - send_scpi_rust(command); + vec![] +} + +#[frb(sync)] +pub fn send_scpi_raw_rust(mut command: Vec) { + command.push(b'\r'); + command.push(b'\n'); + write_data(command); +} + +pub fn query_scpi_binary_raw_rust(mut command: Vec, timeout_ms: u32) -> Vec { + send_scpi_raw_rust(command); let mut raw_buffer = Vec::new(); let start_time = std::time::Instant::now(); diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index c0466ba50..967b513e5 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.13.0"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1058181045; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 641606677; // Section: executor @@ -343,6 +343,43 @@ fn wire__crate__api__simple__push_web_data_impl( }, ) } +fn wire__crate__api__simple__query_scpi_binary_raw_rust_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "query_scpi_binary_raw_rust", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_command = >::sse_decode(&mut deserializer); + let api_timeout_ms = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Ok::<_, ()>(crate::api::simple::query_scpi_binary_raw_rust( + api_command, + api_timeout_ms, + ))?; + std::result::Result::Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__simple__query_scpi_binary_rust_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -447,6 +484,38 @@ fn wire__crate__api__simple__read_web_data_impl( }, ) } +fn wire__crate__api__simple__send_scpi_raw_rust_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "send_scpi_raw_rust", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_command = >::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let output_ok = Ok::<_, ()>({ + crate::api::simple::send_scpi_raw_rust(api_command); + })?; + std::result::Result::Ok(output_ok) + })()) + }, + ) +} fn wire__crate__api__simple__send_scpi_rust_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -984,14 +1053,20 @@ fn pde_ffi_dispatcher_primary_impl( 5 => wire__crate__api__simple__init_android_impl(port, ptr, rust_vec_len, data_len), 6 => wire__crate__api__simple__init_desktop_impl(port, ptr, rust_vec_len, data_len), 7 => wire__crate__api__simple__init_desktop_by_port_impl(port, ptr, rust_vec_len, data_len), - 10 => { + 10 => wire__crate__api__simple__query_scpi_binary_raw_rust_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 11 => { wire__crate__api__simple__query_scpi_binary_rust_impl(port, ptr, rust_vec_len, data_len) } - 11 => wire__crate__api__simple__read_data_impl(port, ptr, rust_vec_len, data_len), - 17 => wire__crate__api__audio__start_microphone_impl(port, ptr, rust_vec_len, data_len), - 18 => wire__crate__api__audio__stop_microphone_impl(port, ptr, rust_vec_len, data_len), - 19 => wire__crate__api__simple__wifi_connect_impl(port, ptr, rust_vec_len, data_len), - 21 => wire__crate__api__simple__wifi_read_impl(port, ptr, rust_vec_len, data_len), + 12 => wire__crate__api__simple__read_data_impl(port, ptr, rust_vec_len, data_len), + 19 => wire__crate__api__audio__start_microphone_impl(port, ptr, rust_vec_len, data_len), + 20 => wire__crate__api__audio__stop_microphone_impl(port, ptr, rust_vec_len, data_len), + 21 => wire__crate__api__simple__wifi_connect_impl(port, ptr, rust_vec_len, data_len), + 23 => wire__crate__api__simple__wifi_read_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -1011,14 +1086,15 @@ fn pde_ffi_dispatcher_sync_impl( 4 => wire__crate__api__simple__get_available_ports_impl(ptr, rust_vec_len, data_len), 8 => wire__crate__api__simple__pop_web_tx_data_impl(ptr, rust_vec_len, data_len), 9 => wire__crate__api__simple__push_web_data_impl(ptr, rust_vec_len, data_len), - 12 => wire__crate__api__simple__read_web_data_impl(ptr, rust_vec_len, data_len), - 13 => wire__crate__api__simple__send_scpi_rust_impl(ptr, rust_vec_len, data_len), - 14 => wire__crate__api__simple__set_baud_rate_impl(ptr, rust_vec_len, data_len), - 15 => wire__crate__api__simple__set_dtr_impl(ptr, rust_vec_len, data_len), - 16 => wire__crate__api__simple__set_rts_impl(ptr, rust_vec_len, data_len), - 20 => wire__crate__api__simple__wifi_disconnect_impl(ptr, rust_vec_len, data_len), - 22 => wire__crate__api__simple__wifi_write_impl(ptr, rust_vec_len, data_len), - 23 => wire__crate__api__simple__write_data_impl(ptr, rust_vec_len, data_len), + 13 => wire__crate__api__simple__read_web_data_impl(ptr, rust_vec_len, data_len), + 14 => wire__crate__api__simple__send_scpi_raw_rust_impl(ptr, rust_vec_len, data_len), + 15 => wire__crate__api__simple__send_scpi_rust_impl(ptr, rust_vec_len, data_len), + 16 => wire__crate__api__simple__set_baud_rate_impl(ptr, rust_vec_len, data_len), + 17 => wire__crate__api__simple__set_dtr_impl(ptr, rust_vec_len, data_len), + 18 => wire__crate__api__simple__set_rts_impl(ptr, rust_vec_len, data_len), + 22 => wire__crate__api__simple__wifi_disconnect_impl(ptr, rust_vec_len, data_len), + 24 => wire__crate__api__simple__wifi_write_impl(ptr, rust_vec_len, data_len), + 25 => wire__crate__api__simple__write_data_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } }