openble-muse
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).
openble.muse.uuids: service0xFE8Dand the273eXXXXcharacteristics, named as the app names them.openble.muse.models: the model from a serial number, the characteristics each model streams on, default presets and the known preset codes.openble.muse.identify: matches advertisements and reads the hardware generation from the manufacturer data.openble.muse.commands: the length-prefixed ASCII commands (v7,s,h,p21,dc001, …).openble.muse.replies: reassembles the chunked JSON replies and reads the version and status replies.openble.muse.packets,universal,ble4: the three data layouts, selected by the protocol version (pv) the headband reports. All three decode to the sameReading.openble.muse.link:MuseLinkkeeps one command in flight, as the app does, andprepareruns the app's start-up sequence.
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.
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.
pip install openble-muse
Layouts
pv |
Layout | Headbands | Module |
|---|---|---|---|
| 1–2 | Per-sensor: one characteristic per channel | Muse 2016, Muse 2, Muse S | packets.decode(uuid, data) |
| 3 | Universal v3: typed sub-packets on 273e0013 |
early Muse S Athena firmware (inferred) | universal.parse_frame, universal.reading |
| ≥ 4 | BLE4: tagged sub-packets on 273e0013 and 273e0014 |
Muse S Athena | ble4.readings(data) |
from openble.muse import ble4, commands
commands.encode("s").hex(" ")
# '02 73 0a'
for reading in ble4.readings(notification):
print(reading.kind, reading.channels, reading.samples)
# eeg ('TP9', 'AF7', 'AF8', 'TP10') ((…), (…), (…), (…))
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.
Stream with bleak
import asyncio
from bleak import BleakClient
from openble.muse import ble4, packets, uuids
from openble.muse.link import MuseLink
async def stream(address: str, seconds: float = 10) -> None:
async with BleakClient(address) as client:
async def write(payload: bytes) -> None:
await client.write_gatt_char(uuids.SERIAL, payload, response=False)
link = MuseLink(write)
await client.start_notify(uuids.SERIAL, lambda _, data: link.feed(data))
session = await link.prepare() # v7, s, h, p<preset>, s
print(session.status.name, session.model, session.layout)
def on_data(char, data: bytearray) -> None:
if session.layout == "ble4":
found = ble4.readings(data)
else:
found = [packets.decode(char.uuid, data)]
for reading in found:
print(reading)
for uuid in session.subscribe:
await client.start_notify(uuid, on_data)
await link.start_streaming() # dc001
await asyncio.sleep(seconds)
await link.halt() # stop streaming before disconnecting
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.
Protocol notes
docs/muse-ble-protocol.md: models, discovery, the GATT layout, the command channel, the connection lifecycle, the three data layouts and the open questions.
Not affiliated with Interaxon. The name identifies the devices this interoperates with. Not a medical device.
License
MIT License, copyright 2026 Kadima Tech.
Metadata
Release files for openble-muse 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| openble_muse-0.1.0.tar.gz | 38.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openble_muse-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 61.0 kB
Release files / openble_muse-0.1.0.tar.gz
| Download URL | openble_muse-0.1.0.tar.gz |
|---|---|
| Size | 38.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
03b53c17b538e2959ea78c106de0ac56e5a3fe806a56fe0ef176fd39eae3ce61
|
|
BLAKE2b-256 checksum How to use checksums |
4a4eca568b99638be26b9c2bd2965d83801fbcd4ed1b1e620e9705440c576f9d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / openble_muse-0.1.0-py3-none-any.whl
| Download URL | openble_muse-0.1.0-py3-none-any.whl |
|---|---|
| Size | 22.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
20e18921258403b42c76d4f2308a1abcd331f9dc53bfae60cf3d9eda3077c130
|
|
BLAKE2b-256 checksum How to use checksums |
5648e9b2807d5f74b751a69232f33b141542252b8cb80335935a838b50daec3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for openble-muse 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for openble_muse-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.