├── .gitattributes
├── .github
└── workflows
│ └── python.yml
├── .gitignore
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── docs
├── .statigen.toml
├── api.md
├── examples.md
├── getting-started.md
├── index.md
└── myo-logo.jpg
├── examples
├── 01_hello_myo.py
├── 02_display_data.py
├── 03_live_emg.py
├── 04_emg_rate.py
└── 05_api_listener.py
├── myo
├── __init__.py
├── _device_listener.py
├── _ffi.py
├── libmyo.h
├── macaddr.py
├── math.py
├── types
│ ├── __init__.py
│ ├── macaddr.py
│ └── math.py
└── utils.py
├── package.yml
├── setup.py
└── test
└── test_import.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.py text eol=lf
2 | *.md text eol=lf
3 |
--------------------------------------------------------------------------------
/.github/workflows/python.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Python package
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | tags: [ '*' ]
10 | pull_request:
11 | branches: [ master ]
12 |
13 | jobs:
14 | test:
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | python-version: [ '3.5', '3.6', '3.7', '3.8', '3.9' ]
19 | steps:
20 | - uses: actions/checkout@v2
21 | - uses: conda-incubator/setup-miniconda@v2
22 | with:
23 | miniconda-version: latest
24 | python-version: 3.8
25 | activate-environment: testenv
26 | channels: conda-forge
27 | - name: Setup Python ${{ matrix.python-version }} with Conda
28 | run: conda create -q -n env python=${{ matrix.python-version }}
29 | - name: Install Shut
30 | run: python -m pip install shut==0.18.2 -q
31 | - name: Test
32 | run: PYTHON=$CONDA/envs/env/bin/python shut pkg test --isolate
33 | - name: Verify package metadata
34 | run: shut pkg update --verify
35 |
36 | publish:
37 | if: startsWith(github.ref, 'refs/tags/')
38 | runs-on: ubuntu-latest
39 | needs: test
40 | steps:
41 | - uses: actions/checkout@v2
42 | - name: Set up Python 3.8
43 | uses: actions/setup-python@v2
44 | with:
45 | python-version: 3.8
46 | - name: Install dependencies
47 | run: pip install -q shut==0.18.2 -q
48 | - name: Shut publish
49 | env:
50 | PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
51 | TEST_PYPI_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }}
52 | run: |
53 | shut pkg update --verify-tag "$GITHUB_REF"
54 | shut pkg publish warehouse:pypi --test
55 | shut pkg publish warehouse:pypi
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | docs/build/
3 | dist/
4 | myo-sdk-*/
5 | .venv
6 | .env/
7 | Pipfile
8 |
9 | *.py[cod]
10 | *.egg-info
11 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2021 Niklas Rosenstein
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | this software and associated documentation files (the "Software"), to deal in
7 | Software without restriction, including without limitation the rights to use,
8 | modify, merge, publish, distribute, sublicense, and/or sell copies of the
9 | and to permit persons to whom the Software is furnished to do so, subject to
10 | following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
20 | USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | # This section is auto-generated by Shut. DO NOT EDIT {
2 | include package.yml
3 | include LICENSE.txt
4 | include README.md
5 | include ./myo/libmyo.h
6 | # }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Announcements
3 |
4 | > [Oct 15, 2018] Thalmic Labs have announced the discontinuation of the Myo
5 | > armband. Software that used to be accessible on the Thalmic downloads page
6 | > can be found on the GitHub [releases](https://github.com/NiklasRosenstein/myo-python/releases) page.
7 |
8 | > [Jun 28, 2018] Myo-Python 1.0 has been released. It comes with a number of
9 | > API changes. If you have been following an older tutorial, you might have
10 | > the new version installed but use code that worked with the old version.
11 | > Check the [Migrating from v0.2.x](#migrating-from-v02x) section below.
12 |
13 | ---
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Python bindings for the Myo SDK
25 |
26 | Myo-Python is a [CFFI] wrapper for the [Thalmic Myo SDK]. Minimum required Python version is 3.5.
27 |
28 | __Table of Contents__
29 |
30 | * [Documentation](#documentation)
31 | * [Example](#example)
32 | * [Migrating from v0.2.x](#migrating-from-v02x)
33 | * [Projects using Myo-Python](#projects-using-myo-python)
34 |
35 | [CFFI]: https://pypi.python.org/pypi/cffi
36 | [Thalmic Myo SDK]: https://developer.thalmic.com/downloads
37 |
38 | ### Documentation
39 |
40 | The documentation can currently be found in the `docs/` directory in the
41 | [GitHub Repository](https://github.com/NiklasRosenstein/myo-python).
42 |
43 | ### Example
44 |
45 | Myo-Python mirrors the usage of the Myo C++ SDK in many ways as it also
46 | requires you to implement a `DeviceListener` that will then be invoked for
47 | any events received from a Myo device.
48 |
49 | ```python
50 | import myo
51 |
52 | class Listener(myo.DeviceListener):
53 | def on_paired(self, event):
54 | print("Hello, {}!".format(event.device_name))
55 | event.device.vibrate(myo.VibrationType.short)
56 | def on_unpaired(self, event):
57 | return False # Stop the hub
58 | def on_orientation(self, event):
59 | orientation = event.orientation
60 | acceleration = event.acceleration
61 | gyroscope = event.gyroscope
62 | # ... do something with that
63 |
64 | if __name__ == '__main__':
65 | myo.init(sdk_path='./myo-sdk-win-0.9.0/')
66 | hub = myo.Hub()
67 | listener = Listener()
68 | while hub.run(listener.on_event, 500):
69 | pass
70 | ```
71 |
72 | As an alternative to implementing a custom device listener, you can instead
73 | use the `myo.ApiDeviceListener` class which allows you to read the most recent
74 | state of one or multiple Myo devices.
75 |
76 | ```python
77 | import myo
78 | import time
79 |
80 | def main():
81 | myo.init(sdk_path='./myo-sdk-win-0.9.0/')
82 | hub = myo.Hub()
83 | listener = myo.ApiDeviceListener()
84 | with hub.run_in_background(listener.on_event):
85 | print("Waiting for a Myo to connect ...")
86 | device = listener.wait_for_single_device(2)
87 | if not device:
88 | print("No Myo connected after 2 seconds.")
89 | return
90 | print("Hello, Myo! Requesting RSSI ...")
91 | device.request_rssi()
92 | while hub.running and device.connected and not device.rssi:
93 | print("Waiting for RRSI...")
94 | time.sleep(0.001)
95 | print("RSSI:", device.rssi)
96 | print("Goodbye, Myo!")
97 | ```
98 |
99 | ### Migrating from v0.2.x
100 |
101 | The v0.2.x series of the Myo-Python library used `ctypes` and has a little
102 | bit different API. The most important changes are:
103 |
104 | * The `Hub` object no longer needs to be shut down explicitly
105 | * The `DeviceListener` method names changed to match the exact event name
106 | as specified by the Myo SDK (eg. from `on_pair()` to `on_paired()`)
107 | * `Hub.run()`: The order of arguments is reversed (`handler, duration_ms`
108 | instead of `duration_ms, handler`)
109 | * `myo.init()`: Provides a few more parameters to control the way `libmyo` is detected.
110 | * `myo.Feed`: Renamed to `myo.ApiDeviceListener`
111 |
112 | ### Projects using Myo-Python
113 |
114 | - [Myo Matlab](https://github.com/yijuilee/myomatlab)
115 | - [hayanalibhatti/Finger-Movement-Classification-via-Machine-Learning-using-EMG-Armband-for-3D-Printed-Robotic-Hand](https://github.com/shayanalibhatti/Finger-Movement-Classification-via-Machine-Learning-using-EMG-Armband-for-3D-Printed-Robotic-Hand)
116 |
117 | ### Changes
118 |
119 | #### v1.0.5 (2021-09-04)
120 |
121 | - Replace use of `time.clock()` with `time.perf_counter()` (#92)
122 | - Bumped minimum required Python version to 3.5
123 |
124 | #### v1.0.4 (2019-04-29)
125 |
126 | - Remove myo.quaternion, it was a leftover and the Quaternion class was actually in myo.types.math
127 | - move myo.types.math and myo.types.macaddr to myo package instead
128 | - myo.types package is now a stub for backwards compatibility
129 | - Depend on `enum34` package instead of `nr.types.enum` which has been removed in `nr.types>=2.0.0`
130 | - Update the error message of a `ValueError` raised in `myo.init()`
131 |
132 | #### v1.0.3 (2018-06-28)
133 |
134 | - `Event.mac_address` now returns `None` if the event's type is `EventType.emg` (#62)
135 | - `Hub.run()` now accepts `DeviceListener` objects for its *handler* parameter.
136 | This carries over to `Hub.run_forever()` and `Hub.run_in_background()`.
137 | - Replace requirement `nr>=2.0.10,<3` in favor of `nr.types>=1.0.3`
138 |
139 | #### v1.0.2 (2018-06-09)
140 |
141 | - Fix `Event.warmup_result` (PR #58 @fribeiro1)
142 |
143 | #### v1.0.1 (2018-06-09)
144 |
145 | - Fix `Event.rotation_on_arm` (#59)
146 |
147 | #### v1.0.0 (2018-06-03)
148 |
149 | - Rewrite using CFFI
150 |
151 | ----
152 |
153 | This project is licensed under the MIT License.
154 | Copyright © 2015-2018 Niklas Rosenstein
155 |
--------------------------------------------------------------------------------
/docs/.statigen.toml:
--------------------------------------------------------------------------------
1 | [statigen]
2 | template = "default/docs"
3 |
4 | [site]
5 | title = "Myo Python"
6 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "API Documentation"
3 | ordering = 4
4 | +++
5 |
6 | ## Data Members
7 |
8 | ### `myo.supported_sdk_version`
9 |
10 | The SDK version that the `myo` Python library supports. This is currently set
11 | to the string `"0.9.0"`.
12 |
13 | ## Functions
14 |
15 | ### `myo.init(lib_name=None, bin_path=None, sdk_path=None)`
16 |
17 | Load the Myo shared library. This must be called before using any other
18 | functionality of the library that interacts with the Myo SDK.
19 |
20 | ## Device Listeners
21 |
22 | ### `myo.DeviceListener` Class
23 |
24 | The base class for implementing a device listener that will receive events
25 | from Myo Connect. Pass the `on_event` method to `Hub.run()`. The default
26 | implementation of `on_event()` method will redirect the event to the
27 | respective handler function based on the event type.
28 |
29 | * `on_paired(event)`
30 | * `on_unpaired(event)`
31 | * `on_connected(event)`
32 | * `on_disconnected(event)`
33 | * `on_arm_synced(event)`
34 | * `on_arm_unsynced(event)`
35 | * `on_unlocked(event)`
36 | * `on_locked(event)`
37 | * `on_pose(event)`
38 | * `on_orientation(event)`
39 | * `on_rssi(event)`
40 | * `on_battery_level(event)`
41 | * `on_emg(event)`
42 | * `on_warmup_complete(event)`
43 |
44 | ### `myo.ApiDeviceListener`
45 |
46 | This device listener implementation records any data it receives per device.
47 |
48 | #### `.wait_for_single_device(timeout=None, interval=0.5)`
49 |
50 | Waits until a Myo is paired and connected and returns its `DeviceProxy`.
51 | If no Myo connects until *timeout* runs out, `None` is returned.
52 |
53 | #### `.devices`
54 |
55 | A list of `DeviceProxy` objects that are paired.
56 |
57 | #### `.connected_devices`
58 |
59 | A list of `DeviceProxy` objects that are connected. This is a subset of
60 | `.devices`.
61 |
62 | ## Classes
63 |
64 | ### `myo.Hub` Class
65 |
66 | The hub is responsible for invoking your `DeviceListener`. The hub can be
67 | run synchronously or in the background. Note that with the background way,
68 | as soon as the end of the context-manager is reached, the hub will stop.
69 |
70 | ```python
71 | hub = myo.Hub()
72 | listener = MyListener()
73 |
74 | # Synchronous -- manual polling.
75 | while hub.run(listener.on_event, duration_ms=500):
76 | # Can do something after every run.
77 | pass
78 |
79 | # Synchronous.
80 | hub.run_forever(listener.on_event)
81 |
82 | # Asynchrnous.
83 | with hub.run_in_background(listener.on_event):
84 | # Do stuff while your listener is invoked in the background.
85 | # Note that reaching the end of the with block ends the hub.
86 | ```
87 |
88 | #### `.locking_policy`
89 |
90 | #### `.running`
91 |
92 | #### `.run(handler, duration_ms)`
93 |
94 | #### `.run_forever(handler, duration_ms=500)`
95 |
96 | #### `.run_in_background(handler, duration_ms=500)`
97 |
98 | #### `.stop()`
99 |
100 | ### `myo.Device` Class
101 |
102 | Represents a Myo device.
103 |
104 | #### `.vibrate(type=VibrationType.medium)`
105 |
106 | #### `.stream_emg(type)`
107 |
108 | #### `.request_rssi()`
109 |
110 | #### `.request_battery_level()`
111 |
112 | #### `.unlock(type=UnlockType.hold)`
113 |
114 | #### `.lock()`
115 |
116 | #### `.notify_user_action(type=UserActionType.single)`
117 |
118 | ### `myo.Event` Class
119 |
120 | #### `.type`
121 |
122 | #### `.timestamp`
123 |
124 | #### `.device`
125 |
126 | #### `.device_name`
127 |
128 | #### `.mac_address`
129 |
130 | #### `.firmware_version`
131 |
132 | #### `.arm`
133 |
134 | #### `.x_direction`
135 |
136 | #### `.warmup_state`
137 |
138 | #### `.warmup_result`
139 |
140 | #### `.rotation_on_arm`
141 |
142 | #### `.orientation`
143 |
144 | #### `.acceleration`
145 |
146 | #### `.gyroscope`
147 |
148 | #### `.pose`
149 |
150 | #### `.rssi`
151 |
152 | #### `.battery_level`
153 |
154 | #### `.emg`
155 |
156 | ## Enumerations
157 |
158 | ### `myo.Result`
159 |
160 | ### `myo.VibrationType`
161 |
162 | ### `myo.StreamEmg`
163 |
164 | ### `myo.Pose`
165 |
166 | ### `myo.EventType`
167 |
168 | ### `myo.HandlerResult`
169 |
170 | ### `myo.LockingPolicy`
171 |
172 | ### `myo.Arm`
173 |
174 | ### `myo.XDirection`
175 |
176 | ### `myo.UnlockType`
177 |
178 | ### `myo.UserActionType`
179 |
180 | ### `myo.WarmupState`
181 |
182 | ### `myo.WarmupResult`
183 |
184 | ## Exception
185 |
186 | ### `myo.Error`
187 |
188 | ### `myo.ResultError`
189 |
190 | ### `myo.InvalidOperation`
191 |
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Examples"
3 | ordering = 3
4 | +++
5 |
6 | [0]: https://github.com/NiklasRosenstein/myo-python/tree/master/examples
7 |
8 | You can find the examples on [GitHub][0].
9 |
10 | ## 03_live_emg
11 |
12 | This example keeps track of the last 512 EMG data events and displays them
13 | in a live graph with `matplotlib`.
14 |
15 | 
16 |
17 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Getting Started"
3 | ordering = 1
4 | +++
5 |
6 | Make sure you follow the installation instructions on the main page, first.
7 |
8 | To receive data from a Myo device, you need to implement a `myo.DeviceListener`.
9 | Alternatively, you can use the `myo.ApiDeviceListener` that allows you to read
10 | the last state of a property without being bound to the event flow.
11 |
12 | ## Custom DeviceListener
13 |
14 | Below is an example that implements a `myo.DeviceListener`:
15 |
16 | ```python
17 | import myo
18 |
19 | class Listener(myo.DeviceListener):
20 |
21 | def on_paired(self, event):
22 | print("Hello, {}!".format(event.device_name))
23 | event.device.vibrate(myo.VibrationType.short)
24 |
25 | def on_unpaired(self, event):
26 | return False # Stop the hub
27 |
28 | def on_orientation(self, event):
29 | orientation = event.orientation
30 | acceleration = event.acceleration
31 | gyroscope = event.gyroscope
32 | # ... do something with that
33 |
34 | if __name__ == '__main__':
35 | myo.init(sdk_path='./myo-sdk-win-0.9.0/')
36 | hub = myo.Hub()
37 | listener = Listener()
38 | while hub.run(listener.on_event, 500):
39 | pass
40 | ```
41 |
42 | ## Using ApiDeviceListener
43 |
44 | Using the stateful `myo.ApiDeviceListener` can make development a lot easier
45 | in some cases as you are not restricted to the Myo event flow.
46 |
47 | ```python
48 | import myo
49 | import time
50 |
51 | def main():
52 | myo.init()
53 | hub = myo.Hub()
54 | listener = myo.ApiDeviceListener()
55 |
56 | with hub.run_in_background(listener.on_event):
57 | print("Waiting for a Myo to connect ...")
58 | device = listener.wait_for_single_device(2)
59 | if not device:
60 | print("No Myo connected after 2 seconds.")
61 | return
62 |
63 | print("Hello, Myo! Requesting RSSI ...")
64 | device.request_rssi()
65 | while hub.running and device.connected and not device.rssi:
66 | print("Waiting for RRSI...")
67 | time.sleep(0.001)
68 | print("RSSI:", device.rssi)
69 | print("Goodbye, Myo!")
70 |
71 | if __name__ == "__main__":
72 | main()
73 | ```
74 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Home"
3 | renderTitle = false
4 | ordering = 0
5 | +++
6 |
7 | [CFFI]: https://pypi.python.org/pypi/cffi
8 | [Thalmic Myo SDK]: https://developer.thalmic.com/downloads
9 |
10 | # Welcome to the Myo Python Documentation
11 |
12 | `myo-python` is a [CFFI] based wrapper around the [Thalmic Myo SDK]. It is
13 | compatible with Python 2.7+ and 3.4+. You can find the source code over at
14 | [GitHub].
15 |
16 | ## Installation
17 |
18 | The Thalmic Myo SDK is only available for Windows and macOS, thus you can not
19 | use `myo-python` on Linux (unfortunately). Use Pip to install the library:
20 |
21 | pip install 'myo-python>=1.0.0'
22 |
23 | Next, download the Myo SDK from the [Developer Downloads][Thalmic Myo SDK]
24 | page and extract it so a convenient location.
25 |
26 | Finally, you need to make sure that `myo-python` can find the shared library
27 | contained in the Myo SDK. There a multiple ways to do this.
28 |
29 | * Add the path to the parent directory of the shared library to your
30 | `PATH` (Windows) or `DYLD_LIBRARY_PATH` (macOS) environment variable.
31 | On macOS, to keep the change persistent, add the following line to your
32 | `~/.bashrc` file: `export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/Developer/myo_sdk/myo.framework`
33 |
34 | * Pass either `sdk_path`, `bin_path` or `lib_name` to the `myo.init()` function.
35 | The `bin_path` would be the same as the path that you would add to the
36 | environment variable.
37 |
38 | ## Examples
39 |
40 | There are a bunch of examples available on the GitHub repository. Check them
41 | out [here](https://github.com/NiklasRosenstein/myo-python/tree/master/examples)!
42 |
--------------------------------------------------------------------------------
/docs/myo-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NiklasRosenstein/myo-python/80967f206ae6f0a0eb9808319840680e6e60f1bf/docs/myo-logo.jpg
--------------------------------------------------------------------------------
/examples/01_hello_myo.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2017 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 | """
23 | This simple example demonstrates how to implement a Device Listener and
24 | how to stop the event flow when the double tap pose is recognized.
25 | """
26 |
27 | from __future__ import print_function
28 | import myo
29 |
30 |
31 | class Listener(myo.DeviceListener):
32 |
33 | def on_connected(self, event):
34 | print("Hello, '{}'! Double tap to exit.".format(event.device_name))
35 | event.device.vibrate(myo.VibrationType.short)
36 | event.device.request_battery_level()
37 |
38 | def on_battery_level(self, event):
39 | print("Your battery level is:", event.battery_level)
40 |
41 | def on_pose(self, event):
42 | if event.pose == myo.Pose.double_tap:
43 | return False
44 |
45 |
46 | if __name__ == '__main__':
47 | myo.init()
48 | hub = myo.Hub()
49 | listener = Listener()
50 | while hub.run(listener.on_event, 500):
51 | pass
52 | print('Bye, bye!')
53 |
--------------------------------------------------------------------------------
/examples/02_display_data.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2017 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 | """
23 | This example displays the orientation, pose and RSSI as well as EMG data
24 | if it is enabled and whether the device is locked or unlocked in the
25 | terminal.
26 |
27 | Enable EMG streaming with double tap and disable it with finger spread.
28 | """
29 |
30 | from __future__ import print_function
31 | from myo.utils import TimeInterval
32 | import myo
33 | import sys
34 |
35 |
36 | class Listener(myo.DeviceListener):
37 |
38 | def __init__(self):
39 | self.interval = TimeInterval(None, 0.05)
40 | self.orientation = None
41 | self.pose = myo.Pose.rest
42 | self.emg_enabled = False
43 | self.locked = False
44 | self.rssi = None
45 | self.emg = None
46 |
47 | def output(self):
48 | if not self.interval.check_and_reset():
49 | return
50 |
51 | parts = []
52 | if self.orientation:
53 | for comp in self.orientation:
54 | parts.append('{}{:.4f}'.format(' ' if comp >= 0 else '', comp))
55 | parts.append(str(self.pose).ljust(10))
56 | parts.append('E' if self.emg_enabled else ' ')
57 | parts.append('L' if self.locked else ' ')
58 | parts.append(self.rssi or 'NORSSI')
59 | if self.emg:
60 | for comp in self.emg:
61 | parts.append(str(comp).ljust(5))
62 | print('\r' + ''.join('[{}]'.format(p) for p in parts), end='')
63 | sys.stdout.flush()
64 |
65 | def on_connected(self, event):
66 | event.device.request_rssi()
67 |
68 | def on_rssi(self, event):
69 | self.rssi = event.rssi
70 | self.output()
71 |
72 | def on_pose(self, event):
73 | self.pose = event.pose
74 | if self.pose == myo.Pose.double_tap:
75 | event.device.stream_emg(True)
76 | self.emg_enabled = True
77 | elif self.pose == myo.Pose.fingers_spread:
78 | event.device.stream_emg(False)
79 | self.emg_enabled = False
80 | self.emg = None
81 | self.output()
82 |
83 | def on_orientation(self, event):
84 | self.orientation = event.orientation
85 | self.output()
86 |
87 | def on_emg(self, event):
88 | self.emg = event.emg
89 | self.output()
90 |
91 | def on_unlocked(self, event):
92 | self.locked = False
93 | self.output()
94 |
95 | def on_locked(self, event):
96 | self.locked = True
97 | self.output()
98 |
99 |
100 | if __name__ == '__main__':
101 | myo.init()
102 | hub = myo.Hub()
103 | listener = Listener()
104 | while hub.run(listener.on_event, 500):
105 | pass
106 |
--------------------------------------------------------------------------------
/examples/03_live_emg.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2017 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 |
23 | from matplotlib import pyplot as plt
24 | from collections import deque
25 | from threading import Lock, Thread
26 |
27 | import myo
28 | import numpy as np
29 |
30 |
31 | class EmgCollector(myo.DeviceListener):
32 | """
33 | Collects EMG data in a queue with *n* maximum number of elements.
34 | """
35 |
36 | def __init__(self, n):
37 | self.n = n
38 | self.lock = Lock()
39 | self.emg_data_queue = deque(maxlen=n)
40 |
41 | def get_emg_data(self):
42 | with self.lock:
43 | return list(self.emg_data_queue)
44 |
45 | # myo.DeviceListener
46 |
47 | def on_connected(self, event):
48 | event.device.stream_emg(True)
49 |
50 | def on_emg(self, event):
51 | with self.lock:
52 | self.emg_data_queue.append((event.timestamp, event.emg))
53 |
54 |
55 | class Plot(object):
56 |
57 | def __init__(self, listener):
58 | self.n = listener.n
59 | self.listener = listener
60 | self.fig = plt.figure()
61 | self.axes = [self.fig.add_subplot('81' + str(i)) for i in range(1, 9)]
62 | [(ax.set_ylim([-100, 100])) for ax in self.axes]
63 | self.graphs = [ax.plot(np.arange(self.n), np.zeros(self.n))[0] for ax in self.axes]
64 | plt.ion()
65 |
66 | def update_plot(self):
67 | emg_data = self.listener.get_emg_data()
68 | emg_data = np.array([x[1] for x in emg_data]).T
69 | for g, data in zip(self.graphs, emg_data):
70 | if len(data) < self.n:
71 | # Fill the left side with zeroes.
72 | data = np.concatenate([np.zeros(self.n - len(data)), data])
73 | g.set_ydata(data)
74 | plt.draw()
75 |
76 | def main(self):
77 | while True:
78 | self.update_plot()
79 | plt.pause(1.0 / 30)
80 |
81 |
82 | def main():
83 | myo.init()
84 | hub = myo.Hub()
85 | listener = EmgCollector(512)
86 | with hub.run_in_background(listener.on_event):
87 | Plot(listener).main()
88 |
89 |
90 | if __name__ == '__main__':
91 | main()
92 |
--------------------------------------------------------------------------------
/examples/04_emg_rate.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2017 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 |
23 | from __future__ import print_function
24 | import collections
25 | import myo
26 | import time
27 | import sys
28 |
29 |
30 | class EmgRate(myo.DeviceListener):
31 |
32 | def __init__(self, n):
33 | super(EmgRate, self).__init__()
34 | self.times = collections.deque()
35 | self.last_time = None
36 | self.n = int(n)
37 |
38 | @property
39 | def rate(self):
40 | if not self.times:
41 | return 0.0
42 | else:
43 | return 1.0 / (sum(self.times) / float(self.n))
44 |
45 | def on_arm_synced(self, event):
46 | event.device.stream_emg(True)
47 |
48 | def on_emg(self, event):
49 | t = time.perf_counter()
50 | if self.last_time is not None:
51 | self.times.append(t - self.last_time)
52 | if len(self.times) > self.n:
53 | self.times.popleft()
54 | self.last_time = t
55 |
56 |
57 | def main():
58 | myo.init()
59 | hub = myo.Hub()
60 | listener = EmgRate(n=50)
61 | while hub.run(listener.on_event, 500):
62 | print("\r\033[KEMG Rate:", listener.rate, end='')
63 | sys.stdout.flush()
64 |
65 |
66 | if __name__ == '__main__':
67 | main()
68 |
--------------------------------------------------------------------------------
/examples/05_api_listener.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2015 Niklas Rosenstein
2 | #
3 | # Permission is hereby granted, free of charge, to any person obtaining a copy
4 | # of this software and associated documentation files (the "Software"), to deal
5 | # in the Software without restriction, including without limitation the rights
6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | # copies of the Software, and to permit persons to whom the Software is
8 | # furnished to do so, subject to the following conditions:
9 | #
10 | # The above copyright notice and this permission notice shall be included in
11 | # all copies or substantial portions of the Software.
12 | #
13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | # THE SOFTWARE.
20 |
21 | from __future__ import print_function
22 | import myo
23 | import time
24 |
25 |
26 | def main():
27 | myo.init()
28 | hub = myo.Hub()
29 | listener = myo.ApiDeviceListener()
30 |
31 | with hub.run_in_background(listener.on_event):
32 | print("Waiting for a Myo to connect ...")
33 | device = listener.wait_for_single_device(2)
34 | if not device:
35 | print("No Myo connected after 2 seconds.")
36 | return
37 |
38 | print("Hello, Myo! Requesting RSSI ...")
39 | device.request_rssi()
40 | while hub.running and device.connected and not device.rssi:
41 | print("Waiting for RRSI...")
42 | time.sleep(0.001)
43 | print("RSSI:", device.rssi)
44 | print("Goodbye, Myo!")
45 |
46 |
47 | if __name__ == "__main__":
48 | main()
49 |
--------------------------------------------------------------------------------
/myo/__init__.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-2018 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 |
23 | __version__ = '1.0.5'
24 | __author__ = 'Niklas Rosenstein '
25 |
26 | from ._ffi import *
27 | from ._device_listener import DeviceListener, ApiDeviceListener
28 |
29 | supported_sdk_version = '0.9.0'
30 |
--------------------------------------------------------------------------------
/myo/_device_listener.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-2018 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 |
23 | import threading
24 | import warnings
25 | from ._ffi import EventType, Pose, VibrationType
26 | from .utils import TimeoutManager
27 | from .math import Vector, Quaternion
28 |
29 |
30 | class DeviceListener(object):
31 | """
32 | Base class for device listeners -- objects that listen to Myo device events.
33 | """
34 |
35 | def on_event(self, event):
36 | if event.type.name: # An event type that we know of.
37 | attr = 'on_' + event.type.name
38 | try:
39 | method = getattr(self, attr)
40 | except AttributeError:
41 | pass
42 | else:
43 | return method(event)
44 |
45 | warnings.warn('unhandled event: {}'.format(event))
46 | return True # continue
47 |
48 | def on_paired(self, event): pass
49 | def on_unpaired(self, event): pass
50 | def on_connected(self, event): pass
51 | def on_disconnected(self, event): pass
52 | def on_arm_synced(self, event): pass
53 | def on_arm_unsynced(self, event): pass
54 | def on_unlocked(self, event): pass
55 | def on_locked(self, event): pass
56 | def on_pose(self, event): pass
57 | def on_orientation(self, event): pass
58 | def on_rssi(self, event): pass
59 | def on_battery_level(self, event): pass
60 | def on_emg(self, event): pass
61 | def on_warmup_completed(self, event): pass
62 |
63 |
64 | class DeviceProxy(object):
65 | """
66 | Stateful container for Myo device data.
67 | """
68 |
69 | def __init__(self, device, timestamp, firmware_version, mac_address,
70 | condition_class=threading.Condition):
71 | self._device = device
72 | self._mac_address = mac_address
73 | self._cond = condition_class()
74 | self._pair_time = timestamp
75 | self._unpair_time = None
76 | self._connect_time = None
77 | self._disconnect_time = None
78 | self._emg = None
79 | self._orientation_update_index = 0
80 | self._orientation = Quaternion.identity()
81 | self._acceleration = Vector(0, 0, 0)
82 | self._gyroscope = Vector(0, 0, 0)
83 | self._pose = Pose.rest
84 | self._arm = None
85 | self._x_direction = None
86 | self._rssi = None
87 | self._battery_level = None
88 | self._firmware_version = firmware_version
89 | self._name = None
90 |
91 | def __repr__(self):
92 | with self._cond:
93 | con = 'connected' if self._connected else 'disconnected'
94 | return ''.format(con, self.name)
95 |
96 | @property
97 | def _connected(self):
98 | return self._connect_time is not None and self._disconnect_time is None
99 |
100 | @property
101 | def connected(self):
102 | with self._cond:
103 | return self._connected
104 |
105 | @property
106 | def paired(self):
107 | with self._cond:
108 | return self._unpair_time is not None
109 |
110 | @property
111 | def mac_address(self):
112 | return self._mac_address
113 |
114 | @property
115 | def pair_time(self):
116 | return self._pair_time
117 |
118 | @property
119 | def unpair_time(self):
120 | with self._cond:
121 | return self._unpair_time
122 |
123 | @property
124 | def connect_time(self):
125 | return self._connect_time
126 |
127 | @property
128 | def disconnect_time(self):
129 | with self._cond:
130 | return self._disconnect_time
131 |
132 | @property
133 | def firmware_version(self):
134 | return self._firmware_version
135 |
136 | @property
137 | def orientation_update_index(self):
138 | with self._cond:
139 | return self._orientation_update_index
140 |
141 | @property
142 | def orientation(self):
143 | with self._cond:
144 | return self._orientation.copy()
145 |
146 | @property
147 | def acceleration(self):
148 | with self._cond:
149 | return self._acceleration.copy()
150 |
151 | @property
152 | def gyroscope(self):
153 | with self._cond:
154 | return self._gyroscope.copy()
155 |
156 | @property
157 | def pose(self):
158 | with self._cond:
159 | return self._pose
160 |
161 | @property
162 | def arm(self):
163 | with self._cond:
164 | return self._arm
165 |
166 | @property
167 | def x_direction(self):
168 | with self._cond:
169 | return self._x_direction
170 |
171 | @property
172 | def rssi(self):
173 | with self._cond:
174 | return self._rssi
175 |
176 | @property
177 | def emg(self):
178 | with self._cond:
179 | return self._emg
180 |
181 | def set_locking_policy(self, policy):
182 | self._device.set_locking_policy(policy)
183 |
184 | def stream_emg(self, type):
185 | self._device.stream_emg(type)
186 |
187 | def vibrate(self, type=VibrationType.short):
188 | self._device.vibrate(type)
189 |
190 | def request_rssi(self):
191 | with self._cond:
192 | self._rssi = None
193 | self._device.request_rssi()
194 |
195 | def request_battery_level(self):
196 | with self._cond:
197 | self._battery_level = None
198 | self._device.request_battery_level()
199 |
200 |
201 | class ApiDeviceListener(DeviceListener):
202 |
203 | def __init__(self, condition_class=threading.Condition):
204 | self._condition_class = condition_class
205 | self._cond = condition_class()
206 | self._devices = {}
207 |
208 | @property
209 | def devices(self):
210 | with self._cond:
211 | return list(self._devices.values())
212 |
213 | @property
214 | def connected_devices(self):
215 | with self._cond:
216 | return [x for x in self._devices.values() if x.connected]
217 |
218 | def wait_for_single_device(self, timeout=None, interval=0.5):
219 | """
220 | Waits until a Myo is was paired **and** connected with the Hub and returns
221 | it. If the *timeout* is exceeded, returns None. This function will not
222 | return a Myo that is only paired but not connected.
223 |
224 | # Parameters
225 | timeout: The maximum time to wait for a device.
226 | interval: The interval at which the function should exit sleeping. We can
227 | not sleep endlessly, otherwise the main thread can not be exit, eg.
228 | through a KeyboardInterrupt.
229 | """
230 |
231 | timer = TimeoutManager(timeout)
232 | with self._cond:
233 | # As long as there are no Myo's connected, wait until we
234 | # get notified about a change.
235 | while not timer.check():
236 | # Check if we found a Myo that is connected.
237 | for device in self._devices.values():
238 | if device.connected:
239 | return device
240 | self._cond.wait(timer.remainder(interval))
241 |
242 | return None
243 |
244 | def on_event(self, event):
245 | with self._cond:
246 | if event.type == EventType.paired:
247 | device = DeviceProxy(event.device, event.timestamp,
248 | event.firmware_version, self._condition_class)
249 | self._devices[device._device.handle] = device
250 | self._cond.notify_all()
251 | return
252 | else:
253 | try:
254 | if event.type == EventType.unpaired:
255 | device = self._devices.pop(event.device.handle)
256 | else:
257 | device = self._devices[event.device.handle]
258 | except KeyError:
259 | message = 'Myo device not in the device list ({})'
260 | warnings.warn(message.format(event), RuntimeWarning)
261 | return
262 | if event.type == EventType.unpaired:
263 | with device._cond:
264 | device._unpair_time = event.timestamp
265 | self._cond.notify_all()
266 |
267 | with device._cond:
268 | if event.type == EventType.connected:
269 | device._connect_time = event.timestamp
270 | elif event.type == EventType.disconnected:
271 | device._disconnect_time = event.timestamp
272 | elif event.type == EventType.emg:
273 | device._emg = event.emg
274 | elif event.type == EventType.arm_synced:
275 | device._arm = event.arm
276 | device._x_direction = event.x_direction
277 | elif event.type == EventType.rssi:
278 | device._rssi = event.rssi
279 | elif event.type == EventType.battery_level:
280 | device._battery_level = event.battery_level
281 | elif event.type == EventType.pose:
282 | device._pose = event.pose
283 | elif event.type == EventType.orientation:
284 | device._orientation_update_index += 1
285 | device._orientation = event.orientation
286 | device._gyroscope = event.gyroscope
287 | device._acceleration = event.acceleration
288 |
--------------------------------------------------------------------------------
/myo/_ffi.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-2018 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 |
23 | import contextlib
24 | import cffi
25 | import os
26 | import pkgutil
27 | import re
28 | import threading
29 | import six
30 | import sys
31 |
32 | from .macaddr import MacAddress
33 | from .math import Quaternion, Vector
34 |
35 | try:
36 | from enum import IntEnum
37 | except:
38 | from enum34 import IntEnum
39 |
40 |
41 | ##
42 | # Exceptions
43 | ##
44 |
45 | class Error(Exception):
46 | """
47 | Base class for errors and exceptions in the myo library.
48 | """
49 |
50 |
51 | class ResultError(Error):
52 | """
53 | Raised if the result of an operation with the Myo library was anything
54 | but successful.
55 | """
56 |
57 | def __init__(self, kind, message):
58 | self.kind = kind
59 | self.message = message
60 |
61 | def __str__(self):
62 | return str((self.kind, self.message))
63 |
64 |
65 | class InvalidOperation(Error):
66 | """
67 | Raised if an invalid operation is performed, for example if you attempt to
68 | read the firmware version in any event other than *paired* and *connect*.
69 | """
70 |
71 |
72 | ##
73 | # Enumerations
74 | ##
75 |
76 | class Result(IntEnum):
77 | __fallback__ = True
78 | success = 0
79 | error = 1
80 | error_invalid_argument = 2
81 | error_runtime = 3
82 |
83 |
84 | class VibrationType(IntEnum):
85 | __fallback__ = True
86 | short = 0
87 | medium = 1
88 | long = 2
89 |
90 |
91 | class StreamEmg(IntEnum):
92 | __fallback__ = True
93 | disabled = 0
94 | enabled = 1
95 |
96 |
97 | class Pose(IntEnum):
98 | __fallback__ = True
99 | rest = 0
100 | fist = 1
101 | wave_in = 2
102 | wave_out = 3
103 | fingers_spread = 4
104 | double_tap = 5
105 |
106 |
107 | Pose.num_poses = 6
108 |
109 |
110 | class EventType(IntEnum):
111 | __fallback__ = True
112 | paired = 0
113 | unpaired = 1
114 | connected = 2
115 | disconnected = 3
116 | arm_synced = 4
117 | arm_unsynced = 5
118 | orientation = 6
119 | pose = 7
120 | rssi = 8
121 | unlocked = 9
122 | locked = 10
123 | emg = 11
124 | battery_level = 12
125 | warmup_completed = 13
126 |
127 |
128 | class HandlerResult(IntEnum):
129 | __fallback__ = True
130 | continue_ = 0
131 | stop = 1
132 |
133 |
134 | class LockingPolicy(IntEnum):
135 | __fallback__ = True
136 | none = 0 #: Pose events are always sent.
137 | standard = 1 #: (default) Pose events are not sent while a Myo is locked.
138 |
139 |
140 | class Arm(IntEnum):
141 | __fallback__ = True
142 | right = 0
143 | left = 1
144 | unknown = 2
145 |
146 |
147 | class XDirection(IntEnum):
148 | __fallback__ = True
149 | toward_wrist = 0
150 | toward_elbow = 1
151 | unknown = 2
152 |
153 |
154 | class UnlockType(IntEnum):
155 | __fallback__ = True
156 | timed = 0
157 | hold = 1
158 |
159 |
160 | class UserActionType(IntEnum):
161 | __fallback__ = True
162 | single = 0
163 |
164 |
165 | class WarmupState(IntEnum):
166 | __fallback__ = True
167 | unknown = 0
168 | cold = 1
169 | warm = 2
170 |
171 |
172 | class WarmupResult(IntEnum):
173 | __fallback__ = True
174 | unknown = 0
175 | success = 1
176 | failed_timeout = 2
177 |
178 |
179 | ##
180 | # CFFI
181 | ##
182 |
183 | def _getffi():
184 | string = pkgutil.get_data(__name__, 'libmyo.h').decode('utf8')
185 | string = string.replace('\r\n', '\n')
186 | # Remove stuff that cffi can not parse.
187 | string = re.sub('^\s*#.*$', '', string, flags=re.M)
188 | string = string.replace('LIBMYO_EXPORT', '')
189 | string = string.replace('extern "C" {', '')
190 | string = string.replace('} // extern "C"', '')
191 |
192 | ffi = cffi.FFI()
193 | ffi.cdef(string)
194 | return ffi
195 |
196 |
197 | ffi = _getffi()
198 | libmyo = None
199 |
200 |
201 | def _getdlname():
202 | arch = 32 if sys.maxsize <= 2 ** 32 else 64
203 | if sys.platform.startswith('win32'):
204 | return 'myo{}.dll'.format(arch)
205 | elif sys.platform.startswith('darwin'):
206 | return 'myo'
207 | else:
208 | raise RuntimeError('unsupported platform: {!r}'.format(sys.platform))
209 |
210 |
211 | def init(lib_name=None, bin_path=None, sdk_path=None):
212 | """
213 | Initialize the Myo SDK by loading the libmyo shared library. With no
214 | arguments, libmyo must be on your `PATH` or `LD_LIBRARY_PATH`.
215 |
216 | You can specify the exact path to libmyo with *lib_name*. Alternatively,
217 | you can specify the binaries directory that contains libmyo with *bin_path*.
218 | Finally, you can also pass the path to the Myo SDK root directory and it
219 | will figure out the path to libmyo by itself.
220 | """
221 |
222 | if sum(bool(x) for x in [lib_name, bin_path, sdk_path]) > 1:
223 | raise ValueError('expected zero or one argument(s)')
224 |
225 | if sdk_path:
226 | if sys.platform.startswith('win32'):
227 | bin_path = os.path.join(sdk_path, 'bin')
228 | elif sys.platform.startswith('darwin'):
229 | bin_path = os.path.join(sdk_path, 'myo.framework')
230 | else:
231 | raise RuntimeError('unsupported platform: {!r}'.format(sys.platform))
232 | if bin_path:
233 | lib_name = os.path.join(bin_path, _getdlname())
234 | if not lib_name:
235 | lib_name = _getdlname()
236 |
237 | global libmyo
238 | libmyo = ffi.dlopen(lib_name)
239 |
240 |
241 | class _BaseWrapper(object):
242 |
243 | def __init__(self, handle):
244 | self._handle = handle
245 |
246 | @property
247 | def handle(self):
248 | return self._handle
249 |
250 |
251 | class ErrorDetails(_BaseWrapper):
252 | """
253 | Wraps Myo error details information.
254 | """
255 |
256 | def __init__(self):
257 | super(ErrorDetails, self).__init__(ffi.new('libmyo_hub_t*'))
258 |
259 | def __del__(self):
260 | if self._handle[0]:
261 | libmyo.libmyo_free_error_details(self._handle[0])
262 |
263 | @property
264 | def kind(self):
265 | if self._handle[0]:
266 | result = Result(libmyo.libmyo_error_kind(self._handle[0]))
267 | else:
268 | result = Result.success
269 | return result
270 |
271 | @property
272 | def message(self):
273 | if self._handle[0]:
274 | return ffi.string(libmyo.libmyo_error_cstring(self._handle[0]))
275 | else:
276 | return ''
277 |
278 | @property
279 | def handle(self):
280 | return self._handle
281 |
282 | def raise_for_kind(self):
283 | kind = self.kind
284 | if kind != Result.success:
285 | raise ResultError(kind, self.message)
286 |
287 |
288 | class Event(_BaseWrapper):
289 |
290 | def __init__(self, handle):
291 | super(Event, self).__init__(handle)
292 | self._type = EventType(libmyo.libmyo_event_get_type(self._handle))
293 |
294 | def __repr__(self):
295 | return 'Event(type={!r}, timestamp={!r}, mac_address={!r})'.format(
296 | self.type, self.timestamp, self.mac_address)
297 |
298 | @property
299 | def type(self):
300 | return self._type
301 |
302 | @property
303 | def timestamp(self):
304 | return libmyo.libmyo_event_get_timestamp(self._handle)
305 |
306 | @property
307 | def device(self):
308 | return Device(libmyo.libmyo_event_get_myo(self._handle))
309 |
310 | @property
311 | def device_name(self):
312 | return str(String(libmyo.libmyo_event_get_myo_name(self._handle)))
313 |
314 | @property
315 | def mac_address(self):
316 | if self.type == EventType.emg:
317 | return None
318 | return MacAddress(libmyo.libmyo_event_get_mac_address(self._handle))
319 |
320 | @property
321 | def firmware_version(self):
322 | return tuple(libmyo.libmyo_event_get_firmware_version(self._handle, x)
323 | for x in [0, 1, 2, 3])
324 |
325 | @property
326 | def arm(self):
327 | if self.type != EventType.arm_synced:
328 | raise InvalidOperation()
329 | return Arm(libmyo.libmyo_event_get_arm(self._handle))
330 |
331 | @property
332 | def x_direction(self):
333 | if self.type != EventType.arm_synced:
334 | raise InvalidOperation()
335 | return XDirection(libmyo.libmyo_event_get_x_direction(self._handle))
336 |
337 | @property
338 | def warmup_state(self):
339 | if self.type != EventType.arm_synced:
340 | raise InvalidOperation()
341 | return WarmupState(libmyo.libmyo_event_get_warmup_state(self._handle))
342 |
343 | @property
344 | def warmup_result(self):
345 | if self.type != EventType.warmup_completed:
346 | raise InvalidOperation()
347 | return WarmupResult(libmyo.libmyo_event_get_warmup_result(self._handle))
348 |
349 | @property
350 | def rotation_on_arm(self):
351 | if self.type != EventType.arm_synced:
352 | raise InvalidOperation()
353 | return libmyo.libmyo_event_get_rotation_on_arm(self._handle)
354 |
355 | @property
356 | def orientation(self):
357 | if self.type != EventType.orientation:
358 | raise InvalidOperation()
359 | vals = (libmyo.libmyo_event_get_orientation(self._handle, i)
360 | for i in [0, 1, 2, 3])
361 | return Quaternion(*vals)
362 |
363 | @property
364 | def acceleration(self):
365 | if self.type != EventType.orientation:
366 | raise InvalidOperation()
367 | vals = (libmyo.libmyo_event_get_accelerometer(self._handle, i)
368 | for i in [0, 1, 2])
369 | return Vector(*vals)
370 |
371 | @property
372 | def gyroscope(self):
373 | if self.type != EventType.orientation:
374 | raise InvalidOperation()
375 | vals = (libmyo.libmyo_event_get_gyroscope(self._handle, i)
376 | for i in [0, 1, 2])
377 | return Vector(*vals)
378 |
379 | @property
380 | def pose(self):
381 | if self.type != EventType.pose:
382 | raise InvalidOperation()
383 | return Pose(libmyo.libmyo_event_get_pose(self._handle))
384 |
385 | @property
386 | def rssi(self):
387 | if self.type != EventType.rssi:
388 | raise InvalidOperation()
389 | return libmyo.libmyo_event_get_rssi(self._handle)
390 |
391 | @property
392 | def battery_level(self):
393 | if self.type != EventType.battery_level:
394 | raise InvalidOperation()
395 | return libmyo.libmyo_event_get_battery_level(self._handle)
396 |
397 | @property
398 | def emg(self):
399 | if self.type != EventType.emg:
400 | raise InvalidOperation()
401 | return [libmyo.libmyo_event_get_emg(self._handle, i) for i in range(8)]
402 |
403 |
404 | class Device(_BaseWrapper):
405 |
406 | # libmyo_get_mac_address() is not in the Myo SDK 0.9.0 DLL.
407 | #@property
408 | #def mac_address(self):
409 | # return MacAddress(libmyo.libmyo_get_mac_address(self._handle))
410 |
411 | def vibrate(self, type=VibrationType.medium):
412 | if not isinstance(type, VibrationType):
413 | raise TypeError('expected VibrationType')
414 | error = ErrorDetails()
415 | libmyo.libmyo_vibrate(self._handle, int(type), error.handle)
416 | return error.raise_for_kind()
417 |
418 | def stream_emg(self, type):
419 | if type is True: type = StreamEmg.enabled
420 | elif type is False: type = StreamEmg.disabled
421 | elif not isinstance(type, StreamEmg):
422 | raise TypeError('expected bool or StreamEmg')
423 | error = ErrorDetails()
424 | libmyo.libmyo_set_stream_emg(self._handle, int(type), error.handle)
425 | error.raise_for_kind()
426 |
427 | def request_rssi(self):
428 | error = ErrorDetails()
429 | libmyo.libmyo_request_rssi(self._handle, error.handle)
430 | error.raise_for_kind()
431 |
432 | def request_battery_level(self):
433 | error = ErrorDetails()
434 | libmyo.libmyo_request_battery_level(self._handle, error.handle)
435 | error.raise_for_kind()
436 |
437 | def unlock(self, type=UnlockType.hold):
438 | if not isinstance(type, UnlockType):
439 | raise TypeError('expected UnlockType')
440 | error = ErrorDetails()
441 | libmyo.libmyo_myo_unlock(self._handle, int(type), error.handle)
442 | error.raise_for_kind()
443 |
444 | def lock(self):
445 | error = ErrorDetails()
446 | libmyo.libmyo_myo_lock(self._handle, error.handle)
447 | error.raise_for_kind()
448 |
449 | def notify_user_action(self, type=UserActionType.single):
450 | if not isinstance(type, UserActionType):
451 | raise TypeError('expected UserActionType')
452 | error = ErrorDetails()
453 | libmyo.libmyo_myo_notify_user_action(self._handle, int(type), error.handle)
454 | error.raise_for_kind()
455 |
456 |
457 | class String(_BaseWrapper):
458 |
459 | def __str__(self):
460 | return ffi.string(libmyo.libmyo_string_c_str(self._handle)).decode('utf8')
461 |
462 | def __del__(self):
463 | libmyo.libmyo_string_free(self._handle)
464 |
465 |
466 | class Hub(_BaseWrapper):
467 | """
468 | Low-level wrapper for a Myo Hub object.
469 | """
470 |
471 | def __init__(self, application_identifier='com.niklasrosenstein.myo-python'):
472 | super(Hub, self).__init__(ffi.new('libmyo_hub_t*'))
473 | error = ErrorDetails()
474 | libmyo.libmyo_init_hub(self._handle, application_identifier.encode('ascii'), error.handle)
475 | error.raise_for_kind()
476 | self.locking_policy = LockingPolicy.none
477 | self._lock = threading.Lock()
478 | self._running = False
479 | self._stop_requested = False
480 | self._stopped = False
481 |
482 | def __del__(self):
483 | if self._handle[0]:
484 | error = ErrorDetails()
485 | libmyo.libmyo_shutdown_hub(self._handle[0], error.handle)
486 | error.raise_for_kind()
487 |
488 | @property
489 | def handle(self):
490 | return self._handle
491 |
492 | @property
493 | def locking_policy(self):
494 | return self._locking_policy
495 |
496 | @locking_policy.setter
497 | def locking_policy(self, policy):
498 | if not isinstance(policy, LockingPolicy):
499 | raise TypeError('expected LockingPolicy')
500 | error = ErrorDetails()
501 | libmyo.libmyo_set_locking_policy(self._handle[0], int(policy), error.handle)
502 | error.raise_for_kind()
503 |
504 | @property
505 | def running(self):
506 | with self._lock:
507 | return self._running
508 |
509 | def run(self, handler, duration_ms):
510 | """
511 | Runs the *handler* function for *duration_ms* milliseconds. The function
512 | must accept exactly one argument which is an #Event object. The handler
513 | must return either a #HandlerResult value, #False, #True or #None, whereas
514 | #False represents #HandlerResult.stop and #True and #None represent
515 | #HandlerResult.continue_.
516 |
517 | If the run did not complete due to the handler returning #HandlerResult.stop
518 | or #False or the procedure was cancelled via #Hub.stop(), this function
519 | returns #False. If the full *duration_ms* completed, #True is returned.
520 |
521 | This function blocks the caller until either *duration_ms* passed, the
522 | handler returned #HandlerResult.stop or #False or #Hub.stop() was called.
523 | """
524 |
525 | if not callable(handler):
526 | if hasattr(handler, 'on_event'):
527 | handler = handler.on_event
528 | else:
529 | raise TypeError('expected callable or DeviceListener')
530 |
531 | with self._lock:
532 | if self._running:
533 | raise RuntimeError('a handler is already running in the Hub')
534 | self._running = True
535 | self._stop_requested = False
536 | self._stopped = False
537 |
538 | exc_box = []
539 |
540 | def callback_on_error(*exc_info):
541 | exc_box.append(exc_info)
542 | with self._lock:
543 | self._stopped = True
544 | return HandlerResult.stop
545 |
546 | def callback(_, event):
547 | with self._lock:
548 | if self._stop_requested:
549 | self._stopped = True
550 | return HandlerResult.stop
551 |
552 | result = handler(Event(event))
553 | if result is None or result is True:
554 | result = HandlerResult.continue_
555 | elif result is False:
556 | result = HandlerResult.stop
557 | else:
558 | result = HandlerResult(result)
559 | if result == HandlerResult.stop:
560 | with self._lock:
561 | self._stopped = True
562 |
563 | return result
564 |
565 | cdecl = 'libmyo_handler_result_t(void*, libmyo_event_t)'
566 | callback = ffi.callback(cdecl, callback, onerror=callback_on_error)
567 |
568 | try:
569 | error = ErrorDetails()
570 | libmyo.libmyo_run(self._handle[0], duration_ms, callback, ffi.NULL, error.handle)
571 | error.raise_for_kind()
572 | if exc_box:
573 | six.reraise(*exc_box[0])
574 | finally:
575 | with self._lock:
576 | self._running = False
577 | result = not self._stopped
578 |
579 | return result
580 |
581 | def run_forever(self, handler, duration_ms=500):
582 | while self.run(handler, duration_ms):
583 | if self._stop_requested:
584 | break
585 |
586 | @contextlib.contextmanager
587 | def run_in_background(self, handler, duration_ms=500):
588 | thread = threading.Thread(target=lambda: self.run_forever(handler, duration_ms))
589 | thread.start()
590 | try:
591 | yield thread
592 | finally:
593 | self.stop()
594 |
595 | def stop(self):
596 | with self._lock:
597 | self._stop_requested = True
598 |
599 |
600 | __all__ = [
601 | 'Error', 'ResultError', 'InvalidOperation',
602 |
603 | 'Result', 'VibrationType', 'StreamEmg', 'Pose', 'EventType', 'LockingPolicy',
604 | 'Arm', 'XDirection', 'UnlockType', 'UserActionType', 'WarmupState',
605 | 'WarmupResult',
606 |
607 | 'Event', 'Device', 'Hub', 'init'
608 | ]
609 |
--------------------------------------------------------------------------------
/myo/libmyo.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2013-2014 Thalmic Labs Inc.
2 | // Distributed under the Myo SDK license agreement. See LICENSE.txt for details.
3 | #ifndef MYO_LIBMYO_H
4 | #define MYO_LIBMYO_H
5 |
6 | #include
7 |
8 | #include "libmyo/detail/visibility.h"
9 |
10 | #ifdef __cplusplus
11 | extern "C" {
12 | #endif
13 |
14 | /// @file libmyo.h
15 | /// libmyo C API declarations.
16 |
17 | typedef void* libmyo_hub_t;
18 |
19 | /// \defgroup errors Error Handling
20 | /// @{
21 |
22 | /// Function result codes.
23 | /// All libmyo functions that can fail return a value of this type.
24 | typedef enum {
25 | libmyo_success,
26 | libmyo_error,
27 | libmyo_error_invalid_argument,
28 | libmyo_error_runtime
29 | } libmyo_result_t;
30 |
31 | /// Opaque handle to detailed error information.
32 | typedef void* libmyo_error_details_t;
33 |
34 | /// Return a null-terminated string with a detailed error message.
35 | LIBMYO_EXPORT
36 | const char* libmyo_error_cstring(libmyo_error_details_t);
37 |
38 | /// Returns the kind of error that occurred.
39 | LIBMYO_EXPORT
40 | libmyo_result_t libmyo_error_kind(libmyo_error_details_t);
41 |
42 | /// Free the resources allocated by an error object.
43 | LIBMYO_EXPORT
44 | void libmyo_free_error_details(libmyo_error_details_t);
45 |
46 | /// @}
47 |
48 | /// \defgroup libmyo_string Strings
49 | /// @{
50 |
51 | // Opaque string.
52 | typedef void* libmyo_string_t;
53 |
54 | // Return a null-terminated string from the opaque string.
55 | LIBMYO_EXPORT
56 | const char* libmyo_string_c_str(libmyo_string_t);
57 |
58 | // Free the resources allocated by the string object.
59 | LIBMYO_EXPORT
60 | void libmyo_string_free(libmyo_string_t);
61 |
62 | /// @}
63 |
64 | /// \defgroup libmyo_direct_mac_addresses MAC address utilities
65 | /// @{
66 |
67 | /// Retrieve the string representation of a MAC address in hex.
68 | /// Returns a string in the format of 00-00-00-00-00-00.
69 | LIBMYO_EXPORT
70 | libmyo_string_t libmyo_mac_address_to_string(uint64_t);
71 |
72 | /// Retrieve the MAC address from a null-terminated string in the format of 00-00-00-00-00-00.
73 | /// Returns 0 if the string does not match the format.
74 | LIBMYO_EXPORT
75 | uint64_t libmyo_string_to_mac_address(const char*);
76 |
77 | /// @}
78 |
79 | /// @defgroup libmyo_hub Hub instance
80 | /// @{
81 |
82 | /// Initialize a connection to the hub.
83 | /// \a application_identifier must follow a reverse domain name format (ex. com.domainname.appname). Application
84 | /// identifiers can be formed from the set of alphanumeric ASCII characters (a-z, A-Z, 0-9). The hyphen (-) and
85 | /// underscore (_) characters are permitted if they are not adjacent to a period (.) character (i.e. not at the start or
86 | /// end of each segment), but are not permitted in the top-level domain. Application identifiers must have three or more
87 | /// segments. For example, if a company's domain is example.com and the application is named hello-world, one could use
88 | /// "com.example.hello-world" as a valid application identifier. \a application_identifier can be NULL or empty.
89 | /// @returns libmyo_success if the connection is successfully established, otherwise:
90 | /// - libmyo_error_runtime if a connection could not be established
91 | /// - libmyo_error_invalid_argument if \a out_hub is NULL
92 | /// - libmyo_error_invalid_argument if \a application_identifier is longer than 255 characters
93 | /// - libmyo_error_invalid_argument if \a application_identifier is not in the proper reverse domain name format
94 | LIBMYO_EXPORT
95 | libmyo_result_t libmyo_init_hub(libmyo_hub_t* out_hub, const char* application_identifier,
96 | libmyo_error_details_t* out_error);
97 |
98 | /// Free the resources allocated to a hub.
99 | /// @returns libmyo_success if shutdown is successful, otherwise:
100 | /// - libmyo_error_invalid_argument if \a hub is NULL
101 | /// - libmyo_error if \a hub is not a valid hub
102 | LIBMYO_EXPORT
103 | libmyo_result_t libmyo_shutdown_hub(libmyo_hub_t hub, libmyo_error_details_t* out_error);
104 |
105 | // Locking policies.
106 | typedef enum {
107 | libmyo_locking_policy_none, ///< Pose events are always sent.
108 | libmyo_locking_policy_standard ///< Pose events are not sent while a Myo is locked.
109 | } libmyo_locking_policy_t;
110 |
111 | /// Set the locking policy for Myos connected to the hub.
112 | /// @returns libmyo_success if the locking policy is successfully set, otherwise
113 | /// - libmyo_error_invalid_argument if \a hub is NULL
114 | /// - libmyo_error if \a hub is not a valid hub
115 | LIBMYO_EXPORT
116 | libmyo_result_t libmyo_set_locking_policy(libmyo_hub_t hub, libmyo_locking_policy_t locking_policy,
117 | libmyo_error_details_t* out_error);
118 |
119 | /// @}
120 |
121 | /// @defgroup libmyo_myo Myo instances
122 | /// @{
123 |
124 | /// Opaque type corresponding to a known Myo device.
125 | typedef void* libmyo_myo_t;
126 |
127 | /// Types of vibration
128 | typedef enum {
129 | libmyo_vibration_short,
130 | libmyo_vibration_medium,
131 | libmyo_vibration_long
132 | } libmyo_vibration_type_t;
133 |
134 | /// Retrieve the MAC address of a Myo.
135 | /// The MAC address is unique to the physical Myo, and is a 48-bit number.
136 | LIBMYO_EXPORT
137 | uint64_t libmyo_get_mac_address(libmyo_myo_t myo);
138 |
139 | /// Vibrate the given myo.
140 | /// Can be called when a Myo is paired.
141 | /// @returns libmyo_success if the Myo successfully vibrated, otherwise
142 | /// - libmyo_error_invalid_argument if \a myo is NULL
143 | LIBMYO_EXPORT
144 | libmyo_result_t libmyo_vibrate(libmyo_myo_t myo, libmyo_vibration_type_t type, libmyo_error_details_t* out_error);
145 |
146 | /// Request the RSSI for a given myo.
147 | /// Can be called when a Myo is paired. A libmyo_event_rssi event will likely be generated with the value of the RSSI.
148 | /// @returns libmyo_success if the Myo successfully got the RSSI, otherwise
149 | /// - libmyo_error_invalid_argument if \a myo is NULL
150 | LIBMYO_EXPORT
151 | libmyo_result_t libmyo_request_rssi(libmyo_myo_t myo, libmyo_error_details_t* out_error);
152 |
153 |
154 | /// Request the battery level for a given Myo.
155 | /// A libmyo_event_battery_level event will be generated with the value of the battery level.
156 | /// @returns libmyo_success if the Myo successfully requested the battery level, otherwise
157 | /// - libmyo_error_invalid_argument if \a myo is NULL
158 | LIBMYO_EXPORT
159 | libmyo_result_t libmyo_request_battery_level(libmyo_myo_t myo_opq, libmyo_error_details_t* out_error);
160 |
161 | /// EMG streaming modes.
162 | typedef enum {
163 | libmyo_stream_emg_disabled, ///< Do not send EMG data.
164 | libmyo_stream_emg_enabled ///< Send EMG data.
165 | } libmyo_stream_emg_t;
166 |
167 | /// Set whether or not to stream EMG data for a given myo.
168 | /// Can be called when a Myo is paired.
169 | /// @returns libmyo_success if the EMG mode was set successfully, otherwise
170 | /// - libmyo_error_invalid_argument if \a myo is NULL
171 | LIBMYO_EXPORT
172 | libmyo_result_t libmyo_set_stream_emg(libmyo_myo_t myo, libmyo_stream_emg_t emg, libmyo_error_details_t* out_error);
173 |
174 | /// @}
175 |
176 | /// @defgroup libmyo_poses Pose recognition.
177 | /// @{
178 |
179 | /// Supported poses.
180 | typedef enum {
181 | libmyo_pose_rest = 0, ///< Rest pose.
182 | libmyo_pose_fist = 1, ///< User is making a fist.
183 | libmyo_pose_wave_in = 2, ///< User has an open palm rotated towards the posterior of their wrist.
184 | libmyo_pose_wave_out = 3, ///< User has an open palm rotated towards the anterior of their wrist.
185 | libmyo_pose_fingers_spread = 4, ///< User has an open palm with their fingers spread away from each other.
186 | libmyo_pose_double_tap = 5, ///< User tapped their thumb and middle finger together twice in succession.
187 |
188 | libmyo_num_poses, ///< Number of poses supported; not a valid pose.
189 |
190 | libmyo_pose_unknown = 0xffff ///< Unknown pose.
191 | } libmyo_pose_t;
192 |
193 | /// @}
194 |
195 | /// @defgroup libmyo_locking Myo locking mechanism
196 |
197 | /// Valid unlock types.
198 | typedef enum {
199 | libmyo_unlock_timed = 0, ///< Unlock for a fixed period of time.
200 | libmyo_unlock_hold = 1, ///< Unlock until explicitly told to re-lock.
201 | } libmyo_unlock_type_t;
202 |
203 | /// Unlock the given Myo.
204 | /// Can be called when a Myo is paired. A libmyo_event_unlocked event will be generated if the Myo was locked.
205 | /// @returns libmyo_success if the Myo was successfully unlocked, otherwise
206 | /// - libmyo_error_invalid_argument if \a myo is NULL
207 | LIBMYO_EXPORT
208 | libmyo_result_t libmyo_myo_unlock(libmyo_myo_t myo, libmyo_unlock_type_t type, libmyo_error_details_t* out_error);
209 |
210 | /// Lock the given Myo immediately.
211 | /// Can be called when a Myo is paired. A libmyo_event_locked event will be generated if the Myo was unlocked.
212 | /// @returns libmyo_success if the Myo was successfully locked, otherwise
213 | /// - libmyo_error_invalid_argument if \a myo is NULL
214 | LIBMYO_EXPORT
215 | libmyo_result_t libmyo_myo_lock(libmyo_myo_t myo, libmyo_error_details_t* out_error);
216 |
217 | /// User action types.
218 | typedef enum {
219 | libmyo_user_action_single = 0, ///< User did a single, discrete action, such as pausing a video.
220 | } libmyo_user_action_type_t;
221 |
222 | /// Notify the given Myo that a user action was recognized.
223 | /// Can be called when a Myo is paired. Will cause Myo to vibrate.
224 | /// @returns libmyo_success if the Myo was successfully notified, otherwise
225 | /// - libmyo_error_invalid_argument if \a myo is NULL
226 | LIBMYO_EXPORT
227 | libmyo_result_t libmyo_myo_notify_user_action(libmyo_myo_t myo, libmyo_user_action_type_t type,
228 | libmyo_error_details_t* out_error);
229 |
230 | /// @}
231 |
232 | /// @defgroup libmyo_events Event Handling
233 | /// @{
234 |
235 | /// Types of events.
236 | typedef enum {
237 | libmyo_event_paired, ///< Successfully paired with a Myo.
238 | libmyo_event_unpaired, ///< Successfully unpaired from a Myo.
239 | libmyo_event_connected, ///< A Myo has successfully connected.
240 | libmyo_event_disconnected, ///< A Myo has been disconnected.
241 | libmyo_event_arm_synced, ///< A Myo has recognized that the sync gesture has been successfully performed.
242 | libmyo_event_arm_unsynced, ///< A Myo has been moved or removed from the arm.
243 | libmyo_event_orientation, ///< Orientation data has been received.
244 | libmyo_event_pose, ///< A change in pose has been detected. @see libmyo_pose_t.
245 | libmyo_event_rssi, ///< An RSSI value has been received.
246 | libmyo_event_unlocked, ///< A Myo has become unlocked.
247 | libmyo_event_locked, ///< A Myo has become locked.
248 | libmyo_event_emg, ///< EMG data has been received.
249 | libmyo_event_battery_level, ///< A battery level value has been received.
250 | libmyo_event_warmup_completed, ///< The warmup period has completed.
251 | } libmyo_event_type_t;
252 |
253 | /// Information about an event.
254 | typedef const void* libmyo_event_t;
255 |
256 | /// Retrieve the type of an event.
257 | LIBMYO_EXPORT
258 | uint32_t libmyo_event_get_type(libmyo_event_t event);
259 |
260 | /// Retrieve the timestamp of an event.
261 | /// @see libmyo_now() for details on timestamps.
262 | LIBMYO_EXPORT
263 | uint64_t libmyo_event_get_timestamp(libmyo_event_t event);
264 |
265 | /// Retrieve the Myo associated with an event.
266 | LIBMYO_EXPORT
267 | libmyo_myo_t libmyo_event_get_myo(libmyo_event_t event);
268 |
269 | /// Retrieve the MAC address of the myo associated with an event.
270 | LIBMYO_EXPORT
271 | uint64_t libmyo_event_get_mac_address(libmyo_event_t event_opq);
272 |
273 | /// Retrieve the name of the myo associated with an event.
274 | /// Caller must free the returned string. @see libmyo_string functions.
275 | LIBMYO_EXPORT
276 | libmyo_string_t libmyo_event_get_myo_name(libmyo_event_t event);
277 |
278 | /// Components of version.
279 | typedef enum {
280 | libmyo_version_major, ///< Major version.
281 | libmyo_version_minor, ///< Minor version.
282 | libmyo_version_patch, ///< Patch version.
283 | libmyo_version_hardware_rev, ///< Hardware revision.
284 | } libmyo_version_component_t;
285 |
286 | /// Hardware revisions.
287 | typedef enum {
288 | libmyo_hardware_rev_c = 1, ///< Alpha units
289 | libmyo_hardware_rev_d = 2, ///< Consumer units
290 | } libmyo_hardware_rev_t;
291 |
292 | /// Retrieve the Myo armband's firmware version from this event.
293 | /// Valid for libmyo_event_paired and libmyo_event_connected events.
294 | LIBMYO_EXPORT
295 | unsigned int libmyo_event_get_firmware_version(libmyo_event_t event, libmyo_version_component_t);
296 |
297 | /// Enumeration identifying a right arm or left arm. @see libmyo_event_get_arm().
298 | typedef enum {
299 | libmyo_arm_right, ///< Myo is on the right arm.
300 | libmyo_arm_left, ///< Myo is on the left arm.
301 | libmyo_arm_unknown, ///< Unknown arm.
302 | } libmyo_arm_t;
303 |
304 | /// Retrieve the arm associated with an event.
305 | /// Valid for libmyo_event_arm_synced events only.
306 | LIBMYO_EXPORT
307 | libmyo_arm_t libmyo_event_get_arm(libmyo_event_t event);
308 |
309 | /// Possible directions for Myo's +x axis relative to a user's arm.
310 | typedef enum {
311 | libmyo_x_direction_toward_wrist, ///< Myo's +x axis is pointing toward the user's wrist.
312 | libmyo_x_direction_toward_elbow, ///< Myo's +x axis is pointing toward the user's elbow.
313 | libmyo_x_direction_unknown, ///< Unknown +x axis direction.
314 | } libmyo_x_direction_t;
315 |
316 | /// Retrieve the x-direction associated with an event.
317 | /// The x-direction specifies which way Myo's +x axis is pointing relative to the user's arm.
318 | /// Valid for libmyo_event_arm_synced events only.
319 | LIBMYO_EXPORT
320 | libmyo_x_direction_t libmyo_event_get_x_direction(libmyo_event_t event);
321 |
322 | /// Possible warmup states for Myo.
323 | typedef enum {
324 | libmyo_warmup_state_unknown = 0, ///< Unknown warm up state.
325 | libmyo_warmup_state_cold = 1, ///< Myo needs to warm up.
326 | libmyo_warmup_state_warm = 2, ///< Myo is already in a warmed up state.
327 | } libmyo_warmup_state_t;
328 |
329 | /// Retrieve the warmup state of the Myo associated with an event.
330 | /// Valid for libmyo_event_arm_synced events only.
331 | LIBMYO_EXPORT
332 | libmyo_warmup_state_t libmyo_event_get_warmup_state(libmyo_event_t event);
333 |
334 | /// Possible warmup results for Myo.
335 | typedef enum {
336 | libmyo_warmup_result_unknown = 0, ///< Unknown warm up result.
337 | libmyo_warmup_result_success = 1, ///< The warm up period has completed successfully.
338 | libmyo_warmup_result_failed_timeout = 2, ///< The warm up period timed out.
339 | } libmyo_warmup_result_t;
340 |
341 | /// Retrieve the warmup result of the Myo associated with an event.
342 | /// Valid for libmyo_event_warmup_completed events only.
343 | LIBMYO_EXPORT
344 | libmyo_warmup_result_t libmyo_event_get_warmup_result(libmyo_event_t event);
345 |
346 | /// Retrieve the estimated rotation of Myo on the user's arm after a sync.
347 | /// The values specifies the rotation of the myo on the arm (0 - logo facing down, pi - logo facing up)
348 | /// Only supported by FW 1.3.x and above (older firmware will always report 0 for the rotation)
349 | /// Valid for libmyo_event_arm_synced events only.
350 | LIBMYO_EXPORT
351 | float libmyo_event_get_rotation_on_arm(libmyo_event_t event);
352 |
353 | /// Index into orientation data, which is provided as a quaternion.
354 | /// Orientation data is returned as a unit quaternion of floats, represented as `w + x * i + y * j + z * k`.
355 | typedef enum {
356 | libmyo_orientation_x = 0, ///< First component of the quaternion's vector part
357 | libmyo_orientation_y = 1, ///< Second component of the quaternion's vector part
358 | libmyo_orientation_z = 2, ///< Third component of the quaternion's vector part
359 | libmyo_orientation_w = 3, ///< Scalar component of the quaternion.
360 | } libmyo_orientation_index;
361 |
362 | /// Retrieve orientation data associated with an event.
363 | /// Valid for libmyo_event_orientation events only.
364 | /// @see libmyo_orientation_index
365 | LIBMYO_EXPORT
366 | float libmyo_event_get_orientation(libmyo_event_t event, libmyo_orientation_index index);
367 |
368 | /// Retrieve raw accelerometer data associated with an event in units of g.
369 | /// Valid for libmyo_event_orientation events only.
370 | /// Requires `index < 3`.
371 | LIBMYO_EXPORT
372 | float libmyo_event_get_accelerometer(libmyo_event_t event, unsigned int index);
373 |
374 | /// Retrieve raw gyroscope data associated with an event in units of deg/s.
375 | /// Valid for libmyo_event_orientation events only.
376 | /// Requires `index < 3`.
377 | LIBMYO_EXPORT
378 | float libmyo_event_get_gyroscope(libmyo_event_t event, unsigned int index);
379 |
380 | /// Retrieve the pose associated with an event.
381 | /// Valid for libmyo_event_pose events only.
382 | LIBMYO_EXPORT
383 | libmyo_pose_t libmyo_event_get_pose(libmyo_event_t event);
384 |
385 | /// Retreive the RSSI associated with an event.
386 | /// Valid for libmyo_event_rssi events only.
387 | LIBMYO_EXPORT
388 | int8_t libmyo_event_get_rssi(libmyo_event_t event);
389 |
390 | /// Retrieve the battery level of the Myo armband associated with an event.
391 | /// Only valid for libmyo_event_battery_level event.
392 | LIBMYO_EXPORT
393 | uint8_t libmyo_event_get_battery_level(libmyo_event_t event);
394 |
395 | /// Retrieve an EMG data point associated with an event.
396 | /// Valid for libmyo_event_emg events only.
397 | /// @a sensor must be smaller than 8.
398 | LIBMYO_EXPORT
399 | int8_t libmyo_event_get_emg(libmyo_event_t event, unsigned int sensor);
400 |
401 | /// Return type for event handlers.
402 | typedef enum {
403 | libmyo_handler_continue, ///< Continue processing events
404 | libmyo_handler_stop, ///< Stop processing events
405 | } libmyo_handler_result_t;
406 |
407 | /// Callback function type to handle events as they occur from libmyo_run().
408 | typedef libmyo_handler_result_t (*libmyo_handler_t)(void* user_data, libmyo_event_t event);
409 |
410 | /// Process events and call the provided callback as they occur.
411 | /// Runs for up to approximately \a duration_ms milliseconds or until a called handler returns libmyo_handler_stop.
412 | /// @returns libmyo_success after a successful run, otherwise
413 | /// - libmyo_error_invalid_argument if \a hub is NULL
414 | /// - libmyo_error_invalid_argument if \a handler is NULL
415 | LIBMYO_EXPORT
416 | libmyo_result_t libmyo_run(libmyo_hub_t hub, unsigned int duration_ms, libmyo_handler_t handler, void* user_data,
417 | libmyo_error_details_t* out_error);
418 |
419 | /// @}
420 |
421 | #ifdef __cplusplus
422 | } // extern "C"
423 | #endif
424 |
425 | #endif // MYO_LIBMYO_H
426 |
--------------------------------------------------------------------------------
/myo/macaddr.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-2018 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 |
23 | import six
24 |
25 | MAX_VALUE = (16 ** 12 - 1)
26 |
27 |
28 | def encode(value):
29 | """
30 | Encodes the number *value* to a MAC address ASCII string in binary form.
31 | Raises a #ValueError if *value* is a negative number or exceeds the MAC
32 | address range.
33 | """
34 |
35 | if value > MAX_VALUE:
36 | raise ValueError('value {!r} exceeds MAC address range'.format(value))
37 | if value < 0:
38 | raise ValueError('value must not be negative')
39 |
40 | # todo: convert to the right byte order. the resulting
41 | # mac address is reversed on my machine compared to the
42 | # mac address displayed by the hello-myo SDK sample.
43 | # See issue #7
44 |
45 | string = ('%x' % value).rjust(12, '0')
46 | assert len(string) == 12
47 |
48 | result = ':'.join(''.join(pair) for pair in zip(*[iter(string)]*2))
49 | return result.upper()
50 |
51 |
52 | def decode(bstr):
53 | """
54 | Decodes an ASCII encoded binary MAC address tring into a number.
55 | """
56 |
57 | bstr = bstr.replace(b':', b'')
58 | if len(bstr) != 12:
59 | raise ValueError('not a valid MAC address: {!r}'.format(bstr))
60 |
61 | try:
62 | return int(bstr, 16)
63 | except ValueError:
64 | raise ValueError('not a valid MAC address: {!r}'.format(bstr))
65 |
66 |
67 | class MacAddress(object):
68 | """
69 | Represents a MAC address. Instances of this class are immutable.
70 | """
71 |
72 | def __init__(self, value):
73 | if isinstance(value, six.integer_types):
74 | if value < 0 or value > MAX_VALUE:
75 | raise ValueError('value {!r} out of MAC address range'.format(value))
76 | elif isinstance(value, six.string_to_int):
77 | if isinstance(value, six.text_type):
78 | value = value.encode('ascii')
79 | value = decode(value)
80 | else:
81 | msg = 'expected string, bytes or int for MacAddress, got {}'
82 | return TypeError(msg.format(type(value).__name__))
83 |
84 | self._value = value
85 | self._string = None
86 |
87 | def __str__(self):
88 | if self._string is None:
89 | self._string = encode(self._value)
90 | return self._string
91 |
92 | def __repr__(self):
93 | return ''.format(self)
94 |
95 | @property
96 | def value(self):
97 | return self._value
98 |
--------------------------------------------------------------------------------
/myo/math.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-2018 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 |
23 | import math
24 | import six
25 |
26 |
27 | class Vector(object):
28 | """
29 | A three-dimensional vector.
30 | """
31 |
32 | __slots__ = ('x', 'y', 'z')
33 |
34 | def __init__(self, x, y, z):
35 | super(Vector, self).__init__()
36 | self.x = float(x)
37 | self.y = float(y)
38 | self.z = float(z)
39 |
40 | def __mul__(self, rhs):
41 | """
42 | Multiplies the vector with *rhs* which can be either a scalar
43 | to retrieve a new Vector or another vector to compute the dot
44 | product.
45 | """
46 |
47 | if isinstance(rhs, (six.integer_types, float)):
48 | return Vector(self.x * rhs, self.y * rhs, self.z * rhs)
49 | else:
50 | return self.dot(rhs)
51 |
52 | def __add__(self, rhs):
53 | """
54 | Adds *self* to *rhs* and returns a new vector.
55 | """
56 |
57 | if isinstance(rhs, (six.integer_types, float)):
58 | return Vector(self.x + rhs, self.y + rhs, self.z + rhs)
59 | else:
60 | return Vector(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
61 |
62 | def __sub__(self, rhs):
63 | """
64 | Substracts *self* from *rhs* and returns a new vector.
65 | """
66 |
67 | if isinstance(rhs, (six.integer_types, float)):
68 | return Vector(self.x - rhs, self.y - rhs, self.z - rhs)
69 | else:
70 | return Vector(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
71 |
72 | def __iter__(self):
73 | return iter((self.x, self.y, self.z))
74 |
75 | def __repr__(self):
76 | return 'Vector({0}, {1}, {2})'.format(self.x, self.y, self.z)
77 |
78 | def __invert__(self):
79 | """
80 | Returns the inversion of the vector.
81 | """
82 |
83 | return Vector(-self.x, -self.y, -self.z)
84 |
85 | def __getitem__(self, index):
86 | return (self.x, self.y, self.z)[index]
87 |
88 | def copy(self):
89 | """
90 | Returns a shallow copy of the vector.
91 | """
92 |
93 | return Vector(self.x, self.y, self.z)
94 |
95 | def magnitude(self):
96 | """
97 | Return the magnitude of this vector.
98 | """
99 |
100 | return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)
101 |
102 | def normalized(self):
103 | """
104 | Returns a normalized copy of this vector.
105 | """
106 |
107 | norm = self.magnitude()
108 | return Vector(self.x / norm, self.y / norm, self.z / norm)
109 |
110 | def dot(self, rhs):
111 | """
112 | Return the dot product of this vector and *rhs*.
113 | """
114 |
115 | return self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
116 |
117 | def cross(self, rhs):
118 | """
119 | Return the cross product of this vector and *rhs*.
120 | """
121 |
122 | return Vector(
123 | self.y * rhs.z - self.z * rhs.y,
124 | self.z * rhs.x - self.x * rhs.z,
125 | self.x * rhs.y - self.y * rhs.x)
126 |
127 | def angle_to(self, rhs):
128 | """
129 | Return the angle between this vector and *rhs* in radians.
130 | """
131 |
132 | return math.acos(self.dot(rhs) / (self.magnitude() * rhs.magnitude()))
133 |
134 | __abs__ = magnitude
135 |
136 |
137 | class Quaternion(object):
138 | """
139 | This class represents a quaternion which can be used to represent
140 | gimbal-lock free rotations.
141 |
142 | This implementation can work with any vector type that has members
143 | x, y and z and it has a constructor that accepts values for these
144 | members in order. This is convenient when combining the Myo SDK
145 | with other 3D APIs that provide a vector class.
146 | """
147 |
148 | __slots__ = ('x', 'y', 'z', 'w')
149 |
150 | def __init__(self, x, y, z, w):
151 | super(Quaternion, self).__init__()
152 | self.x = float(x)
153 | self.y = float(y)
154 | self.z = float(z)
155 | self.w = float(w)
156 |
157 | def __mul__(self, rhs):
158 | """
159 | Multiplies *self* with the #Quaternion *rhs* and returns a new #Quaternion.
160 | """
161 |
162 | if not isinstance(rhs, Quaternion):
163 | raise TypeError('can only multiply with Quaternion')
164 | return Quaternion(
165 | self.w * rhs.x + self.x * rhs.w + self.y * rhs.z - self.z * rhs.y,
166 | self.w * rhs.y - self.x * rhs.z + self.y * rhs.w + self.z * rhs.x,
167 | self.w * rhs.z + self.x * rhs.y - self.y * rhs.x + self.z * rhs.w,
168 | self.w * rhs.w - self.x * rhs.x - self.y * rhs.y - self.z * rhs.z)
169 |
170 | def __iter__(self):
171 | return iter((self.x, self.y, self.z, self.w))
172 |
173 | def __repr__(self):
174 | return '{0}({1}, {2}, {3}, {4})'.format(
175 | type(self).__name__, self.x, self.y, self.z, self.w)
176 |
177 | def __invert__(self):
178 | """
179 | Returns this Quaternion's conjugate.
180 | """
181 |
182 | return Quaternion(-self.x, -self.y, -self.z, self.w)
183 |
184 | def __getitem__(self, index):
185 | return (self.x, self.y, self.z, self.w)[index]
186 |
187 | def copy(self):
188 | """
189 | Returns a shallow copy of the quaternion.
190 | """
191 |
192 | return Quaternion(self.x, self.y, self.z, self.w)
193 |
194 | def magnitude(self):
195 | """
196 | Returns the magnitude of the quaternion.
197 | """
198 |
199 | return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2 + self.w ** 2)
200 |
201 | def normalized(self):
202 | """
203 | Returns the unit quaternion corresponding to the same rotation
204 | as this one.
205 | """
206 |
207 | magnitude = self.magnitude()
208 | return Quaternion(
209 | self.x / magnitude, self.y / magnitude,
210 | self.z / magnitude, self.w / magnitude)
211 |
212 | conjugate = __invert__
213 |
214 | def rotate(self, vec):
215 | """
216 | Returns *vec* rotated by this #Quaternion.
217 |
218 | :param vec: A vector object.
219 | :return: object of type of *vec*
220 | """
221 |
222 | qvec = self * Quaternion(vec.x, vec.y, vec.z, 0) * ~self
223 | return type(vec)(qvec.x, qvec.y, qvec.z)
224 |
225 | # Reference:
226 | # http://answers.unity3d.com/questions/416169/finding-pitchrollyaw-from-quaternions.html
227 |
228 | @property
229 | def roll(self):
230 | """ Calculates the Roll of the Quaternion. """
231 |
232 | x, y, z, w = self.x, self.y, self.z, self.w
233 | return math.atan2(2*y*w - 2*x*z, 1 - 2*y*y - 2*z*z)
234 |
235 | @property
236 | def pitch(self):
237 | """ Calculates the Pitch of the Quaternion. """
238 |
239 | x, y, z, w = self.x, self.y, self.z, self.w
240 | return math.atan2(2*x*w - 2*y*z, 1 - 2*x*x - 2*z*z)
241 |
242 | @property
243 | def yaw(self):
244 | """ Calculates the Yaw of the Quaternion. """
245 |
246 | x, y, z, w = self.x, self.y, self.z, self.w
247 | return math.asin(2*x*y + 2*z*w)
248 |
249 | @property
250 | def rpy(self):
251 | """ Calculates the Roll, Pitch and Yaw of the Quaternion. """
252 |
253 | x, y, z, w = self.x, self.y, self.z, self.w
254 | roll = math.atan2(2*y*w - 2*x*z, 1 - 2*y*y - 2*z*z)
255 | pitch = math.atan2(2*x*w - 2*y*z, 1 - 2*x*x - 2*z*z)
256 | yaw = math.asin(2*x*y + 2*z*w)
257 | return (roll, pitch, yaw)
258 |
259 | @staticmethod
260 | def identity():
261 | """
262 | Returns the identity #Quaternion.
263 | """
264 |
265 | return Quaternion(0, 0, 0, 1)
266 |
267 | @staticmethod
268 | def rotation_of(source, dest):
269 | """
270 | Returns a #Quaternion that represents a rotation from vector
271 | *source* to *dest*.
272 | """
273 |
274 | source = Vector(source.x, source.y, source.z)
275 | dest = Vector(dest.x, dest.y, dest.z)
276 | cross = source.cross(dest)
277 | cos_theta = source.dot(dest)
278 |
279 | # Return identity if the vectors are the same direction.
280 | if cos_theta >= 1.0:
281 | return Quaternion.identity()
282 |
283 | # Product of the square of the magnitudes.
284 | k = math.sqrt(source.dot(source), dest.dot(dest))
285 |
286 | # Return identity in the degenerate case.
287 | if k <= 0.0:
288 | return Quaternion.identity()
289 |
290 | # Special handling for vectors facing opposite directions.
291 | if cos_theta / k <= -1:
292 | x_axis = Vector(1, 0, 0)
293 | y_axis = Vector(0, 1, 1)
294 | if abs(source.dot(x_ais)) < 1.0:
295 | cross = source.cross(x_axis)
296 | else:
297 | cross = source.cross(y_axis)
298 |
299 | return Quaternion(cross.x, cross.y, cross.z, k + cos_theta)
300 |
301 | @staticmethod
302 | def from_axis_angle(axis, angle):
303 | """
304 | Returns a #Quaternion that represents the right-handed
305 | rotation of *angle* radians about the givne *axis*.
306 |
307 | :param axis: The unit vector representing the axis of rotation.
308 | :param angle: The angle of rotation, in radians.
309 | """
310 |
311 | sincomp = math.sin(angle / 2.0)
312 | return Quaternion(
313 | axis.x * sincomp, axis.y * sincomp,
314 | axis.z * sincomp, math.cos(angle / 2.0))
315 |
--------------------------------------------------------------------------------
/myo/types/__init__.py:
--------------------------------------------------------------------------------
1 | # backwards compatibility with versions 1.0.0 - 1.0.3
2 |
--------------------------------------------------------------------------------
/myo/types/macaddr.py:
--------------------------------------------------------------------------------
1 | from ..macaddr import *
--------------------------------------------------------------------------------
/myo/types/math.py:
--------------------------------------------------------------------------------
1 | from ..math import *
--------------------------------------------------------------------------------
/myo/utils.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-2018 Niklas Rosenstein
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to
7 | # deal in the Software without restriction, including without limitation the
8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | # sell copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | # IN THE SOFTWARE.
22 |
23 |
24 | import time
25 |
26 |
27 | class TimeInterval(object):
28 | """
29 | A helper class to keep track of a time interval.
30 | """
31 |
32 | def __init__(self, value, value_on_reset=None, clock=None):
33 | self.value = value
34 | self.value_on_reset = value_on_reset
35 | self.clock = clock or time.perf_counter
36 | self.start = self.clock
37 |
38 | def check(self):
39 | """
40 | Returns #True if the time interval has passed.
41 | """
42 |
43 | if self.value is None:
44 | return True
45 | return (self.clock() - self.start) >= self.value
46 |
47 | def reset(self, value=None):
48 | """
49 | Resets the start time of the interval to now or the specified value.
50 | """
51 |
52 | if value is None:
53 | value = self.clock()
54 | self.start = value
55 | if self.value_on_reset:
56 | self.value = self.value_on_reset
57 |
58 | def check_and_reset(self, value=None):
59 | """
60 | Combination of #check() and #reset().
61 | """
62 |
63 | if self.check():
64 | self.reset(value)
65 | return True
66 | return False
67 |
68 |
69 | class TimeoutManager(TimeInterval):
70 |
71 | def check(self):
72 | """
73 | Returns #True if the timeout is exceeded.
74 | """
75 |
76 | if self.value is None:
77 | return False
78 | return (self.clock() - self.start) >= self.value
79 |
80 | def remainder(self, max_value=None):
81 | """
82 | Returns the time remaining for the timeout, or *max_value* if that
83 | remainder is larger.
84 | """
85 |
86 | if self.value is None:
87 | return max_value
88 | remainder = self.value - (self.clock() - self.start)
89 | if remainder < 0.0:
90 | return 0.0
91 | elif max_value is not None and remainder > max_value:
92 | return max_value
93 | else:
94 | return remainder
95 |
--------------------------------------------------------------------------------
/package.yml:
--------------------------------------------------------------------------------
1 | name: myo-python
2 | version: 1.0.5
3 | author: Niklas Rosenstein
4 | modulename: myo
5 | description: Python bindings for the Thalmic Labs Myo SDK.
6 | url: https://github.com/NiklasRosenstein/myo-python
7 | license: MIT
8 | source-directory: .
9 | requirements:
10 | - cffi ^1.11.5
11 | - python ^3.5
12 | - six ^1.11.0
13 | package-data:
14 | - include: libmyo.h
15 | test-drivers:
16 | - type: pytest
17 | publish:
18 | pypi:
19 | credentials:
20 | username: __token__
21 | password: $PYPI_TOKEN
22 | test_username: __token__
23 | test_password: $TEST_PYPI_TOKEN
24 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # This file was auto-generated by Shut. DO NOT EDIT
2 | # For more information about Shut, check out https://pypi.org/project/shut/
3 |
4 | from __future__ import print_function
5 | import io
6 | import os
7 | import setuptools
8 | import sys
9 |
10 | readme_file = 'README.md'
11 | if os.path.isfile(readme_file):
12 | with io.open(readme_file, encoding='utf8') as fp:
13 | long_description = fp.read()
14 | else:
15 | print("warning: file \"{}\" does not exist.".format(readme_file), file=sys.stderr)
16 | long_description = None
17 |
18 | requirements = [
19 | 'cffi >=1.11.5,<2.0.0',
20 | 'six >=1.11.0,<2.0.0',
21 | ]
22 |
23 | setuptools.setup(
24 | name = 'myo-python',
25 | version = '1.0.5',
26 | author = 'Niklas Rosenstein',
27 | author_email = 'rosensteinniklas@gmail.com',
28 | description = 'Python bindings for the Thalmic Labs Myo SDK.',
29 | long_description = long_description,
30 | long_description_content_type = 'text/markdown',
31 | url = 'https://github.com/NiklasRosenstein/myo-python',
32 | license = 'MIT',
33 | packages = setuptools.find_packages('.', ['test', 'test.*', 'tests', 'tests.*', 'docs', 'docs.*']),
34 | package_dir = {'': '.'},
35 | include_package_data = True,
36 | install_requires = requirements,
37 | extras_require = {},
38 | tests_require = [],
39 | python_requires = '>=3.5.0,<4.0.0',
40 | data_files = [],
41 | entry_points = {},
42 | cmdclass = {},
43 | keywords = [],
44 | classifiers = [],
45 | zip_safe = True,
46 | )
47 |
--------------------------------------------------------------------------------
/test/test_import.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | def test_import():
4 | import myo
5 | import myo.types
6 |
--------------------------------------------------------------------------------