Skip to content

Commit

Permalink
ported bluetooth bloc to flutter_bloc
Browse files Browse the repository at this point in the history
  • Loading branch information
pseudoincorrect committed Apr 7, 2021
1 parent edc8f59 commit 63a05d4
Show file tree
Hide file tree
Showing 14 changed files with 639 additions and 394 deletions.
2 changes: 1 addition & 1 deletion Mobile_app/smart_mask/.flutter-plugins-dependencies
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_blue","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"android":[{"name":"flutter_blue","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_macos-2.0.0\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_linux-2.0.0\\\\","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_windows-2.0.0\\\\","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_blue","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2021-04-06 17:06:34.035852","version":"2.0.4"}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_blue","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"android":[{"name":"flutter_blue","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_macos-2.0.0\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.0+3\\\\","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_linux-2.0.0\\\\","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"C:\\\\Users\\\\maxim\\\\Programming\\\\SDKs\\\\Flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_windows-2.0.0\\\\","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_blue","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2021-04-07 10:18:24.853516","version":"2.0.4"}
48 changes: 21 additions & 27 deletions Mobile_app/smart_mask/lib/src/app.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// ignore: import_of_legacy_library_into_null_safe
import 'package:flutter_blue/flutter_blue.dart';
import 'package:smart_mask/src/logic/blocs/bloc.dart';
import 'package:smart_mask/src/logic/blocs/bluetooth/bluetooth_bloc.dart';
import 'package:smart_mask/src/logic/blocs/bluetooth/bluetooth_provider.dart';
import 'package:smart_mask/src/ui/screens/bluetooth/ble_find_device_screen.dart';
import 'package:smart_mask/src/ui/screens/graphs_screen.dart';
import 'package:smart_mask/src/ui/screens/analytics_screen.dart';
Expand All @@ -22,21 +21,17 @@ class MyApp extends StatelessWidget {

FlutterBlue.instance.setLogLevel(LogLevel.error);

final bluetoothBloc = BluetoothBloc();

return BluetoothProvider(
bloc: bluetoothBloc,
child: MultiBlocProvider(
providers: [
BlocProvider<AnalyticsBloc>(create: (context) => AnalyticsBloc()),
BlocProvider<SensorDataBloc>(create: (context) => SensorDataBloc()),
],
child: MaterialApp(
title: "Smart Mask",
theme: getTheme(),
home: SplashScreen(),
),
),
return MultiBlocProvider(
providers: [
BlocProvider<BleBloc>(create: (context) => BleBloc()),
BlocProvider<AnalyticsBloc>(create: (context) => AnalyticsBloc()),
BlocProvider<SensorDataBloc>(create: (context) => SensorDataBloc()),
],
child: MaterialApp(
title: "Smart Mask",
theme: getTheme(),
home: SplashScreen(),
),
);
}

Expand Down Expand Up @@ -70,10 +65,7 @@ class TabControl extends StatefulWidget {
}

class _TabControlState extends State<TabControl> {
BluetoothBloc? bluetoothBloc;

Widget build(BuildContext context) {
bluetoothBloc = BluetoothProvider.of(context);
return DefaultTabController(
length: choices.length,
initialIndex: 3,
Expand All @@ -86,14 +78,16 @@ class _TabControlState extends State<TabControl> {
style: TextStyle(fontSize: 30),
),
Expanded(child: Container()),
StreamBuilder<bool>(
stream: bluetoothBloc!.isConnectedStream,
initialData: false,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == true) {
return Text("Connected");
BlocBuilder<BleBloc, BleState>(
buildWhen: (_, state) => state is BleStateSetConnected,
builder: (context, state) {
if (state is BleStateSetConnected) {
if (state.connected)
return Text("Connected");
else
return Text("Disconnected");
}
return Text("Disconnected");
return Text("Loading..");
},
),
],
Expand Down
4 changes: 4 additions & 0 deletions Mobile_app/smart_mask/lib/src/logic/blocs/bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ export "./analytics/analytics_event.dart";
export './sensor_data/sensor_data_bloc.dart';
export './sensor_data/sensor_data_state.dart';
export './sensor_data/sensor_data_event.dart';

export './bluetooth/bluetooth_bloc.dart';
export './bluetooth/bluetooth_state.dart';
export './bluetooth/bluetooth_event.dart';
260 changes: 75 additions & 185 deletions Mobile_app/smart_mask/lib/src/logic/blocs/bluetooth/bluetooth_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,215 +1,105 @@
// Bluetooth Business logic (BLoc)
// Ble Business logic (BLoc)
//
// Description:
// contain the bluetooth state management for the app
// manage data stream (sensors, and notification)

import 'dart:async';
import 'dart:typed_data';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_mask/src/logic/blocs/bloc.dart';
import 'package:smart_mask/src/logic/blocs/bluetooth/bluetooth_event.dart';

// ignore: import_of_legacy_library_into_null_safe
import 'package:flutter_blue/flutter_blue.dart';

// ignore: import_of_legacy_library_into_null_safe
import 'package:rxdart/rxdart.dart';
import 'package:smart_mask/src/logic/database/models/sensor_model.dart';
import 'package:smart_mask/src/logic/blocs/bluetooth/bluetooth_logic.dart';
import 'package:smart_mask/src/logic/database/models/sensor_control_model.dart';
import 'package:smart_mask/src/logic/repositories/sensor_data_repo.dart';
import 'package:smart_mask/src/logic/blocs/bluetooth/smart_mask_services_const.dart'
as smsConst;

final String sensorServiceUUID = smsConst.S["sensorMeasurementService"]["UUID"];

final Map<String, Map<String, String>> valuesChars =
smsConst.S["sensorMeasurementService"]["characteristics"]["values"];

final Map<String, Map<String, String>> controlChars =
smsConst.S["sensorMeasurementService"]["characteristics"]["control"];

class BluetoothBloc {
Map<Sensor, SensorControl> _sensorControls = Map();
late SensorDataRepository _sensorDataRepository;
late BehaviorSubject<bool> _isConnectedSubject;

BluetoothBloc() {
_sensorDataRepository = SensorDataRepository();

Timer.periodic(Duration(seconds: 2), (Timer t) => _checkConnected());

_isConnectedSubject = BehaviorSubject<bool>();

var j = 0;
for (var i in Sensor.values) {
_sensorControls[i] = SensorControl(
initGain: SensorGain.fifth,
initSamplePeriodMs: 200 + j * 10,
initEnable: true,
);
j++;
}
}

_checkConnected() async {
var devices = await FlutterBlue.instance.connectedDevices;
if (devices.length > 0)
_isConnectedSubject.add(true);
else
_isConnectedSubject.add(false);
}

Stream<bool> get isConnectedStream => _isConnectedSubject.stream;

checkServiceUpdate(BluetoothDevice device) async {
print("checkServiceUpdate ${device.name}");
var services = await device.discoverServices();
print(services);
for (var s in services) {
print(s.uuid);
await updateCharacteristics(s.characteristics);
}
}

updateCharacteristics(List<BluetoothCharacteristic> characteristics) async {
for (var characteristic in characteristics) {
var uuid = characteristic.uuid.toString().toUpperCase();

dynamic uuids = [];
valuesChars.forEach((key, value) {
uuids.add(value["UUID"]);
});

if (uuids.contains(uuid)) {
await setSensorReceive(characteristic);
print("enabled characteristic ${characteristic.uuid}");
}
}
}

setSensorReceive(BluetoothCharacteristic characteristic) async {
characteristic.value.listen((x) => onReceiveValue(x, characteristic));
if (!characteristic.isNotifying) {
await characteristic.setNotifyValue(true);
}
}
import 'package:smart_mask/src/logic/database/models/sensor_model.dart';

onReceiveValue(List<int> values, BluetoothCharacteristic char) async {
List<SensorData> sensorDatas;
final String uuid = char.uuid.toString();
final sensor = sensorFromBLEchararcteristicUUID(uuid)!;
sensorDatas = await parseSensorValues(values, sensor);
for (var sensorData in sensorDatas) {
await _sensorDataRepository.insertSensorData(sensorData);
class BleBloc extends Bloc<BleEvent, BleState> {
late BleLogic _logic;
late Timer connSub;

BleBloc() : super(BleStateInitial()) {
_logic = BleLogic();

connSub = Timer.periodic(
Duration(seconds: 2),
(Timer t) async =>
add(BleEventSetConnected(connected: await _logic.isConnected())),
);
}

@override
Stream<BleState> mapEventToState(event) async* {
if (event is BleEventRefresh) {
yield* _mapBleEventRefresh();
} else if (event is BleEventRefreshWithSensor) {
yield* _mapBleEventRefreshWithSensor(event);
} else if (event is BleEventSetSamplePeriod) {
yield* _mapBleEventSetSamplePeriod(event);
} else if (event is BleEventSetGain) {
yield* _mapBleEventSetGain(event);
} else if (event is BleEventSetEnable) {
yield* _mapBleEventSetEnable(event);
} else if (event is BleEventSetConnected) {
yield* _mapBleEventSetConnected(event);
}
}

Future<List<SensorData>> parseSensorValues(
List<int> values, Sensor sensor) async {
List<SensorData> sensorDatas = [];
int timeNow = DateTime.now().millisecondsSinceEpoch;

for (var i = 0, j = smsConst.SENSOR_VALS_PER_PACKET;
i < values.length;
i += 2, j--) {
var buffer = Uint8List(2).buffer;
var bdata = ByteData.view(buffer);
bdata.setUint8(1, values[i]);
bdata.setUint8(0, values[i + 1]);
int value = bdata.getInt16(0);
// Since we receive a list of value from one sensor, we need to assign
// a different time stamp for each value
int calcTime = timeNow - j * getSamplePeriod(sensor);
// int calcTime = timeNow - j * smsConst.SAMPLE_PERIOD_MS;
sensorDatas.add(SensorData.fromSensorAndValue(sensor, value, calcTime));
Stream<BleState> _mapBleEventRefresh() async* {
for (var sensor in Sensor.values) {
int samplePeriod = _logic.getSamplePeriod(sensor);
add(BleEventSetSamplePeriod(sensor: sensor, samplePeriod: samplePeriod));
SensorGain gain = _logic.getGain(sensor);
add(BleEventSetGain(sensor: sensor, gain: gain));
bool enable = _logic.getEnable(sensor);
add(BleEventSetEnable(sensor: sensor, enable: enable));
}
return sensorDatas;
}

SensorControl getSensorControl(Sensor sensor) => _sensorControls[sensor]!;

int getSamplePeriod(Sensor sensor) {
var ctrl = getSensorControl(sensor);
// print("sample period ${sensorEnumToString(sensor)} ${ctrl.samplePeriodMs}");
return ctrl.samplePeriodMs;
}

setSamplePeriod(Sensor sensor, int newPeriodMs) {
var ctrl = getSensorControl(sensor);
ctrl.samplePeriodMs = newPeriodMs;
setSensorCtrlBle(sensor);
}

SensorGain getGain(Sensor sensor) {
var ctrl = getSensorControl(sensor);
return ctrl.gain;
Stream<BleState> _mapBleEventRefreshWithSensor(
BleEventRefreshWithSensor event) async* {
Sensor sensor = event.sensor;
int samplePeriod = _logic.getSamplePeriod(sensor);
add(BleEventSetSamplePeriod(sensor: sensor, samplePeriod: samplePeriod));
SensorGain gain = _logic.getGain(sensor);
add(BleEventSetGain(sensor: sensor, gain: gain));
bool enable = _logic.getEnable(sensor);
add(BleEventSetEnable(sensor: sensor, enable: enable));
}

setGain(Sensor sensor, SensorGain newGain) async {
SensorControl ctrl = getSensorControl(sensor);
ctrl.gain = newGain;
setSensorCtrlBle(sensor);
Stream<BleState> _mapBleEventSetSamplePeriod(
BleEventSetSamplePeriod event) async* {
await _logic.setSamplePeriod(event.sensor, event.samplePeriod);
yield BleStateSetSamplePeriod(
sensor: event.sensor,
samplePeriod: event.samplePeriod,
);
}

bool getEnable(Sensor sensor) {
var ctrl = getSensorControl(sensor);
return ctrl.enable;
Stream<BleState> _mapBleEventSetGain(BleEventSetGain event) async* {
await _logic.setGain(event.sensor, event.gain);
yield BleStateSetGain(
sensor: event.sensor,
gain: event.gain,
);
}

setEnable(Sensor sensor, bool newEnable) {
SensorControl ctrl = getSensorControl(sensor);
ctrl.enable = newEnable;
setSensorCtrlBle(sensor);
Stream<BleState> _mapBleEventSetEnable(BleEventSetEnable event) async* {
await _logic.setEnable(event.sensor, event.enable);
yield BleStateSetEnable(
sensor: event.sensor,
enable: event.enable,
);
}

Future<BluetoothCharacteristic?> getSensorCtrlChar(Sensor sensor) async {
List<BluetoothDevice> devices = await FlutterBlue.instance.connectedDevices;
BluetoothDevice device;
if (devices[0].name.contains("Smart"))
device = devices[0];
else
return null;
List<BluetoothService> services = await device.services.first;
BluetoothService smsService = services
.where((s) => s.uuid.toString().toUpperCase() == sensorServiceUUID)
.first;
List<BluetoothCharacteristic> characteristics = smsService.characteristics;
BluetoothCharacteristic ctrlChar = characteristics
.where((c) =>
c.uuid.toString().toUpperCase() ==
controlChars[sensorEnumToString(sensor)]!["UUID"])
.first;
return ctrlChar;
}

setSensorCtrlBle(Sensor sensor) async {
var ctrl = getSensorControl(sensor);
var ctrlPacket = SensorControlPacket(ctrl);
var char = await getSensorCtrlChar(sensor);
if (char != null)
await char.write(ctrlPacket.buffer, withoutResponse: false);
Stream<BleState> _mapBleEventSetConnected(BleEventSetConnected event) async* {
yield BleStateSetConnected(connected: event.connected);
}

dispose() {
_isConnectedSubject.close();
connSub.cancel();
}
}

class SensorControlPacket {
List<int> buffer = [];
/////////////////////////////////////////////////////////////////////////////
SensorControlPacket(SensorControl sensorControl) {
// uint32_t samplePeriodMs
int byte;
byte = sensorControl.samplePeriodMs & 0x000000FF;
buffer.add(byte);
byte = (sensorControl.samplePeriodMs & 0x0000FF00) >> 8;
buffer.add(byte);
byte = (sensorControl.samplePeriodMs & 0x00FF0000) >> 16;
buffer.add(byte);
byte = (sensorControl.samplePeriodMs & 0xFF000000) >> 24;
buffer.add(byte);
// uint8_t gain
buffer.add(sensorControl.gain.index);
// uint8_t enable
buffer.add(sensorControl.enable ? 1 : 0);
}
BleLogic get bleLogic => _logic;
}
Loading

0 comments on commit 63a05d4

Please sign in to comment.