Skip to content

Muse: PPG package number is decoded but never written to the buffer #860

Description

@hafloe

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions