Untitled

mail@pastecode.io avatar
unknown
plain_text
7 months ago
8.4 kB
2
Indexable
Never
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:polar/polar.dart';
import 'package:uuid/uuid.dart';
import 'package:self_science_station/provider/sensor_provider.dart';
import 'package:self_science_station/model/sensor_model.dart';
import 'package:self_science_station/sensor/sensorInstance.dart';
import 'package:flutter/services.dart';

class PolarAPI extends SensorInstance {
  //Function _onSensorChange;
  final identifier;
  final polarModel;
  final polar = Polar();
  final logs = ['Service started'];

  PolarExerciseEntry? exerciseEntry;
  //SensorProvider sp;
  // A constructor that calls the super constructor with the model instance and type parameters
  // -- this shouldn't be necessary when all child-instances are created by the super-class SensorProvider
  //PolarAPI(SensorInstanceModel sInstance, SensorTypeModel sType) : super(SensorInstanceModel sInstance, SensorTypeModel sType);

  PolarAPI(super.sp, super.sInstance, super.sType, this.identifier,
      this.polarModel) {
    initState();
  }

  void initState() async {
    await polar.requestPermissions();
    // Disconnect device upon instantiation in case it was connected
    await disconnectSensor();
    polar.batteryLevel.listen((e) => log('Battery: ${e.level}'));
    polar.deviceConnecting.listen((_) {
      print("CONNECTING POLAR EVENT");
      onConnecting();
    });
    polar.deviceConnected.listen((_) {
      print("CONNECTED POLAR EVENT");
      onConnected();
    });
    polar.deviceDisconnected.listen((_) {
      print("DISCONNECTED POLAR EVENT");
      if (!isConnecting) {
        onDisconnected();
      }
    });
  }

  @override
  Future<bool> connectSensor() async {
    await disconnectSensor();
    onConnecting();
    log('Connecting to device: $identifier');
    try {
      await polar.connectToDevice(identifier);
    } catch (e) {
      log('Failed to connect device $identifier: ${e.toString()}');
      onConnectFail();
      return false;
    }
    return true;
  }

  @override
  void connectSensorWithTimeout(Duration timeout) async {
    print("Connecting sensor $identifier with timeout $timeout");
    await disconnectSensor();
    print("Disconnected before connecting again");
    onConnecting();
    await polar.connectToDevice(identifier);
    print("Called connectToDevice");
    Future.delayed(timeout, () {
      if (!isConnected) {
        print("Connection timeout reached. Bailing.");
        onConnectFail();
      }
    });
  }

  @override
  Future<bool> disconnectSensor() async {
    log('Disconnecting from device: $identifier');
    try {
      await polar.disconnectFromDevice(identifier);
    } catch (e) {
      log('Failed to disconnect device $identifier: ${e.toString()}');
      return false;
    }
    onDisconnected();
    return true;
  }

  @override
  Future<bool> startRecording(String? recordingIdentifier) async {
    if (recordingIdentifier == null) {
      log('No recording identifier provided. Aborting recording.');
      return false;
    }
    log('Starting recording');
    await polar.startRecording(
      identifier,
      exerciseId: recordingIdentifier,
      interval: RecordingInterval.interval_1s,
      sampleType: SampleType.rr,
    );
    onRecordingStarted();
    log('Started recording');
    return true;
  }

  @override
  Future<bool> stopRecording() async {
    log('Stopping recording');
    await polar.stopRecording(identifier);
    onRecordingStopped();
    log('Stopped recording');
    return true;
  }

  @override
  Future<bool> isRecording() async {
    log('Getting recording status');
    try {
      final status = await polar.requestRecordingStatus(identifier);
      log('Recording status: $status');
      print(status);
      return status.ongoing;
    } catch (e) {
      if (e is PlatformException) {
        print(
            "Error when trying to get recording status. isRecording set to false. Error: ${e.message}");
        return false;
      }
    }
    return false;
  }

