Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive rewrite #17

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
78f2f35
install reactive_ble
MoralCode Nov 11, 2022
345b7ad
first pass at migrating bluetooth scanning over to reactive ble
MoralCode Nov 11, 2022
707a6b9
update connection and IO related stuff to use reactive
MoralCode Nov 11, 2022
e2179dc
add flutterReactiveBle class
MoralCode Nov 11, 2022
d001e45
combine MonitorConnectionState with connection function
MoralCode Nov 11, 2022
a3cbd3a
converted to reactive calls and removed various errors
Aldaniee Nov 12, 2022
de7d632
make flutterReactiveBle property private
MoralCode Nov 12, 2022
e639294
fix other uses of the flutterReactiveBle field
MoralCode Nov 15, 2022
dae2d91
bump minsdkversion on android to make the example app compile
MoralCode Nov 15, 2022
89bb035
remove now-unused flutter_ble_lib dependency
MoralCode Nov 15, 2022
e07ba7f
add android bluetooth permissions to demo app
MoralCode Nov 15, 2022
305f47b
other android build parameter changes
MoralCode Nov 15, 2022
4a32c11
request permissions android
MoralCode Nov 15, 2022
3788c11
misc updates to settings, mainly concerning hooking up cocoapods
MoralCode Nov 16, 2022
d2750b5
xcode recommended updates
MoralCode Nov 16, 2022
3b63796
more pod stuff maybe?
MoralCode Nov 16, 2022
f6ce67a
commit podfile
MoralCode Nov 16, 2022
89b8880
fixed scanning issue and added scan button
Aldaniee Nov 16, 2022
9ae13ca
code cleaning
Aldaniee Nov 17, 2022
1942a5f
some updates to README
MoralCode Nov 16, 2022
91aad2d
missed setState
Aldaniee Nov 17, 2022
339db59
Merge remote-tracking branch 'origin/reactive-rewrite' into reactive-…
Aldaniee Nov 17, 2022
712e537
ignore use of old deprecated functions
MoralCode Nov 17, 2022
897f30b
misc formatting
MoralCode Nov 17, 2022
5ebad8b
unused import
MoralCode Nov 18, 2022
a5b5698
add location permission to android manifest
MoralCode Nov 19, 2022
42a3c85
remove old doc comment
MoralCode Nov 19, 2022
0801f51
update doc comments for Ergometer and ErgBleManager to account for ne…
MoralCode Nov 19, 2022
d61a021
update READMEs to reflect the newer version of the demo app
MoralCode Nov 19, 2022
e5554a6
update scanning to work more asynchronously
MoralCode Nov 19, 2022
8bd7250
updates to standalone docs
MoralCode Nov 19, 2022
11c4d17
add a deprecation notice to monitorForWorkoutSummary
MoralCode Nov 19, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
C2Bluetooth is a flutter package designed to provide an easy API for accessing data from Concept2 PM5 Indoor fitness machines via bluetooth. This library implements the [Concept2 Performance Monitor Bluetooth Smart Communications Interface Definition](https://www.concept2.com/files/pdf/us/monitors/PM5_BluetoothSmartInterfaceDefinition.pdf) Specification ([newer versions](https://www.c2forum.com/viewtopic.php?f=15&t=193697#p527068) are also available). It also relies heavily on the [CSAFE specification](https://web.archive.org/web/20060718175014/http://www.fitlinxx.com/csafe/specification.htm) from FitLinxx.

## Demo
This package comes with a demo app in the `example/` directory.

![A demo showing the distance completed after a workout](docs/images/demo/demo1-small.jpg)
See the [`example/README.md`](example/README.md) for more detailed information about the demo app and how to use it.

This is a relatively old screenshot of the included example app using an older version of the library to display the completed distance from a short 20-second test workout. Many improvements to expose more datapoints have been made since this screenshot was taken.
## Key Features
## Key Library Features

Currently this library supports a few basic features such as:
- retrieving workout summary information from the erg after a workout
Expand Down Expand Up @@ -44,7 +44,6 @@ Similar to how the underlying bluetooth library works, pretty much everything be

```dart
ErgBleManager bleManager = ErgBleManager();
bleManager.init(); //ready to go!
```
### Scanning for devices
Next, you need to start scanning for available devices. This uses a Stream to return an instance of the `Ergometer` class for each erg found. Each of these instances represents an erg and should be stored for later reuse as these act as the base upon which everything else (retrieving data, sending workouts .etc) is based.
Expand All @@ -54,27 +53,43 @@ Next, you need to start scanning for available devices. This uses a Stream to re
```dart
Ergometer myErg;

bleManager.startErgScan().listen((erg) {
StreamSubscription<Ergometer> ergScanStream = bleManager.startErgScan().listen((erg) {
//your code for detecting an erg here.
myErg = erg

//you can store the erg instance somewhere
myErg = erg;

//or connect to it (see later examples)

//or stop scanning
ergScanStream.cancel();

return erg;
});
```
This block of code is where you can do things like:
- determine what erg(s) you want to work with (this can be based on name, user choice, or basicaly anything)
- store the erg instance somewhere more permanent, like the `myErg` variable to allow you to be able to access it after you stop scanning.
- call `bleManager.stopErgScan()` if you know you are done scanning early. As an example, one way to immediately connect to the first erg found is to unconditionally call `stopErgScan` within this function so the scan stops after the first erg is received. Don't forget to close the stream too!
- cancel the stream if you are done scanning.


### Connecting and disconnecting
Once you have the `Ergometer` instance for the erg you want to connect to, you can call `connectAndDiscover()` on it to connect.
Once you have the `Ergometer` instance for the erg you want to connect to, you can call `connectAndDiscover()` on it to connect. This will provide you with a stream indicating the connection state of the erg.

```dart
await myErg.connectAndDiscover();
StreamSubscription<Ergometer> ergConnectionStream = myErg.connectAndDiscover().listen((event) {
if(event == ErgometerConnectionState.connected) {
//do stuff here once the erg is connected
} else if (event == ErgometerConnectionState.disconnected) {
//handle disconnection here
}
});
}
```

When you are done, make sure to disconnect from your erg:
When you are done, disconnect from your erg by cancelling the stream:
```dart
await myErg.disconnectOrCancel();
ergConnectionStream.cancel();
```

### Getting data from the erg
Expand Down
25 changes: 14 additions & 11 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,34 @@ This document is the starting point for learning more about the c2bluetooth API

- The broadest overview comes from using the API as documented in the README
- sections like [Overall API Design](#overall-api-design) explain some of the core concepts or goals that we wanted to achieve with the API.
- For people looking to get into the internals of c2bluetooth, the [Core API Concepts](#core-api-concepts) section below is a good mid-level overview of the various groups or categories of classes that are used in the API and what their purpose is.
- For people looking to get into the internals of c2bluetooth, the [Core API Concepts](#core-api-concepts) section below is a good mid-level overview of the various groups or categories of classes that are used in the API and what their purpose is.
- For summaries of how c2bluetooth works internally and all the things it "takes care of" for end users, see the [internals](internals.md) document
- Obviously the most detailed explaination of how the code works comes from reading the code and inline comments themselves. It is helpful to understand the general goals first

## Terms used

"implementor" generally refers to users of this library. This is intended to be an audience of primarily other flutter developers looking to use this library in their apps.



## Overall API design

### Inspiration
In order for this library to be a good fit within the community and provide a good user experience for developers, the goal is to design the interface for this library after other existing libraries interfacing with Concept2 rowing machines. The libraries looked at were [Py3Row](https://github.com/droogmic/Py3Row) (Python, BSD-2), [BoutFitness/Concept2-SDK](https://github.com/BoutFitness/Concept2-SDK) (Swift, MIT), [ErgometerJS](https://github.com/tijmenvangulik/ErgometerJS) (Javascript, Apache 2).

There are likely more libraries like these available, but these were just the first few that were looked at based on a GitHub search.
There are likely more libraries like these available, but these were just the first few that were looked at based on a GitHub search at the time of writing.

### Object Oriented
These three examples all seem to use some kind of Class-based approach where a particular instance of an object represents a particular rowing machine and contains functions to make interaction with the machine easier, like getting data.
These three examples all seem to use some kind of object-oriented approach where a particular instance of an object represents a particular rowing machine and contains functions to make interaction with the machine easier, like getting data.

Designing the library in an object oriented way seemed to make the most sense given what other projects in the space seem to ave done. This should also should keep things relatively straightforward to program and maintain.
Designing the library in an object oriented way seemed to make the most sense given what other projects in the space seem to have done. This should also should keep things relatively straightforward to program and maintain.

### Subscription-based data access
Both BoutFitness/Concept2-SDK and ErgometerJS also seemed to have a way to asynchronously "subscribe" to get a callback when particular values change so the screen can be updated. Since the FlutterBleLib bluetooth library also exposes [Flutter streams](https://apgapg.medium.com/using-streams-in-flutter-62fed41662e4) for reading values in this way, it seems like a good choice to follow this model when exposing data about a PM5.
Both BoutFitness/Concept2-SDK and ErgometerJS also seemed to have a way to asynchronously "subscribe" to get a callback when particular values change so values being displayed on the screen in the implementors flutter app can be updated. Since many Flutter bluetooth libraries also expose notification data from bluetooth devices as [Flutter streams](https://apgapg.medium.com/using-streams-in-flutter-62fed41662e4), this seems like a good, clean way to expose data about a PM5.

#### Single Values
For getting single values from an erg, such as serial number, software and hardware version info, and other things that likely wont change that often, Streams may be unnecessary and it might be easier to have a simple synchronous interface for grabbing a value either from the erg or from somewhere in the memory allocated to the particular Erg object being used.

Whether or not this is actually a good solution is still TBD

<!-- ### Modularity
Since a lot of the architecture is already provided by FlutterBleLib and will likely just pass through most of the aspects of the existing bluetooth APIs, it seems like it may be useful to make this passthrough more explicit. By duplicating any of the types and methods exposed by FlutterBleLib this package will be be more able to maintain a stable API, even in the event that there is a technical need (or desire from users) to be able to change the underlying bluetooth implementation, potentially even grouping the methods that handle the actual bluetooth access into a class/interface. This is something whtat would be helpful to keep in mind during initial development but shouldn't take too much energy until later versions. -->
Expand All @@ -43,14 +46,14 @@ These concepts are roughly divided up into "external" (i.e. those that are part

### External Concepts
#### Data Objects
Data objects, like the WorkoutSummary class, are essentially wrappers around data provided by the PM and allow the data to be accessed as an object by an application.
Data objects, such as the WorkoutSummary class, are essentially wrappers around data exposed by the PM (Performance Monitor)'s bluetooth interface. This makes it easier for applications to access this data by providing a more object-oriented interface.

Data objects are primarily one-way communication from a PM to your application.
Data objects are primarily a form of one-way communication from a PM to your application.

Data objects are located in the `data` directory and represent a large chunk of the public API for c2bluetooth
Data objects are located in the `data` directory and represent the parts of c2bluetooth's public API that are most likely to be useful to an application.


#### Model Objects
This is a gairly general group of classes that represent various indoor rowing conceptsas objects for ease of use by applications looking to interact with ergs. Some examples of classses in this category are the `Ergometer` and `Workout` classes. Unlike Data Objects, they are intended to be able to enable bidirectional data flow. For example, an `Ergometer` object may have properties for getting data (like Data Objects) but also may contain methods like `sendWorkout()` that allow you to provide a `Workout` object to set up on the erg. `Workout` objects could also be returned by other methods as a way to represent a workout if needed.
This is a gairly general group of classes that represent various indoor rowing concepts (in the form of objects). Some examples of classses in this category are the `Ergometer` and `Workout` classes. Unlike Data Objects, they are intended to be able to enable bidirectional data flow. For example, an `Ergometer` object may have properties for getting data (such as Data Objects) but also may contain methods like `sendWorkout()` that allow you to provide a `Workout` object to set up on the erg. `Workout` objects could also be returned by other methods as a way to represent a workout.

Model objects are located in the `models` directory and represent a large chunk of the public API for c2bluetooth
Model objects are located in the `models` directory.
5 changes: 4 additions & 1 deletion docs/DesignDecisions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Design Decisions

## Bluetooth Library
This library ultimately depends on some bluetooth library to function. Originally the plan was to use [flutter_blue](https://github.com/pauldemarco/flutter_blue) because thats the first [tutorial](https://lupyuen.github.io/pinetime-rust-mynewt/articles/flutter#bluetooth-le-services) I came across. However, after seeing how many open issues and PR's they still have, the decline evident in their contributor graph, [comments online](https://www.reddit.com/r/FlutterDev/comments/hm63uk/why_bluetooth_in_flutter_is_so_problematic/), and [an analysis on cauldron.io](https://cauldron.io/project/5134), I've decided to use [FlutterBleLib](https://github.com/dotintent/FlutterBleLib) instead since, even though it seems similarly unmaintained, it has less open issues and seems to have reached a later stage of maturity based on its version number being in the 2.X range, rather than the 0.X range.
This library ultimately depends on some bluetooth library to function. Originally the plan was to use [flutter_blue](https://github.com/pauldemarco/flutter_blue) because thats the first [tutorial](https://lupyuen.github.io/pinetime-rust-mynewt/articles/flutter#bluetooth-le-services) I came across at the time development was started on c2bluetooth. However, after seeing how many open issues and PR's they still have, the decline evident in their contributor graph, [comments online](https://www.reddit.com/r/FlutterDev/comments/hm63uk/why_bluetooth_in_flutter_is_so_problematic/), and [an analysis on cauldron.io](https://cauldron.io/project/5134), [FlutterBleLib](https://github.com/dotintent/FlutterBleLib) was briefly used instead, before the project ultimately switched to using [flutter_reactive_ble](https://github.com/PhilipsHue/flutter_reactive_ble) mainteined by Philips Hue because it seems to be the most likely to continue to be maintained into the future.


During the transition from FlutterBleLib to flutter_reactive_ble creating an interface to represent any bluetooth library was considered because it would give implementors the ability to use a bluetooth library that may already exist in their app. This would halp maintainers reduce app dependencies, app size, and conflicting libraries, but was ultimately never implemented because it would make the process of debugging implementor-reported issues reported against the library more difficult.

## CSAFE API Usage

Expand Down
Binary file added docs/images/demo/completed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/images/demo/demo1-small.jpg
Binary file not shown.
Binary file added docs/images/demo/pre-scan.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ This document is meant to be similar to the [API](API.md) document, but specific

Only people interested in contributing to c2bluetooth should need to understand things at this level.

## Internal API Design

TODO

## Internal API Concepts
#### Commands
The command classes are based on the similarly named classes in the csafe-fitness library. There is a command superclass that is responsible for implementing general-purpose command structures from the relevant CSAFE/Concept2 specifications. These general command classes can then be subclassed to make clearly-named human readable shortcuts that pre-fill details like the identifier and command type while also performing validation of the command data.
Expand Down
23 changes: 15 additions & 8 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@

This is a sample app created from a fresh flutter project it is useful as a playground for experimenting with/testing the c2bluetooth library as it is being built as well as providing an example for how this could be used in an app

This app simply connects to the first erg that it sees.
Currently this app just connects to the first erg that it sees. An update is planned to make this a little more user-friendly for testing in environments with many ergs.

<!-- ![A demo showing the distance completed after a workout](docs/images/demo/demo1-small.jpg)

This is a relatively old screenshot of the included example app using an older version of the library to display the completed distance from a short 20-second test workout. Many improvements to expose more datapoints have been made since this screenshot was taken. -->

## Sample App capabilities
### Get workout summary information


1. build and run app.
2. long press app on android(samsung) home screen. click the info button, go to permissions and enable location permissions
3. confirm bluetooth is on
4. turn on PM and go to the screen where you would connect something like the ergdata app (usually this is a connect button on the main menu)
5. open/run the app. it should do some discovery and show you a stroke rate: 0 message
6. hit back on the erg and set up a piece. Recommended to set a 20 sec (minimum allowed) single time piece.
7. start the piece and take some strokes. after the piece is over you should see some data for the piece you completed appear on screen. feel free to modify the app to show other data points.
1. build and install the example app for your platform.
2. confirm bluetooth is on.
3. Accept any permission prompts you are given
4. turn on PM and go to the screen where you would connect something like the ergdata app (on newer firmware there will be a connect button on the main menu)
5. open/run the app. you should see a screen with a "Start scan" button. ![A demo screenshot showing the start scan button](../docs/images/demo/pre-scan.png)
6. Press this "start scan" button when you are ready to start scanning for ergs. You will see a few messages on the screen while it scans. Wait until the app says "setting up streams".
7. Use the back button on the erg to go back and set up a piece. A 20 sec (minimum allowed) single time piece is the shortest thing you can do that still works (just row pieces must be longer than 1 minute in order to be visible to the app and be saved in the PM's memory).
8. start the piece. after the piece is over you should see some data for the piece you completed appear on screen. ![A demo screenshot showing the results of a piece](../docs/images/demo/completed.png)
9. you are now ready to start making changes to the sample app to play around with the API and explore the other data points that are made available.

4 changes: 2 additions & 2 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 30
compileSdkVersion 33

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand All @@ -35,7 +35,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.fresh_example"
minSdkVersion 18
minSdkVersion 21
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
14 changes: 14 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.fresh_example">
<!-- https://developer.android.com/guide/topics/connectivity/bluetooth/permissions -->
<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />

<!-- Needed only if your app looks for Bluetooth devices.
If your app doesn't use Bluetooth scan results to derive physical
location information, you can strongly assert that your app
doesn't derive physical location. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<!-- Needed only if your app uses Bluetooth scan results to derive physical location. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
android:label="fresh_example"
android:icon="@mipmap/ic_launcher">
Expand Down
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.21'
repositories {
google()
jcenter()
Expand Down
1 change: 1 addition & 0 deletions example/ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
1 change: 1 addition & 0 deletions example/ios/Flutter/Release.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
Loading