‹ The journey

Day 1, part 2: what bad contact looks like

Solved: it was contact 1 November 2026

In short

  • Our code was fine: every packet arrived and was decoded correctly.
  • A sensor that doesn’t touch skin acts as an antenna and catches the 50 Hz hum from the walls.
  • Our first explanation (the reference sensor) was wrong.
  • Dampening only the forehead fixed only the forehead: contact, not code.
On this page

Every programmer who touches EEG runs into this, and most tutorials skip it: the hardest part is not the code, it’s the contact between a sensor and skin. This is our log, measurement by measurement, including the conclusion we got wrong.

The setup

  • Headband: Muse S Gen 2 (model MS-02), sensors at TP9 (behind the left ear), AF7 (left forehead), AF8 (right forehead), TP10 (behind the right ear), with the reference in the middle of the forehead.
  • Skin: dry. No water, no wiping.
  • Tool: the app’s Save 10 s raw data button, which stores every Bluetooth packet exactly as it arrived, and tools/analyze_raw_muse.py, which decodes those bytes independently of the app.

Question 1: is it our code?

When a signal looks wrong, first rule out your own bugs. The script checks:

CheckWhat it would show if our code were wrongResult
Packet sizesanything other than 20 bytesall 20 bytes ✅
Packet countersgaps or repeats0 gaps on every channel ✅
Samples per secondnot 256256 ✅
Jumps at packet boundariesmuch bigger steps exactly every 12 samples (bytes decoded in the wrong order)the same size inside and at boundaries ✅

Conclusion: the data arrives complete and is decoded correctly. Whatever is wrong is happening before the data reaches us: at the skin.

Question 2: what does the signal say?

Measurement 1, band just put on (14:47)

SensorStuck at the limitsMains hum (50 + 100 Hz)Verdict
TP9, left ear22.7%40% of the powerno contact
AF7, left forehead22.3%36%no contact
AF8, right forehead0.8%36%touching, but humming
TP10, right ear21.8%42%no contact

TP9 and TP10, on opposite sides of the head, were almost identical, sample by sample. Sensors in different places should never match like that.

Our first conclusion, and why it was wrong

We concluded: the reference sensor in the middle of the forehead isn’t touching, because when the reference floats, every channel picks up the same hum.

Measurement 2, two minutes later (14:49)

SensorStuck at the limitsMains humVerdict
TP923.0%31%no contact
AF722.6%26%no contact
AF88.0%2.7%settling: hum gone, slow drift
TP1022.7%34%no contact

AF8 lost almost all of its hum. If the reference were the problem, AF8 would be humming too. So the reference was fine; three sensors simply weren’t touching skin yet. We had blamed the one part that was working.

Lesson: one measurement is a guess. Two measurements can prove the first guess wrong.

What’s going on physically

  • A sensor on dry skin, or not quite on skin, is a tiny antenna. The wiring in your walls carries 50 Hz (60 Hz in the Americas), and the antenna picks it up. That hum is far bigger than brainwaves, so the signal slams into the top and bottom of the amplifier’s range: stuck at the limits.
  • Two floating sensors catch the same hum at the same moment, which is why they look identical.
  • When contact forms, the hum disappears first. What’s left is a slow drift while the skin and sensor settle. After that, real brain activity becomes visible, for example alpha (8–12 Hz) when you close your eyes.

What about a whole night?

Wetting your skin helps at the start, but nobody stays wet all night. The Muse S is made to be worn dry: after 5–10 minutes your skin warms up and gets slightly moist on its own, and contact improves. What matters more is firm, even pressure: the strap snug, forehead sensors flat above the eyebrows, ear sensors on bare skin behind the ears.

Measurement 3, after 10 minutes of wearing (14:57)

SensorStuck at the limitsMains humVerdict
TP918.5%37%no contact, still identical to TP10
AF718.0%37%no contact
AF81.5%28%touching skin, humming again
TP1018.1%38%no contact

Ten minutes of wearing changed almost nothing. Three sensors are stuck at nearly the same percentage (18.0, 18.1, 18.5), and the two ear sensors still match each other. That makes us less sure it’s only the skin: it could also be a setting this headband model handles differently from the ones the open-source decoders were written for.

