myonnaise
myonnaise copied to clipboard
🍯 A RxJava library to access Raw EMG data from your Myo 📈 (plus an Android companion App 📱)
Myonnaise 🍯
An Android library to interact with your Thalmic Myo, written in Kotlin and using RxJava2.
This repo contains also a sample app that showcases the usage of the library: Myo EMG Visualizer. With this app you can stream EMG Raw data from your device and save it as a CSV. The app is also available on the play store:
DISCLAIMER: If you don't know what a Myo is, please go here: support.getmyo.com. Please note that you need a Myo in order to use this library/app.
- Getting Started
-
Example
- Searching for a Myo
- Connecting to a Myo
- Sending a Command
- Starting the Streaming
- Streaming Frequency
- Keep Alive
- Features
-
Test App
- Videos
-
Building/Testing
- CircleCI
- Codecov
- Building locally
- Testing
- Contributing
- License
Getting Started 👣
Myonnaise is distributed through JCenter. To use it you need to add the following Gradle dependency to your android app gradle file (NOT the root file).
dependencies {
implementation 'com.ncorti:myonnaise:1.0.0'
}
Example 🚸
After setting up the Gradle dependency, you will be able to access two main classes: Myonnaise
and Myo
.
-
Myonnaise
is the entry point where you can trigger a bluetooth scan to search for nearby devices. A scan will return you one or modeBluetoothDevice
(from the android framework). If you don't know your Myo's address a priori, you need to show those devices to the user and allow him to pick one. -
Myo
is the class that will allow you to connect to your device, send commands and start the streaming. You need aBluetoothDevice
in order to create aMyo
.
Searching for a Myo
First, you need to find a Myo with a bluetooth scan.
** ⚠️ Please note that you need to request the user the ACCESS_COARSE_LOCATION permission. If not, the scan will be empty ⚠️ **
To start a bluetooth scan, you can use the startScan()
method:
Myonnaise(context).startScan()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// Do something with the found device
println(it.address)
})
This method will return a Flowable
that will publish all the BluetoothDevice
that are discovered nearby. Please note that the scan will stop only when you cancel the Flowable.
Alternatively, you can also provide a timeout and the scan will stop after the timeout:
Myonnaise(context).startScan(5, TimeUnit.MINUTES)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// Do something with the found device
println(it.address)
})
Once you found a BluetoothDevice
that is a Myo, you can get a Myo
instance from it.
val myMyo = Myonnaise.getMyo(foundDevice)
Connecting to a Myo
Connecting or disconnecting to a Myo is really easy:
// To Connect
myMyo.connect(getContext())
// To Disconnect
myMyo.disconnect()
Connecting and disconnecting are not syncronous operations. You have to wait that the device is successfully connected before start sending commands.
You can get notified of status updates using the RxJava statusObservable
.
myMyo.statusObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
when (it) {
MyoStatus.CONNECTED -> { ... }
MyoStatus.CONNECTING -> { ... }
MyoStatus.READY -> { ... }
else -> { ... } // DISCONNECTED
}
}
In order to send command to your Myo
, your Myo
should be in the READY state. If the Myo
is not ready, commands will be ignored.
Sending a Command
To send a command you can use the sendCommand()
method. For example, you can let your device vibrate with:
myMyo.sendCommand(CommandList.vibration1())
Commands will be processed by the library and sent to the device (the library has a queue to process all the commands).
List of all available commands is in the CommandList.kt
file.
Starting the Streaming
You can start/stop the streaming using again the sendCommand
method:
// Start Streaming
myMyo.sendCommand(CommandList.emgUnfilteredOnly())
// Stop Streaming
myMyo.sendCommand(CommandList.stopStreaming())
You will start receiving the streaming of data as a Flowable<FloatArray>
through the dataFlowable()
method. To collect the data, just subscribe to the flowable:
myMyo.dataFlowable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
println(it) // it is an array of 8 floats.
}
Streaming Frequency
You can change the streaming frequency to receive less data. By default data is streamed at 200Hz (the max supported by the device). You can subsample the data if you set the frequency property:
myMyo.frequency = 50 // Streaming at 50Hz
Allowed values are from 0 (reset to default) to 200.
Keep Alive
The Myo will go to sleep if he receives no intereaction within some seconds. For this reason we are sending a CommandList.unSleep()
every 10 seconds, in order to keep the connection always on.
If you don't want this behavior, just turn off the keep alive:
myMyo.keepAlive = false
Features 🎨
- 100% Kotlin (but you don't need Kotlin to use it)!
- Uses RxJava. You don't need to poll for status update, the library will call you.
- Unleash the full Myo power, Raw Data Streaming at 200Hz! 💪
- Small footprint: The AAR is just 36Kb
- API >= 21 compatible (due to BluetoothLE limitations).
- Easy to integrate (just a gradle
implementation
line).
Test App 📲
You can find the test app (Myo Emg Visualizer) inside the app
module.
This app allows you to:
- Scan for a Myo
- Connect to a Myo, control the frequency and send vibration.
- See a graph of the EMG data.
- Export the EMG data as a CSV file
Some technical features are:
- The app is 100% Kotlin.
- Architecture pattern used: MVP.
- Dagger Android to inject the views.
- Use AndroidX.
- Use the Material Design Components library.
Videos
Searching for a Myo

Starting the Streaming

Exporting to CSV

Building/Testing ⚙️
CircleCI 
This projects is built with Circle CI 2.0. The CI environment takes care of building the library .AAR, the example app and to run the JUnit tests. Test and lint reports are exposes in the artifacts section at the end of every build.
Codecov 
Circle CI is responsible of uploading Jacoco reports to Codecov. When opening a Pull Request, Codecov will post a report of the diff of the test coverage.
Please don't ignore it! PR with new features and without are likely to be discarded 😕
Building locally
Before building, make sure you have the following updated components from the Android SDK:
- tools
- platform-tools
- build-tools-28.0.1
- android-28
Then just clone the repo locally and build the .AAR with the following command:
git clone [email protected]:cortinico/myonnaise.git
cd myonnaise/
./gradlew app:assemble
The assembled .AAR (library) will be inside the myonnaise/build/outputs/aar folder. The assembled .APK (application) will be inside the app/build/outputs/apk/debug folder.
Testing
Once you're able to build successfully, you can run JUnit tests locally with the following command.
./gradlew test
Please note that there are tests inside the myonnaise
and the app
module. The app
module contains test for the presenters. The myonnaise
module contains tests for the library.
Make sure your tests are all green ✅ locally before submitting PRs.
Contributing 🤝
Looking for contributors! Don't be shy. 😁 Feel free to open issues/pull requests to help me improve this project.
- When reporting a new Issue, make sure to attach Screenshots of the problem you are reporting.
- Debugging
- When submitting a new PR, make sure tests are all green. Write new tests if necessary (would be great if the code coverage doesn't decrease).
License 📄
This project is licensed under the MIT License - see the License file for details