  @override
  Future<List<int>> fetchData(String? recordingIdentifier) async {
    print('Fetching recording with id $recordingIdentifier');
    final entries = await polar.listExercises(identifier);
    for (var entry in entries) {
      if (entry.entryId == recordingIdentifier) {
        final data = await polar.fetchExercise(identifier, entry);
        print('Fetched recording: $data');
        // We no longer need this data to be taking space on the device
        await polar.removeExercise(identifier, entry);
        return data.samples;
      }
    }
    return [];
  }

  @override
  Future<void> emptyRecordings() async {
    print('Emptying recordings');
    final entries = await polar.listExercises(identifier);
    for (var entry in entries) {
      await polar.removeExercise(identifier, entry);
    }
  }

  @override
  Future<void> resetSensor() async {
    print('Resetting sensor $identifier');
    await polar.doFactoryReset(identifier, true /* preserve pairing info */);
  }

  @override
  StreamSubscription<PolarDeviceInfo> scanForSensor(Function onDeviceFound) {
    return polar.searchForDevice().listen((e) {
      log('Device found from scan: ${e.deviceId}, isConnectable: ${e.isConnectable}');
      if (e.isConnectable) {
        onDeviceFound(e.deviceId);
      }
    });
  }

  // For Online Streaming. Polar has an offline API but it doesn't seem available through this flutter wrapper
  void streamWhenReady() async {
    await polar.sdkFeatureReady.firstWhere(
      (e) =>
          e.identifier == identifier &&
          e.feature ==
              PolarSdkFeature
                  .onlineStreaming, // Online, but it continues offline if disconnected by itself
    );
    final availabletypes =
        await polar.getAvailableOnlineStreamDataTypes(identifier);

    debugPrint('available types: $availabletypes');

    if (availabletypes.contains(PolarDataType.hr)) {
      polar
          .startHrStreaming(identifier)
          .listen((e) => log('Heart rate: ${e.samples.map((e) => e.hr)}'));
    }
    if (availabletypes.contains(PolarDataType.ecg)) {
      polar
          .startEcgStreaming(identifier)
          .listen((e) => log('ECG data received'));
    }
    if (availabletypes.contains(PolarDataType.acc)) {
      polar
          .startAccStreaming(identifier)
          .listen((e) => log('ACC data received'));
    }
  }

  void log(String log) {
    // ignore: avoid_print
    print(log);
  }

  Future<void> _handleRecordingAction(RecordingAction action) async {
    switch (action) {
      case RecordingAction.start:
        log('Starting recording');
        await polar.startRecording(
          identifier,
          exerciseId: const Uuid().v4(),
          interval: RecordingInterval.interval_1s,
          sampleType: SampleType.rr,
        );
        onRecordingStarted();
        log('Started recording');
        break;
      case RecordingAction.stop:
        log('Stopping recording');
        await polar.stopRecording(identifier);
        onRecordingStopped();
        log('Stopped recording');
        break;
      case RecordingAction.status:
        log('Getting recording status');
        final status = await polar.requestRecordingStatus(identifier);
        log('Recording status: $status');
        // TODO update the sensor state depending on the status
        break;
      case RecordingAction.list:
        log('Listing recordings');
        final entries = await polar.listExercises(identifier);
        log('Recordings: $entries');
        // H10 can only store one recording at a time
        exerciseEntry = entries.first;
        break;
      case RecordingAction.fetch:
        log('Fetching recording');
        if (exerciseEntry == null) {
          log('Exercises not yet listed');
          await _handleRecordingAction(RecordingAction.list);
        }
        final entry = await polar.fetchExercise(identifier, exerciseEntry!);
        log('Fetched recording: $entry');
        break;
      case RecordingAction.remove:
        log('Removing recording');
        if (exerciseEntry == null) {
          log('No exercise to remove. Try calling list first.');
          return;
        }
        await polar.removeExercise(identifier, exerciseEntry!);
        log('Removed recording');
        break;
    }
  }
}

enum RecordingAction {
  start,
  stop,
  status,
  list,
  fetch,
  remove,
}
Leave a Comment