Summary
In src/board_controller/muse/muse.cpp, Muse::peripheral_on_ppg decodes the
packet number from the notification but never stores it in
package_num_channel. As a result every sample in ANCILLARY_PRESET reports
package number 0, so packet loss in the PPG stream cannot be detected.
The EEG and gyro handlers in the same file both store it correctly.
Evidence
// peripheral_on_ppg, muse.cpp:801
unsigned int package_num = data[0] * 256 + data[1];
new_ppg_data[ppg_num] = true;
std::vector<int> ppg_channels = board_descr["ancillary"]["ppg_channels"];
// format is: 2 bytes for package num, 6 int24 values for actual data
for (int i = 0; i < 6; i++)
{
double ppg_val = (double)cast_24bit_to_int32 ((unsigned char *)&data[2 + i * 3]);
current_anc_buf[i][ppg_channels[ppg_num]] = ppg_val;
}
package_num does not appear again anywhere in the function. Compare with the
handlers that do store it:
// peripheral_on_eeg, muse.cpp:692
current_default_buf[counter][board_descr["default"]["package_num_channel"].get<int> ()] =
package_num;
// peripheral_on_gyro, muse.cpp:774
current_aux_buf[i][board_descr["auxiliary"]["package_num_channel"].get<int> ()] =
(double)package_num;
The device transmits the value correctly — PPG notifications are 20 bytes:
2 bytes of packet index followed by six 24-bit samples, matching the comment in
the code.
Observed
Muse 2 (MU-03), macOS 26.5.2 on Apple silicon, brainflow 5.22.2 (PyPI), preset
p50, ~30 s recording:
| preset |
rows |
distinct package numbers |
| DEFAULT (EEG) |
7692 |
641 (12 samples each, as expected) |
| AUXILIARY (accel/gyro) |
1596 |
532 (3 samples each, as expected) |
| ANCILLARY (PPG) |
1926 |
1 — all zero |
board = BoardShim(BoardIds.MUSE_2_BOARD, params)
board.prepare_session(); board.config_board("p50"); board.start_stream()
time.sleep(30)
anc = board.get_board_data(preset=BrainFlowPresets.ANCILLARY_PRESET)
pkg = anc[BoardShim.get_package_num_channel(BoardIds.MUSE_2_BOARD,
BrainFlowPresets.ANCILLARY_PRESET)]
print(np.unique(pkg)) # -> [0.]
Expected
get_package_num_channel(..., ANCILLARY_PRESET) should carry the device's
packet counter, as it does for the other two presets, so that dropped PPG
packets are detectable.
Suggested fix
Write it alongside the samples, mirroring the other handlers:
for (int i = 0; i < 6; i++)
{
double ppg_val = (double)cast_24bit_to_int32 ((unsigned char *)&data[2 + i * 3]);
current_anc_buf[i][ppg_channels[ppg_num]] = ppg_val;
current_anc_buf[i][board_descr["ancillary"]["package_num_channel"].get<int> ()] =
(double)package_num;
}
One caveat I am not sure about, and did not want to paper over: three PPG
characteristics each carry their own counter, and the existing comment notes
that only two of the three appear to stream. If those counters can differ, it
may be worth writing only the one from a designated characteristic rather than
whichever arrived last.
Summary
In
src/board_controller/muse/muse.cpp,Muse::peripheral_on_ppgdecodes thepacket number from the notification but never stores it in
package_num_channel. As a result every sample inANCILLARY_PRESETreportspackage number
0, so packet loss in the PPG stream cannot be detected.The EEG and gyro handlers in the same file both store it correctly.
Evidence
package_numdoes not appear again anywhere in the function. Compare with thehandlers that do store it:
The device transmits the value correctly — PPG notifications are 20 bytes:
2 bytes of packet index followed by six 24-bit samples, matching the comment in
the code.
Observed
Muse 2 (MU-03), macOS 26.5.2 on Apple silicon, brainflow 5.22.2 (PyPI), preset
p50, ~30 s recording:Expected
get_package_num_channel(..., ANCILLARY_PRESET)should carry the device'spacket counter, as it does for the other two presets, so that dropped PPG
packets are detectable.
Suggested fix
Write it alongside the samples, mirroring the other handlers:
One caveat I am not sure about, and did not want to paper over: three PPG
characteristics each carry their own counter, and the existing comment notes
that only two of the three appear to stream. If those counters can differ, it
may be worth writing only the one from a designated characteristic rather than
whichever arrived last.