Summary of Changes
- Add support for
BoardIds.MUSE_S_ANTHENA_BOARD. - Add
get_optical_channelsfor boards which expose raw optical data. - Add
get_board_sampling_rate, an instance method for the actual sampling rate of a prepared board session. - Improve
config_boardresponse handling in native and high level bindings. - Add Python examples for recording all Muse S Anthena presets, recording optical data, and analyzing pulse from optical recordings.
- BLED boards are deprecated, users are expected to switch to native BLE without BLED112 dongle.
Muse S Anthena
We’ve had a lot of requests to add support for Muse S devices with Anthena firmware, and we are glad to finally deliver it in BrainFlow. If you have one of these headbands, please try the new board id and examples below, and report any issues or device-specific findings in GitHub issues or the BrainFlow Slack workspace.
Muse S devices with the newer Anthena firmware use a different BLE protocol than earlier Muse 2 and Muse S devices. BrainFlow now has a dedicated board id for it:
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
params = BrainFlowInputParams()
board = BoardShim(BoardIds.MUSE_S_ANTHENA_BOARD.value, params)
The board has three BrainFlow presets:
| BrainFlow preset | Data | Sampling rate | Notes |
|---|---|---|---|
BrainFlowPresets.DEFAULT_PRESET |
EEG | 256 Hz | Four EEG channels TP9,AF7,AF8,TP10 plus four additional raw EEG-like rows exposed as other_channels. |
BrainFlowPresets.AUXILIARY_PRESET |
Accelerometer and gyroscope | 52 Hz | Three accelerometer rows and three gyroscope rows. |
BrainFlowPresets.ANCILLARY_PRESET |
Optical data and battery | 64 Hz | Sixteen raw optical rows and one battery row. Use get_optical_channels, not get_ppg_channels, for this preset. |
The Anthena optical packets contain the data used for PPG and fNIRS-style analysis. BrainFlow exposes these values as raw optical channels instead of converting them into synthetic PPG rows inside the board parser. This keeps the data format close to the device protocol and lets users apply their own optical, pulse, or fNIRS processing.
Muse Commands and Presets
Anthena uses BLE service 0xFE8D, control characteristic 273e0001-4c4d-454d-96be-f03bac821358, and data characteristics 273e0013-4c4d-454d-96be-f03bac821358 and 273e0014-4c4d-454d-96be-f03bac821358. BrainFlow subscribes to data notifications, parses packet tags, and routes samples into the three BrainFlow presets above.
Commands are sent to the control characteristic as ASCII command strings with a length prefix and trailing newline. End users usually should not send these commands manually. Set startup options with BrainFlowInputParams.other_info and let BrainFlow call prepare_session, start_stream, and stop_stream.
During prepare_session, BrainFlow sends:
v6to select the protocol mode.sto request status.hto stop streaming before configuration.- the selected device preset, for example
p1041. sagain after applying the preset.
During start_stream, BrainFlow sends dc001 twice and then requests status again. If low latency mode is enabled, it also sends L1. During stop_stream, BrainFlow sends h.
Preset selection and low latency are independent. The preset decides which data the firmware sends. low_latency=true decides whether BrainFlow sends L1 during start_stream. For long unattended recordings, users can disable low latency if live latency matters less than a more conservative device mode.
Anthena status packets are asynchronous notifications, so config_board should not be treated as a reliable request/response API for these Muse commands. Use it only when you understand the firmware command you are sending.
The default startup configuration is:
params.other_info = 'preset=p1041;low_latency=true'
You can also use shorthand syntax when you only need to select a preset:
params.other_info = 'p1035'
The explicit format is recommended when scripts need to be clear and reproducible:
params.other_info = 'preset=p1035;low_latency=false'
Invalid presets or invalid low_latency values fail early in prepare_session with BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.
The preset values accepted by BrainFlow are:
| Device preset | EEG | Optical packets | ACC/GYRO | Battery | LED behavior |
|---|---|---|---|---|---|
p20, p21, p50, p51, p60, p61 |
EEG4 | none | yes | yes | off |
p1034, p1043 |
EEG8 | Optics8 | yes | yes | bright |
p1044 |
EEG8 | Optics8 | yes | yes | dim |
p1035 |
EEG4 | Optics4 | yes | yes | dim |
p1041, p1042 |
EEG8 | Optics16 | yes | yes | bright |
p1045 |
EEG8 | Optics4 | yes | yes | dim |
p1046 |
EEG8 | Optics4 | yes | yes | unknown |
p4129 |
EEG8 | Optics4 | yes | yes | dim |
The packet tags decoded by BrainFlow are:
| Packet tag | Data type | Shape | BrainFlow preset |
|---|---|---|---|
0x11 |
EEG4 | 4 channels, 4 samples per packet | DEFAULT_PRESET |
0x12 |
EEG8 | 8 channels, 2 samples per packet | DEFAULT_PRESET |
0x34 |
Optics4 | 4 channels, 3 samples per packet | ANCILLARY_PRESET |
0x35 |
Optics8 | 8 channels, 2 samples per packet | ANCILLARY_PRESET |
0x36 |
Optics16 | 16 channels, 1 sample per packet | ANCILLARY_PRESET |
0x47 |
Accelerometer and gyroscope | 6 channels, 3 samples per packet | AUXILIARY_PRESET |
0x88, 0x98 |
Battery | 10 raw values, last parsed battery value is exposed | ANCILLARY_PRESET |
Code Sample
This example records all three BrainFlow presets and writes them to separate CSV files:
import time
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds, BrainFlowPresets
from brainflow.data_filter import DataFilter
params = BrainFlowInputParams()
params.other_info = 'preset=p1041;low_latency=true'
board = BoardShim(BoardIds.MUSE_S_ANTHENA_BOARD.value, params)
try:
board.prepare_session()
board.start_stream()
try:
time.sleep(10)
eeg_data = board.get_board_data(preset=BrainFlowPresets.DEFAULT_PRESET)
imu_data = board.get_board_data(preset=BrainFlowPresets.AUXILIARY_PRESET)
optical_data = board.get_board_data(preset=BrainFlowPresets.ANCILLARY_PRESET)
finally:
board.stop_stream()
finally:
if board.is_prepared():
board.release_session()
DataFilter.write_file(eeg_data, 'muse_anthena_eeg.csv', 'w')
DataFilter.write_file(imu_data, 'muse_anthena_accel_gyro.csv', 'w')
DataFilter.write_file(optical_data, 'muse_anthena_optics_battery.csv', 'w')
To inspect the channel layout:
board_id = BoardIds.MUSE_S_ANTHENA_BOARD.value
eeg_channels = BoardShim.get_eeg_channels(board_id, BrainFlowPresets.DEFAULT_PRESET)
accel_channels = BoardShim.get_accel_channels(board_id, BrainFlowPresets.AUXILIARY_PRESET)
gyro_channels = BoardShim.get_gyro_channels(board_id, BrainFlowPresets.AUXILIARY_PRESET)
optical_channels = BoardShim.get_optical_channels(board_id, BrainFlowPresets.ANCILLARY_PRESET)
Python Examples
The new Muse S Anthena examples are in python_package/examples/tests.
muse_anthena_save_all.py records EEG, IMU, and optical/battery data and writes three CSV files:
python python_package/examples/tests/muse_anthena_save_all.py --duration 30 --output-prefix muse_anthena
You can limit discovery with a BLE MAC address or serial number:
python python_package/examples/tests/muse_anthena_save_all.py --duration 30 --mac-address 00:11:22:33:44:55
python python_package/examples/tests/muse_anthena_save_all.py --duration 30 --serial-number MuseS-1234
muse_anthena_record_optics.py records only the ancillary preset, writes raw optical data, prints the expected sampling rate, estimates the timestamp-derived sampling rate, and lists active optical rows:
python python_package/examples/tests/muse_anthena_record_optics.py --duration 60 --other-info "preset=p1035;low_latency=true" --output-file muse_anthena_optics_recording.csv
muse_anthena_analyze_optics_pulse.py loads an optical CSV, detects active optical channels, filters them, combines them, estimates pulse from peaks and spectrum, and writes four PNG plots:
python python_package/examples/tests/muse_anthena_analyze_optics_pulse.py --input-file muse_anthena_optics_recording.csv --output-prefix muse_anthena_optics
It creates:
muse_anthena_optics_raw.pngmuse_anthena_optics_filtered.pngmuse_anthena_optics_pulse.pngmuse_anthena_optics_spectrum.png
get_board_sampling_rate
BrainFlow already had BoardShim.get_sampling_rate(board_id, preset). This method is static: it reads the board description and returns the default metadata for that board and preset. That is correct for most boards, but it is not enough for boards whose sampling rate can be changed after the board object is created.
This showed up in issue #804 for CYTON_WIFI_BOARD. Users can send OpenBCI sample-rate commands with config_board, for example ~5 for 500 Hz, but the static call still returned the default 1000 Hz. Also, command responses such as ~~ and V are not reliably available on the WiFi path, so callers needed a BrainFlow API for the current session state.
BrainFlow 5.21.0 adds get_board_sampling_rate as an instance method:
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
params = BrainFlowInputParams()
board = BoardShim(BoardIds.CYTON_WIFI_BOARD.value, params)
try:
board.prepare_session()
board.config_board('~5')
actual_sampling_rate = board.get_board_sampling_rate()
board.start_stream()
try:
# read or stream data here
pass
finally:
board.stop_stream()
finally:
if board.is_prepared():
board.release_session()
Use BoardShim.get_sampling_rate(board_id, preset) when you need static board metadata and no runtime configuration changed the sampling rate. Use board.get_board_sampling_rate(preset) after prepare_session when you need the actual sampling rate for this board instance, especially after config_board commands or board-specific initialization that can change it.
For Muse S Anthena, the current preset rates are the same as the static metadata, but the instance method is still useful in generic code:
board.prepare_session()
eeg_rate = board.get_board_sampling_rate(BrainFlowPresets.DEFAULT_PRESET)
optical_rate = board.get_board_sampling_rate(BrainFlowPresets.ANCILLARY_PRESET)
This keeps code which handles both static boards and dynamically configured boards on the same API path.
Deprecation of BLED112 dongle
BLED112 dongle has been used so far for devices such as Ganglion from OpenBCI and a few more. BGLIB(library which we used for this dongle) is not well maintained and implementation we had was insufficient and had known issues. In addition to that BLED112 is not available anymore and deprecated by it’s vendor.
Finally, all devices we originally supported via BLED112 dongle now also have native BLE support.
- BoardIds.GANGLION_BOARD -> BoardIds.GANGLION_NATIVE_BOARD should be used instead
- BoardIds.BRAINBIT_BLED_BOARD -> BoardIds.BRAINBIT_BOARD should be used instead
- BoardIds.MUSE_S_BLED_BOARD -> BoardIds.MUSE_S_BOARD should be used instead
- BoardIds.MUSE_2_BLED_BOARD -> BoardIds.MUSE_2_BOARD should be used instead
- BoardIds.MUSE_2016_BLED_BOARD -> BoardIds.MUSE_2016_BOARD should be used instead
Currently these BLED boards are deprecated and removed from docs, but code to run them is still present. In the future releases it will be removed also.