We don’t know yet. Instead of guessing a third time, we planned two tests that tell the possibilities apart:

  1. Check with the manufacturer’s own app. If the official Muse app shows good contact on all four sensors while ours doesn’t, the problem is in our code. If the Muse app also shows poor contact, it’s the contact.
  2. The wet test. Dampen the skin under the three silent sensors. If they come to life, it’s contact. If they still only hum, something else is wrong.

A clue from an older app

Before running those tests, we remembered an earlier Flutter app (for mybliss.space) that had the same trouble with a Muse S and was made to work. Comparing its Muse code with ours showed three differences:

The app that workedOpenLucid so far
Timingwaits 200–400 ms between commandssent h, p21, s, d back to back
Checkingasks for the firmware version, sends a preset, and reads the answer ("rc":0 means accepted); tries other presets if refusedsent p21 and never checked
Ordercontrol channel, choose preset, then subscribe to EEG, then s and dsubscribed to EEG first, then all commands at once

Commands go to the Muse “without response”, so nothing tells the sender when one is lost. If p21 gets lost, the headband keeps streaming in its own default mode, and the data channels may not carry four clean EEG signals. That would explain three sensors that look strangely alike.

We copied the careful approach, and the app now shows which firmware and preset the headband accepted. Lesson: when talking to hardware, never assume a command arrived. Ask, wait, and check the answer.

Measurement 4, with the careful start (15:05)

SensorStuck at the limitsMains humRadio, 21.3 HzAbove 60 HzVerdict
TP920.0% (always at the bottom)2.9%6.0%41%floating input
AF720.8%3.2%6.9%37%floating input
AF82.3%6.2%2.0%2%touching skin
TP1020.4%2.8%5.7%38%floating input

The mains hum was gone, but the three sensors were still stuck at the limits, now with a new rhythm: exactly 21.3 Hz. That number is a giveaway: the Muse sends 12 samples per Bluetooth packet, 256 ÷ 12 = 21.3 packets per second. Brains don’t keep time with Bluetooth. A floating input, an electrode connected to nothing, does: it picks up the headband’s own radio bursts. AF8 meanwhile looked like a real signal, smooth and slow.

Honest conclusion: the careful start is better code, but probably not the fix. Whether the hum vanished because of the code or because something around us changed, we can’t tell from one measurement. What four measurements in a row do say: the same three sensors never touched skin, and AF8 always did.

Lesson: the frequency of a disturbance tells you where it comes from. 50 Hz: the walls. 21.3 Hz: the headband’s own radio. 8–12 Hz with your eyes closed: finally, a brain.

Measurement 5: the experiment that settled it (15:11)

Instead of the manufacturer’s app, a simpler test: dampen the forehead, but not the ears. Change one thing in one place, and see whether only that place changes.

SensorBefore (15:05)After a damp forehead (15:11)
AF7, left forehead20.8% stuck at the limits: floating0.0%: touching skin
AF8, right forehead2.3%: touching skin0.0%: touching skin
TP9, left ear (still dry)20.0%: no contact22.8%: no contact
TP10, right ear (still dry)20.4%: no contact22.4%: no contact

The forehead sensors came to life; the dry ear sensors stayed silent. It was contact, not code. If our code had been wrong, a wet forehead would not have changed anything.

Lesson: the best experiment changes one thing, in one place, and predicts where the effect will show up. A controlled experiment beats any amount of staring at code.

What we learned on day 1, in one list

  1. Rule out your own code first: packet sizes, counters, sample rate, jumps at packet boundaries.
  2. A sensor that doesn’t touch skin is an antenna. It catches the walls (50 Hz) or the headband’s own radio (21.3 Hz), and sticks to the edge of its range.
  3. Two floating sensors look alike. Sensors in different places should never match.
  4. One measurement is a guess. Ours was wrong once.
  5. Change one thing in one place. That’s how we found the answer.
  6. For OpenLucid the forehead sensors matter most: they sit right above the eyes and catch the eye movements of REM sleep, and the left-right signal from inside a dream. The ear sensors are welcome extras; the app should use the best sensors it has, and say which.

What this changes in OpenLucid

  • The app needs a settling phase before a night starts: wait until the contact is good, and say which sensor is still settling.
  • Contact quality must be recorded all night, so the morning report can say “your left ear sensor lost contact at 03:12”.
  • The contact check should recognize the “identical channels” pattern and say so in plain words.
  • The course needs a lesson, and maybe a game, about exactly this.