import 'dart:async'; import 'dart:io'; import 'package:esense_flutter/esense.dart'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:sense_the_rhythm/models/arrow_direction.dart'; import 'package:sense_the_rhythm/models/input_direction.dart'; class ESenseInput { // create singleton that is available on all widgets so it does not have to be // carried down in the widget tree static final instance = ESenseInput._(); ESenseManager eSenseManager = ESenseManager('unknown'); // valuenotifier allows widgets to rerender when the value changes ValueNotifier deviceStatus = ValueNotifier('Disconnected'); StreamSubscription? subscription; String eSenseDeviceName = ''; bool connected = false; bool sampling = false; int sampleRate = 20; InputDirection inputDirection = InputDirection(); int x = 0; int y = 0; int z = 0; ESenseInput._() { _listenToESense(); } /// ask and check if permissions are enabled and granted Future _askForPermissions() async { // is desktop if (!Platform.isAndroid && !Platform.isIOS) return false; // is bluetooth even enabled? if (!await Permission.bluetooth.serviceStatus.isEnabled) { deviceStatus.value = "Bluetooth is disabled!"; return false; } if (!(await Permission.bluetoothScan.request().isGranted && await Permission.bluetoothConnect.request().isGranted && await Permission.bluetooth.request().isGranted)) { print( 'WARNING - no permission to use Bluetooth granted. Cannot access eSense device.'); deviceStatus.value = "Insufficiant Permissions"; return false; } // for some strange reason, Android requires permission to location for Bluetooth to work.....? if (Platform.isAndroid) { if (!(await Permission.locationWhenInUse.request().isGranted)) { print( 'WARNING - no permission to access location granted. Cannot access eSense device.'); deviceStatus.value = "Insufficiant Permissions"; return false; } } return true; } /// listen to connectionEvents and set deviceStatus void _listenToESense() { // if you want to get the connection events when connecting, // set up the listener BEFORE connecting... eSenseManager.connectionEvents.listen((event) { print('CONNECTION event: $event'); // when we're connected to the eSense device, we can start listening to events from it // if (event.type == ConnectionType.connected) _listenToESenseEvents(); connected = false; switch (event.type) { case ConnectionType.connected: deviceStatus.value = 'Connected'; connected = true; _startListenToSensorEvents(); break; case ConnectionType.unknown: deviceStatus.value = 'Unknown'; break; case ConnectionType.disconnected: deviceStatus.value = 'Disconnected'; sampling = false; _pauseListenToSensorEvents(); break; case ConnectionType.device_found: deviceStatus.value = 'Device_found'; break; case ConnectionType.device_not_found: deviceStatus.value = 'Device_not_found'; break; } }); } /// get eSenseEvent stream only containung button events Stream buttonEvents() { return eSenseManager.eSenseEvents .where((event) => event.runtimeType == ButtonEventChanged) .cast(); } /// sets sampling rate and listens to sensorEvents void _startListenToSensorEvents() async { // // any changes to the sampling frequency must be done BEFORE listening to sensor events print('setting sampling frequency...'); bool successs = await eSenseManager.setSamplingRate(sampleRate); if (successs) { print('setSamplingRate success'); } else { print('setSamplingRate fail'); } // subscribe to sensor event from the eSense device subscription = eSenseManager.sensorEvents.listen((event) { // print('SENSOR event: $event'); if (event.gyro != null) { _parseGyroData(event.gyro!); } }); sampling = true; } /// cancels the sensorEvents listening void _pauseListenToSensorEvents() async { subscription?.cancel(); sampling = false; } /// add up all new gyro [data] in the form of deg/s multiplied by scaling factor /// to get real angles void _parseGyroData(List data) { // Float value in deg/s = Gyro value / Gyro scale factor // The default configuration is +- 500deg/s for the gyroscope. x = (x + (15 * data[0] ~/ (500 * sampleRate))) % 360; y = (y + (15 * data[1] ~/ (500 * sampleRate))) % 360; z = (z + (15 * data[2] ~/ (500 * sampleRate))) % 360; print('$x, $y, $z'); // print('${(z.toDouble() / 500.0 * (1.0 / sampleRate.toDouble())) * 7.5}'); // print('${z.toDouble() / 500.0 * (1.0 / 10.0)}'); } /// nulls all angles and reset inputDirection void resetAngles() { inputDirection.reset(); x = 0; y = 0; z = 0; } /// get InputDirection by checking if angels are in defined ranges and /// calibrating based on the [expect]ed direction from ArrowDirection InputDirection getInputDirection(ArrowDirection expect) { // check if angle is in range inputDirection.up = z > 180 && z < 340; inputDirection.down = z > 20 && z < 180; inputDirection.left = y > 0 && y < 180; inputDirection.right = y > 180 && y < 360; // calibrate based on expected directoin from ArrowDirection if (expect == ArrowDirection.up && inputDirection.up || expect == ArrowDirection.down && inputDirection.down) { y = 0; } if (expect == ArrowDirection.left && inputDirection.left || expect == ArrowDirection.right && inputDirection.right) { z = 0; } return inputDirection; } /// connect to ESense with [deviceName] by first asking for permissions Future connectToESense(String deviceName) async { if (!connected) { bool permissionSuccessfull = await _askForPermissions(); if (!permissionSuccessfull) return; print('Trying to connect to eSense device namend \'$deviceName\''); eSenseDeviceName = deviceName; eSenseManager.deviceName = deviceName; bool connecting = await eSenseManager.connect(); print( 'Trying to connect to eSense device namend \'${eSenseManager.deviceName}\''); deviceStatus.value = connecting ? 'connecting...' : 'connection failed'; print(deviceStatus.value); } } }