diff --git a/.gitignore b/.gitignore index c20e093..2a5dff8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ +# Do not remove or rename entries in this file, only add new ones +# See https://github.com/flutter/flutter/issues/128635 for more context. + # Miscellaneous *.class +*.lock *.log *.pyc *.swp @@ -15,26 +19,143 @@ *.iws .idea/ -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/ +.ccls-cache + +# This file, on the master branch, should never exist or be checked-in. +# +# On a *final* release branch, that is, what will ship to stable or beta, the +# file can be force added (git add --force) and checked-in in order to effectively +# "pin" the engine artifact version so the flutter tool does not need to use git +# to determine the engine artifacts. +# +# See https://github.com/flutter/flutter/blob/main/docs/tool/Engine-artifacts.md. +/bin/internal/engine.version + +# Flutter repo-specific +/bin/cache/ +/bin/internal/bootstrap.bat +/bin/internal/bootstrap.sh +/bin/internal/engine.realm +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/devicelab/ABresults*.json +/dev/docs/doc/ +/dev/docs/api_docs.zip +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version +analysis_benchmark.json + +# packages file containing multi-root paths +.packages.generated # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies +**/generated_plugin_registrant.dart .packages +.pub-preload-cache/ .pub-cache/ .pub/ build/ -# If you're building an application, you may want to check-in your pubspec.lock -pubspec.lock +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +.gradle/ +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks +local.properties +**/.cxx/ + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Windows +**/windows/flutter/ephemeral/ +**/windows/flutter/generated_plugin_registrant.cc +**/windows/flutter/generated_plugin_registrant.h +**/windows/flutter/generated_plugins.cmake +# Linux +**/linux/flutter/ephemeral/ +**/linux/flutter/generated_plugin_registrant.cc +**/linux/flutter/generated_plugin_registrant.h +**/linux/flutter/generated_plugins.cmake +# Coverage +coverage/ +# Symbols +app.*.symbols -## Custom stuff +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock +!.vscode/settings.json -coverage/ \ No newline at end of file +# Monorepo +.cipd +.gclient +.gclient_entries +.python-version +.gclient_previous_custom_vars +.gclient_previous_sync_commits \ No newline at end of file diff --git a/README.md b/README.md index 97b6130..84a5dd3 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. @@ -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 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 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 @@ -99,40 +114,7 @@ To Be Implemented/Documented To Be Implemented/Documented #### Workout Summary data -Workout summary data can be `monitorForWorkoutSummary()`. This is a stream that returns a `WorkoutSummary` object that allows you to access the data from a completed workout (this includes programmed pieces as well as "Just row" pieces that are longer than 1 minute) - -```dart -myErg.monitorForWorkoutSummary().listen((workoutSummary) { - //do whatever here -}); -``` - -Each workout summary is a flutter object that contains the following fields: -- `Future timestamp` -- `Future workTime` -- `Future workDistance` -- `Future avgSPM` -- `Future endHeartRate` -- `Future avgHeartRate` -- `Future minHeartRate` -- `Future maxHeartRate` -- `Future avgDragFactor` -- `Future recoveryHeartRate` -- `Future workoutType` -- `Future avgPace` -- `Future intervalType` -- `Future intervalSize` -- `Future intervalCount` -- `Future totalCalories` -- `Future watts` -- `Future totalRestDistance` -- `Future intervalRestTime` -- `Future avgCalories` - -Futures are handy here since the erg can send back different data at different times. The primary reason for this is the `recoveryHeartRate` field which the erg sends after the user has been resting for 1 minute. If the workout is cancelled or the erg is turned off before the end of this minute, the data may never arrive. See #10. - -Overall this method of accessing workout summary data is not the most ideal, and is likely to change later if a better solution is found. See #11. - +To be implemented. diff --git a/docs/API.md b/docs/API.md index cc54f1e..242b123 100644 --- a/docs/API.md +++ b/docs/API.md @@ -3,54 +3,61 @@ This document is the starting point for learning more about the c2bluetooth API and inner workings at varying levels of detail: - 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 summaries of how c2bluetooth works internally and all the things it "takes care of" for end users, see the [internals](internals.md) document +- sections like [Core Values](#core-values) help explain more of the "why" behind some of the design choices made in the API. +- The [Basic api details](#basic-api-details) section is a detailed, mid-level overview of as man aspects of the public API as posible and what their purpose is. +- For people looking to get into the internals of c2bluetooth or make contributions, 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 + +## Core values + +These are some of the core values that were considered as part of the development of this library and forms the ethos/soul of the project. These should be used as general guidance when questions of scope or direction of the library are considered. + +### The Silent Protector + +This principle shares its name with the [popular meme template](https://knowyourmeme.com/memes/the-silent-protector) depicting a soldier kneeling over and protecting a sleeping child. + +![A remix of the "silent protector" meme depicting c2bluetooth protecting the apps that use it from low-level details of bluetooth communications with a concept2 erg](../docs/images/silent-protector.jpg) + +This is essentially a graphical analogy to represent the idea that this library aims to "take on" as much responsibility for abstracting low-level details and hiding away various "gotchas" and complexities of the Concept2 Bluetooth interface specification as possible. + +To my knowledge this is not currently a software design principle found in the broader software industry. However I think it is an awesome way to explain the concept. ### 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 -## Core API Concepts +## Basic API details This library is built from a few core concepts, some of which are shared with the `csafe-fitness` library. These core concepts represent general groupings of classes that serve a particular purpose or abstract certain aspects of communicating with an erg. These concepts are roughly divided up into "external" (i.e. those that are part of the libraries public API) and "internal". If you are just using the library in your app, the external concepts should be all you need. Anyone looking to contribute to this library might find the "internal" concepts helpful ### 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 are primarily 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 - #### 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 \ No newline at end of file +Model objects are located in the `models` directory. \ No newline at end of file diff --git a/docs/DesignDecisions.md b/docs/DesignDecisions.md index c9d198f..0b52632 100644 --- a/docs/DesignDecisions.md +++ b/docs/DesignDecisions.md @@ -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 diff --git a/docs/images/demo/completed.png b/docs/images/demo/completed.png new file mode 100644 index 0000000..deacd08 Binary files /dev/null and b/docs/images/demo/completed.png differ diff --git a/docs/images/demo/demo1-small.jpg b/docs/images/demo/demo1-small.jpg deleted file mode 100644 index 8c23263..0000000 Binary files a/docs/images/demo/demo1-small.jpg and /dev/null differ diff --git a/docs/images/demo/pre-scan.png b/docs/images/demo/pre-scan.png new file mode 100644 index 0000000..bb7f2a9 Binary files /dev/null and b/docs/images/demo/pre-scan.png differ diff --git a/docs/images/silent-protector.jpg b/docs/images/silent-protector.jpg new file mode 100644 index 0000000..fca1eb2 Binary files /dev/null and b/docs/images/silent-protector.jpg differ diff --git a/docs/internals.md b/docs/internals.md index 72cb15e..9c7a693 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -1,11 +1,15 @@ # Internal API and Design Concepts This document is meant to be similar to the [API](API.md) document, but specifically for providing an overview of the internal API organization. -Only people interested in contributing to c2bluetooth should need to understand things at this level. +Only people interested in modifying c2bluetooth should need to understand things at this level. -## Internal API Design +This also can be thought of as an outline of many of the (often low-level) things that c2bluetooth "takes care of" for impleenting applications in keeping with the [Silent Protector principle](API.md#the-silent-protector). -TODO + +## Terms used + +### Segment +One key difference you may notice between this library and the source documentation that this is based on (such as the Concept2 Bluetooth API specifications) is the appearance of the term "segment". This is useful because splits and intervals are fundamentally similar enough that concept2 uses the same API to convey both split and interval data (the two are mutually exclusive anyway). Concept2's documentation, however, refers to it as "Split/interval", so the term segment was introduced to make this a little easier to think about and help differentiate data points that are unique to either splits or intervals. ## Internal API Concepts #### Commands @@ -33,3 +37,15 @@ If you need to create your own datatype, you should look at the existing datatyp This section intends to give a broad overview of various components of how c2bluetooth solves certain problems and why they were solved that way +### Subscription Data Multiplex + +In order to provide c2bluetooth with the most flexibility and control over data coming from the PM5, it is useful to insert an additional layer between the incoming data from the bluetooth streams from the PM5 and the stream going out to the user so that c2bluetooth can take on and abstract as much of the complexity as possible. + +This is similar to how a library might add a custom class of its own that wraps an existing API from one of its dependencies so that, even if the API being depended on by the library changes, users of the library are more insulated as the library has a locaton where it can perform changes to keep the API as consistent for the end user as possible. + +Within the context of c2bluetooth, this additional layer is intended to also add functionality by managing both the data requested by implementors of the c2bluetooth library and the data available to it via the concept2's bluetooth interface so as to provide the implementors with the data that they requested for use in their own apps. + +This layer has a few general objectives: +- to keep track of what data the implementor wants ("requested data") +- to keep track of what data we are currently receiving ("subscriptions" to bluetooth notifications from the PM) +- to route data that comes in via bluetooth subscriptions to "outgoing streams" that the implementor is listening to while conserving bluetooth bandwidth as much as possible diff --git a/example/.gitignore b/example/.gitignore index 0fa6b67..6ce9b51 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,5 +1,9 @@ +# Do not remove or rename entries in this file, only add new ones +# See https://github.com/flutter/flutter/issues/128635 for more context. + # Miscellaneous *.class +*.lock *.log *.pyc *.swp @@ -15,32 +19,143 @@ *.iws .idea/ -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/* +.ccls-cache + +# This file, on the master branch, should never exist or be checked-in. +# +# On a *final* release branch, that is, what will ship to stable or beta, the +# file can be force added (git add --force) and checked-in in order to effectively +# "pin" the engine artifact version so the flutter tool does not need to use git +# to determine the engine artifacts. +# +# See https://github.com/flutter/flutter/blob/main/docs/tool/Engine-artifacts.md. +/bin/internal/engine.version + +# Flutter repo-specific +/bin/cache/ +/bin/internal/bootstrap.bat +/bin/internal/bootstrap.sh +/bin/internal/engine.realm +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/devicelab/ABresults*.json +/dev/docs/doc/ +/dev/docs/api_docs.zip +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version +analysis_benchmark.json + +# packages file containing multi-root paths +.packages.generated # Flutter/Dart/Pub related **/doc/api/ -**/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies +**/generated_plugin_registrant.dart .packages +.pub-preload-cache/ .pub-cache/ .pub/ -/build/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +.gradle/ +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks +local.properties +**/.cxx/ + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Windows +**/windows/flutter/ephemeral/ +**/windows/flutter/generated_plugin_registrant.cc +**/windows/flutter/generated_plugin_registrant.h +**/windows/flutter/generated_plugins.cmake + +# Linux +**/linux/flutter/ephemeral/ +**/linux/flutter/generated_plugin_registrant.cc +**/linux/flutter/generated_plugin_registrant.h +**/linux/flutter/generated_plugins.cmake -# Web related -lib/generated_plugin_registrant.dart +# Coverage +coverage/ -# Symbolication related +# Symbols app.*.symbols -# Obfuscation related -app.*.map.json +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock +!.vscode/settings.json -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release +# Monorepo +.cipd +.gclient +.gclient_entries +.python-version +.gclient_previous_custom_vars +.gclient_previous_sync_commits \ No newline at end of file diff --git a/example/README.md b/example/README.md index 80716b9..be615e6 100644 --- a/example/README.md +++ b/example/README.md @@ -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. + + ## 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. + diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 5a1c3ea..58afde7 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,33 +1,23 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 30 - + namespace "com.example.fresh_example" + // Any value starting with "flutter." get its value from + // the Flutter Gradle plugin. + compileSdk flutter.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = '17' + } sourceSets { main.java.srcDirs += 'src/main/kotlin' } @@ -35,10 +25,12 @@ 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 - targetSdkVersion 30 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + // You can update the following value to match your application needs. + minSdk flutter.minSdkVersion + targetSdk flutter.targetSdkVersion + // You can set these values in the property declaration or use a variable + versionCode flutter.versionCode.toInteger() + versionName flutter.versionName } buildTypes { @@ -53,7 +45,3 @@ android { flutter { source '../..' } - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 99c6fb1..dab5e37 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,11 @@ + + + + + + @@ -9,7 +15,8 @@ android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:exported="true">