edufolly / flutter_bluetooth_serial

A basic Flutter Bluetooth Serial
MIT License
479 stars 465 forks source link

Data Skipping when the transfer rate is high #194

Closed Dracula-101 closed 1 year ago

Dracula-101 commented 1 year ago

I am working on health-related device which relies on getting the data from a piezoelectric sensor and transmitting the data through Arduino using the hc 05 module. The data transfer rate is exceptionally high (about 20 ms intervals) and I am getting values that have some skipped digits. I have made a wrapper class for sending and receiving data and also using special delimiters as data already to eliminate data arriving randomly.

Here is the methods of Bluetooth Class, I have used Provider for state management

class BluetoothProvider extends ChangeNotifier {

  BluetoothState bluetoothState = BluetoothState.UNKNOWN;
  FlutterBluetoothSerial bluetooth = FlutterBluetoothSerial.instance;
  BluetoothConnection? connection;
  bool isDisconnecting = false;
  StreamSubscription<Uint8List>? dataStreamSubscription;
  StreamController<DataPoint?> dataPointStream = StreamController.broadcast();
  //other vars

  BluetoothProvider() {
    init();
  }

  Future<void> init() async {
    bluetooth = FlutterBluetoothSerial.instance;
    bool isEnabled = await bluetooth.isEnabled ?? false;
    bool isBluetoothAvailable = await Permission.bluetoothConnect.isGranted;
    if (isBluetoothAvailable) {
      if (!isEnabled) {
        await enableBluetooth();
      } else {
        await Future.wait([getPairedDevices(), getName()]);
      }

      bluetooth.state.then((state) {
        bluetoothState = state;
        notifyListeners();
      });
      deviceState = 0;
      streamSubscription =
          bluetooth.onStateChanged().distinct().listen((event) {
        bluetoothState = event;
        notifyListeners();
      });
      bluetooth.onStateChanged().asBroadcastStream().listen((event) {
        bluetoothState = event;
        notifyListeners();
      });
      streamSubscription?.onData((data) {
        bluetoothState = data;
      });
      streamSubscription?.onError((error) {
        // printlog('Error: $error');
      });
      bluetooth.onStateChanged().handleError((error) {
        // printlog('Error: $error');
      });
    }
  }

  //other methods

  List<String>? computeData(Uint8List data) {
    List<String>? smallFunction() {
      String text = utf8.decode(data);
      String startingSymbol = '\$';
      for (int i = 0; i < text.length; i++) {
        buffer.add(text[i]);
        if (text[i] == startingSymbol && findElement(startingSymbol) != -1) {
          List<String> tempBuffer = buffer.sublist(0, buffer.length - 1);
          buffer.clear();
          return tempBuffer;
        }
      }
      return null;
    }

    List<String>? dataString = smallFunction();
    if (dataString != null && dataString.length > 2) {
      return dataString;
    } else {
      return null;
    }
  }

  void clearBuffer(String startingSymbol) {
    buffer.clear();
  }

  Future<bool> send(String text) async {
    text = text.trim();
    connection!.output.add(Uint8List.fromList(utf8.encode("$text\r\n")));
    try {
      await connection!.output.allSent;
      deviceState = text == "1" ? 1 : -1;
      notifyListeners();
      return true;
    } catch (e) {
      // printlog(e.toString());
      return false;
    }
  }
}

This class only handles the sending the data and computeData(Uint8List data) method only sends the incoming data into buffer such that proper data manipulation can be done afterwards.

I am using a DataPoint class to store the points receiving from the sensor. The x value is the time in millisecond and y is the sensor value.

    class DataPoint {
      // x and y values
      double x;
      double y;
      double? ynew;

      DataPoint({required this.x, required this.y, this.ynew});

      @override
      String toString() {
        return 'DataPoint{x: $x, y: $y, ynew: $ynew}';    
      }
    }

Below is the code when I convert the data coming from the Bluetooth into List and then convert it to a Datapoint Object

_dataStreamSubscription?.onData((data) {
      if (!mounted) {
        return;
      }
      /*
        getting that data and putting it in buffer to avoid random order of incoming data
        It temporarily stores data if is incomplete and gives List<String> if the all values are recieved in correct order
       */
      List<String>? tempData =
          Provider.of<BluetoothProvider>(context, listen: false)
              .computeData(data);
      if (tempData?.isNotEmpty ?? false) {
        DataPoint? tempPoint = BluetoothDataProvider.bluetoothDataProvider
            .addParsedData(tempData!, '&', '#');
        //adding to dataStream 
        Provider.of<BluetoothProvider>(context, listen:false).dataPointStream.add(tempPoint);
      }
    });
    Provider.of<BluetoothProvider>(context,listen: false).dataPointStream.stream.listen((tempPoint) {
      print(tempPoint.toString());
      if (tempPoint?.x != null && tempPoint?.y != null && tempPoint!=null) {
        if (tempPoint.y != 0 && tempPoint.x!=0) {
          tempPoint.x = (tempPoint.x) / 1000;
          updateChartData(tempPoint);
        }
      }
    });
Ignore the values as floating point How Data should come How data is coming in flutter

image image

Data is in format : $(Value1)&(Value2) when sending from arduino

I have experimented with native android apps that have Bluetooth serial functionality, it works perfectly fine. Some values are getting skipped and some digits are missing from a certain value. Any help would be appreciated.

Dracula-101 commented 1 year ago

The issue is been solved. The main reason was the listen method being in the initstate, rather than it being in the Initstate, call the listen method in an onPressed or onTap method ( for some reason it does skip data).