openble-muse 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ readings.jsonl
6
+ .philips-scale.json
7
+ .philips-scale-*.json
8
+ data/
9
+ dist/
10
+ build/
11
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kadima Tech
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 deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ 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 all
13
+ 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 FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.5
2
+ Name: openble-muse
3
+ Version: 0.1.0
4
+ Summary: Interaxon Muse EEG headband BLE protocol: command framing, JSON replies, the three data layouts and a command link, without a Bluetooth stack.
5
+ Project-URL: Source, https://github.com/kadima-tech/openble/tree/main/packages/openble-muse
6
+ Project-URL: Issues, https://github.com/kadima-tech/openble/issues
7
+ Author: Kadima Tech
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ble,bluetooth,eeg,muse
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: System :: Hardware
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+
21
+ # openble-muse
22
+
23
+ The Interaxon Muse EEG headband BLE protocol, read from the Muse Android app (55.1), whose protocol library is libmuse, and from public projects (muse-js, muse-lsl, amused, OpenMuse, muse-rs).
24
+
25
+ - `openble.muse.uuids`: service `0xFE8D` and the `273eXXXX` characteristics, named as the app names them.
26
+ - `openble.muse.models`: the model from a serial number, the characteristics each model streams on, default presets and the known preset codes.
27
+ - `openble.muse.identify`: matches advertisements and reads the hardware generation from the manufacturer data.
28
+ - `openble.muse.commands`: the length-prefixed ASCII commands (`v7`, `s`, `h`, `p21`, `dc001`, …).
29
+ - `openble.muse.replies`: reassembles the chunked JSON replies and reads the version and status replies.
30
+ - `openble.muse.packets`, `universal`, `ble4`: the three data layouts, selected by the protocol version (`pv`) the headband reports. All three decode to the same `Reading`.
31
+ - `openble.muse.link`: `MuseLink` keeps one command in flight, as the app does, and `prepare` runs the app's start-up sequence.
32
+
33
+ Pure Python 3.11+ with no dependencies and no Bluetooth stack. `MuseLink` takes a `write` coroutine and is fed notifications, so it runs over bleak, a test double, or anything else.
34
+
35
+ The library is built from static reading of the app and from public work, checked against one live headband: a Muse S on firmware 1.0.1 (`pv` 1) over bleak on macOS. Discovery, the start-up sequence, streaming and halting work on it, and its accelerometer, gyro, DRL/REF and battery packets decode; the tests include those bytes. The Muse S Athena (BLE4) and universal v3 decoders are still checked only against hand-packed bytes. The manual records what the run showed and what is still open, including the BLE4 time-sync message that firmware with `pv` 5 and up may expect.
36
+
37
+ ```
38
+ pip install openble-muse
39
+ ```
40
+
41
+ ## Layouts
42
+
43
+ | `pv` | Layout | Headbands | Module |
44
+ | --- | --- | --- | --- |
45
+ | 1–2 | Per-sensor: one characteristic per channel | Muse 2016, Muse 2, Muse S | `packets.decode(uuid, data)` |
46
+ | 3 | Universal v3: typed sub-packets on `273e0013` | early Muse S Athena firmware (inferred) | `universal.parse_frame`, `universal.reading` |
47
+ | ≥ 4 | BLE4: tagged sub-packets on `273e0013` and `273e0014` | Muse S Athena | `ble4.readings(data)` |
48
+
49
+ ```python
50
+ from openble.muse import ble4, commands
51
+
52
+ commands.encode("s").hex(" ")
53
+ # '02 73 0a'
54
+
55
+ for reading in ble4.readings(notification):
56
+ print(reading.kind, reading.channels, reading.samples)
57
+ # eeg ('TP9', 'AF7', 'AF8', 'TP10') ((…), (…), (…), (…))
58
+ ```
59
+
60
+ `samples` has one row per time step and one column per channel. EEG is in microvolts, the IMU in g and degrees per second. Optics and PPG stay raw. Where the app and the public projects scale differently, `openble.muse.readings` keeps both constants.
61
+
62
+ ## Stream with bleak
63
+
64
+ ```python
65
+ import asyncio
66
+
67
+ from bleak import BleakClient
68
+ from openble.muse import ble4, packets, uuids
69
+ from openble.muse.link import MuseLink
70
+
71
+
72
+ async def stream(address: str, seconds: float = 10) -> None:
73
+ async with BleakClient(address) as client:
74
+
75
+ async def write(payload: bytes) -> None:
76
+ await client.write_gatt_char(uuids.SERIAL, payload, response=False)
77
+
78
+ link = MuseLink(write)
79
+ await client.start_notify(uuids.SERIAL, lambda _, data: link.feed(data))
80
+ session = await link.prepare() # v7, s, h, p<preset>, s
81
+ print(session.status.name, session.model, session.layout)
82
+
83
+ def on_data(char, data: bytearray) -> None:
84
+ if session.layout == "ble4":
85
+ found = ble4.readings(data)
86
+ else:
87
+ found = [packets.decode(char.uuid, data)]
88
+ for reading in found:
89
+ print(reading)
90
+
91
+ for uuid in session.subscribe:
92
+ await client.start_notify(uuid, on_data)
93
+ await link.start_streaming() # dc001
94
+ await asyncio.sleep(seconds)
95
+ await link.halt() # stop streaming before disconnecting
96
+ ```
97
+
98
+ `prepare(preset)` takes a preset code such as `"21"` or `"1045"`. Without one it uses the app's default for the model: `21` for most headbands, and `1021` for the Athena. Public Athena clients use `1041`, `1034` or `1045` instead. A headband in its bootloader raises `BootloaderError`; `link.boot_to_headset()` restarts it, and you then reconnect.
99
+
100
+ ## Protocol notes
101
+
102
+ [docs/muse-ble-protocol.md](https://github.com/kadima-tech/openble/blob/main/packages/openble-muse/docs/muse-ble-protocol.md): models, discovery, the GATT layout, the command channel, the connection lifecycle, the three data layouts and the open questions.
103
+
104
+ Not affiliated with Interaxon. The name identifies the devices this interoperates with. Not a medical device.
105
+
106
+ ## License
107
+
108
+ [MIT License](https://github.com/kadima-tech/openble/blob/main/packages/openble-muse/LICENSE), copyright 2026 Kadima Tech.
@@ -0,0 +1,88 @@
1
+ # openble-muse
2
+
3
+ The Interaxon Muse EEG headband BLE protocol, read from the Muse Android app (55.1), whose protocol library is libmuse, and from public projects (muse-js, muse-lsl, amused, OpenMuse, muse-rs).
4
+
5
+ - `openble.muse.uuids`: service `0xFE8D` and the `273eXXXX` characteristics, named as the app names them.
6
+ - `openble.muse.models`: the model from a serial number, the characteristics each model streams on, default presets and the known preset codes.
7
+ - `openble.muse.identify`: matches advertisements and reads the hardware generation from the manufacturer data.
8
+ - `openble.muse.commands`: the length-prefixed ASCII commands (`v7`, `s`, `h`, `p21`, `dc001`, …).
9
+ - `openble.muse.replies`: reassembles the chunked JSON replies and reads the version and status replies.
10
+ - `openble.muse.packets`, `universal`, `ble4`: the three data layouts, selected by the protocol version (`pv`) the headband reports. All three decode to the same `Reading`.
11
+ - `openble.muse.link`: `MuseLink` keeps one command in flight, as the app does, and `prepare` runs the app's start-up sequence.
12
+
13
+ Pure Python 3.11+ with no dependencies and no Bluetooth stack. `MuseLink` takes a `write` coroutine and is fed notifications, so it runs over bleak, a test double, or anything else.
14
+
15
+ The library is built from static reading of the app and from public work, checked against one live headband: a Muse S on firmware 1.0.1 (`pv` 1) over bleak on macOS. Discovery, the start-up sequence, streaming and halting work on it, and its accelerometer, gyro, DRL/REF and battery packets decode; the tests include those bytes. The Muse S Athena (BLE4) and universal v3 decoders are still checked only against hand-packed bytes. The manual records what the run showed and what is still open, including the BLE4 time-sync message that firmware with `pv` 5 and up may expect.
16
+
17
+ ```
18
+ pip install openble-muse
19
+ ```
20
+
21
+ ## Layouts
22
+
23
+ | `pv` | Layout | Headbands | Module |
24
+ | --- | --- | --- | --- |
25
+ | 1–2 | Per-sensor: one characteristic per channel | Muse 2016, Muse 2, Muse S | `packets.decode(uuid, data)` |
26
+ | 3 | Universal v3: typed sub-packets on `273e0013` | early Muse S Athena firmware (inferred) | `universal.parse_frame`, `universal.reading` |
27
+ | ≥ 4 | BLE4: tagged sub-packets on `273e0013` and `273e0014` | Muse S Athena | `ble4.readings(data)` |
28
+
29
+ ```python
30
+ from openble.muse import ble4, commands
31
+
32
+ commands.encode("s").hex(" ")
33
+ # '02 73 0a'
34
+
35
+ for reading in ble4.readings(notification):
36
+ print(reading.kind, reading.channels, reading.samples)
37
+ # eeg ('TP9', 'AF7', 'AF8', 'TP10') ((…), (…), (…), (…))
38
+ ```
39
+
40
+ `samples` has one row per time step and one column per channel. EEG is in microvolts, the IMU in g and degrees per second. Optics and PPG stay raw. Where the app and the public projects scale differently, `openble.muse.readings` keeps both constants.
41
+
42
+ ## Stream with bleak
43
+
44
+ ```python
45
+ import asyncio
46
+
47
+ from bleak import BleakClient
48
+ from openble.muse import ble4, packets, uuids
49
+ from openble.muse.link import MuseLink
50
+
51
+
52
+ async def stream(address: str, seconds: float = 10) -> None:
53
+ async with BleakClient(address) as client:
54
+
55
+ async def write(payload: bytes) -> None:
56
+ await client.write_gatt_char(uuids.SERIAL, payload, response=False)
57
+
58
+ link = MuseLink(write)
59
+ await client.start_notify(uuids.SERIAL, lambda _, data: link.feed(data))
60
+ session = await link.prepare() # v7, s, h, p<preset>, s
61
+ print(session.status.name, session.model, session.layout)
62
+
63
+ def on_data(char, data: bytearray) -> None:
64
+ if session.layout == "ble4":
65
+ found = ble4.readings(data)
66
+ else:
67
+ found = [packets.decode(char.uuid, data)]
68
+ for reading in found:
69
+ print(reading)
70
+
71
+ for uuid in session.subscribe:
72
+ await client.start_notify(uuid, on_data)
73
+ await link.start_streaming() # dc001
74
+ await asyncio.sleep(seconds)
75
+ await link.halt() # stop streaming before disconnecting
76
+ ```
77
+
78
+ `prepare(preset)` takes a preset code such as `"21"` or `"1045"`. Without one it uses the app's default for the model: `21` for most headbands, and `1021` for the Athena. Public Athena clients use `1041`, `1034` or `1045` instead. A headband in its bootloader raises `BootloaderError`; `link.boot_to_headset()` restarts it, and you then reconnect.
79
+
80
+ ## Protocol notes
81
+
82
+ [docs/muse-ble-protocol.md](https://github.com/kadima-tech/openble/blob/main/packages/openble-muse/docs/muse-ble-protocol.md): models, discovery, the GATT layout, the command channel, the connection lifecycle, the three data layouts and the open questions.
83
+
84
+ Not affiliated with Interaxon. The name identifies the devices this interoperates with. Not a medical device.
85
+
86
+ ## License
87
+
88
+ [MIT License](https://github.com/kadima-tech/openble/blob/main/packages/openble-muse/LICENSE), copyright 2026 Kadima Tech.