Thanks Nate, I took you up on the live capture. Short version first, details and data below. All captured data is from the main door (IR beams fitted); my second door is described under “Two doors” below. Times are AEST, and “device t” means the ESP’s own millisecond clock. The post has largely been generated by Claude AI.
Setup
-
Merlin MS105MYQ (AU), Security+ 2.0, IR beams fitted. blaQ project 1.6.2, ESPHome 2026.9.1, HA 2026.9.4.
-
gdolib at PR #36 (fix/v2-rx-resync, 4110dca) plus log-only additions: every received and sent frame, errors, resyncs, door and obstruction state changes, with device timestamps. Built following docs/live-capture-workflow.md; no behaviour changes. I’ve since rolled the door back to the build I was running before.
-
Two sessions on 5 Oct (about 14:25 to 15:48): beam blocks (door closed, open, closing, rapid waves), full open/close from the wall button and from HA, and stops/reversals mid-travel. I noted each action by wall clock and matched it to the frames.
Two doors, set up differently
|
Main door (this capture) |
Small door |
| Opener |
Merlin MS105MYQ |
MT100EVO |
| Protocol |
Security+ 2.0 |
Security+ 2.0 |
| IR beams |
Fitted |
None |
| Toggle-only |
Off: discrete open, close and stop |
On (set because it has no beams): a single toggle |
| Close from HA |
The Merlin runs about 7.4 s of its own warning before the door moves |
Starts moving straight away (my observation, not captured) |
| Position tracking |
Drifts on closes (see below) |
Tracks cleanly |
| Obstruction |
Works (see below) |
n/a, no beams |
Both run blaQ 1.6.2, ESPHome 2026.9.1 and the same gdolib PR #36. I’m happy to run the same capture on the small door for a comparison, just not in this session.
Obstruction: reports correctly in HA
-
The Merlin reports it in the STATUS frame: byte1 is 0x68 when clear and 0x28 when the beam is broken. I saw no OBST_1, OBST_2 or PAIR_3_RESP frames (the secplus_gdo component sets obst_from_status = true).
-
Every trip was followed by a clear: 5 s blocks, 1 s blocks, a block while closing, and two sets of 5 rapid waves (all 10 trips and clears reported). Frames arrive within about a second of the physical break. I didn’t see the ~4.4 s STATUS lag mentioned in the gdolib comments.
-
Beam broken while closing: the Merlin switches to “opening” 1.0 s and 1.2 s after the beam frame (two runs).
-
One thing I can’t interpret: byte2 bit 0x04 goes high with the first beam break. With the door closed it clears with the beam; with the door open or moving it stays set until the door’s next state change.
-
Was it 1.6.2? My InfluxDB history shows this entity existing from 22 Aug on project 1.6.0, writing “off” at each boot but never changing to “on” over the ~10 days I was crossing and testing the beam. I updated to 1.6.2 on 1 Sep, and it started changing state within hours (first event 1 Sep 18:36). So I suspect 1.6.2, not the resync fix, is what made it work. 1.6.0 only gave it an initial state.
Travel percentage: the Merlin’s pre-close warning gets learned as travel
Time between the Merlin’s “opening”/“closing” frame and its “open”/“closed” frame (device clock):
| Move |
Button/remote |
HA command |
| Full open |
15.1, 15.1, 16.1 s |
15.1, 16.1, 16.1 s |
| Full close |
18.1, 18.9, 19.1 s |
26.1, 26.1 s |
| Close after a stop (about 26 to 33% open) |
n/a |
13.0 s, 15.0 s |
Opens are identical either way. Closes from HA take 7.4 s longer (6.95 to 7.97 s across the full closes; the two partial runs, compared against my partial button closes, give 7.1 s and 8.0 s). The sequence on an HA close is: blaQ alert (2 s), command sent, the Merlin sends “closing” about 1 s later and runs its own warning (flashing light), and the door only starts moving about 7 s after that frame. Nothing marks the start of movement: in all four HA closes the opener’s rolling code advances by exactly 1 between the “closing” and “closed” frames, so no frames are sent in between, and the STATUS bytes don’t change when the door starts. My guess is that the Merlin applies the warning to discrete close commands from the bus and not to button presses, which would also explain the small door, where toggle-only means there is no discrete close.
What I see in gdolib’s behaviour:
-
The cover entity reaches “closed” 5 to 6 s before the Merlin’s closed frame (15:43:46.1 vs 15:43:51.5, and 15:45:27.3 vs 15:45:33.2).
-
The position updates imply a stored close time of about 27 s, against about 19 s real. From my reading of update_door_state(), durations are measured between the closing and closed frames, so a close learned from an HA command picks up the warning.
-
Resetting the door timings can’t fix this, because one stored time can’t suit both kinds of close: a time learned from a button close makes HA closes show “closed” about 7 s early, and one learned from an HA close makes button closes lag.
-
Partial moves also have about 1.4 s of start-up time (from my partial button closes, close time is roughly 1.4 s + 17.3 s x the fraction), so a straight 0 to 100% line is a few percent off even with no warning.
A possible change. I think gdolib could calibrate this itself:
-
A close counts as “ours” if gdolib sent a discrete close within about 3 s before the Merlin’s “closing” frame (in my log the frame follows the command by 0.8 to 1.2 s). Otherwise it’s “external”: button, remote, or the opener’s own timer.
-
It learns a close time for each kind from uninterrupted open-to-closed travel only, and uses the difference as the delay, only when both kinds have at least two samples and the difference is over about 2 s.
-
External closes advance the position straight away. For our closes, gdolib holds the start position for the learned delay after the “closing” frame, then runs at the same rate. Opens don’t change.
Setups with no warning, like my toggle-only door, would learn zero and behave as today. Before relying on it I’d want to test pressing stop or breaking the beam during the Merlin’s warning, and closes started by the opener’s own auto-close timer.
Motor: no sign of it
-
HA’s motor entity has never gone on. InfluxDB shows no “on” state for the main door’s motor since I added it on 22 Aug (about 6 weeks of daily use, plus the captures), nor for my second door, which runs in toggle-only mode. Every recorded state is “off”. The blaQ’s own status page also never shows it.
-
Of about 140 decoded frames, every one is STATUS (0x081), the OPENINGS reply (0x48c), or one of our own DOOR_ACTION, LIGHT or GET_STATUS. There is no MOTOR_ON (0x284) and no unknown command number.
-
While the door moves, the only change in STATUS is the door-state nibble (4 opening, 5 closing). byte1 stays 0x68 and byte2 stays 0x02 (light). The Merlin sends one frame when travel starts and one when it ends, and nothing in between.
-
So either this model never reports the motor, or it does so somewhere gdolib doesn’t see (see “What I can’t see” below).
Stops and reversals
-
A stop is reported as STATUS nibble 3 (“stopped”) 0.3 to 0.5 s after a stop from HA, with all other bytes identical to a moving frame.
-
The next press reverses direction: open, stop, then close; close, stop, then open. A close from HA after a stop has the same delay (about 7 to 8 s).
-
The OPENINGS counter ticks only after a full cycle; partial moves don’t count.
What I can’t see (limits of this capture)
-
gdolib reads and discards packets shorter than 19 bytes without logging them. I counted 21 such events since boot (sizes 2, 3, 6, 8, 9, 10, 12, 15, 16, 17), most within about 40 to 115 ms before a state-change frame or around my own transmissions. They could be frame fragments or line noise, but if the Merlin sent anything about the motor in a short message I would never have seen it. A capture that logs those bytes would settle it.
-
Many of my log lines were dropped by ESPHome’s logger (I believe its default task_log_buffer_size of 768 bytes is too small for gdolib’s bursts). The raw RX: frame lines mostly survived, and the opener’s rolling-code steps show no gaps in session 2 (33 steps of 1, 3 repeats, 53 frames). Session 1 had one single-frame gap.
-
Every oversized packet was exactly 20 bytes (break byte plus one frame).
-
Sync after the flash was slow: about 170 s on the capture build (33 s after a restart, 6 s on the rolled-back build). In that first sync, 146 commands were queued within about a second and 9 sent. I also saw one signature error with a resync during each of the first two syncs, and none during the test windows.
One unexplained reset
On the first restart with the capture build the device came back with reboot reason “task watchdog”: crash handler Task wdt, core 1, PC 0x4037D6AE, backtrace 0x4037D6AB 0x420F7AC1 0x420F9A33 (ESPHome couldn’t decode it, toolchain not found). There was no other reset in about 85 minutes of testing, and in the 10 days before, every reboot on PR #36 was OTA or web-server initiated. It might be down to my extra logging, so I’m noting it in case anyone repeats the capture.
Questions
-
Does the MS105MYQ dialect report motor state anywhere? If not, should the motor entity be expected to stay off on this model?
-
Would you consider the self-calibrating position change proposed above? I’m happy to test it on my door.
-
What is byte2 bit 0x04 in STATUS?
-
Did anything in 1.6.2 (vs 1.6.0) change how obstruction is handled?
-
Would you like a second capture that logs the discarded short packets and uses larger log buffers, and/or the same capture on the small door?
Data
The decoded frame tables for both sessions (device clock, command, door state, bytes, rolling-code step) are below. A redacted log extract (Wi-Fi, IP, MAC and device ID removed) is here: blaQ / Merlin MS105MYQ capture, 5 Oct 2026 (redacted) - Pastebin.com . I’ve left out the raw hex frames, but I’m happy to send them privately.
Decoded frames (device clock). Command, door state, STATUS bytes and the opener’s rolling-code step. Session 1 is 14:40 to 14:58, session 2 is 15:39 to 15:49 (AEST).
Decoded frames, session 1
frames decoded: 86 failed to decode: 0
wall t_ms dir cmd name door par b1 b2 nib roll_step
14:41:29 1024346 RX 081 STATUS closed 5 28 04 2
14:41:35 1030394 RX 081 STATUS closed 5 68 00 2 1
14:41:49 1044352 RX 081 STATUS closed 5 28 04 2 1
14:41:55 1049947 RX 081 STATUS closed 5 68 00 2 1
14:42:25 1079978 RX 081 STATUS closed 5 68 00 2 0
14:42:26 1080991 RX 081 STATUS opening 1 68 02 4 1
14:42:27 1081930 RX 081 STATUS opening 1 68 02 4 0
14:42:42 1097073 RX 081 STATUS open 4 68 02 1 1
14:43:00 1115370 RX 081 STATUS closing 0 68 02 5 1
14:43:19 1134262 RX 081 STATUS closed 7 68 02 2 1
14:43:51 1166519 RX 081 STATUS opening 1 68 02 4 2
14:44:06 1181631 RX 081 STATUS open 4 68 02 1 1
14:44:25 1200786 RX 081 STATUS closing 0 68 02 5 1
14:44:44 1219932 RX 081 STATUS closed 7 68 02 2 1
14:44:45 1220057 RX 48c OPENINGS e 1d 13 0 1
14:47:32 1387892 RX 081 STATUS closed 5 68 00 2 1
14:47:38 1393733 RX 081 STATUS closed 5 68 00 2 0
14:48:30 1445272 TX 280 DOOR_ACTION b 01 01 1
14:48:30 1445277 TX 280 DOOR_ACTION a 00 01 1 0
14:48:30 1445684 RX 081 STATUS opening 1 68 02 4 1
14:48:46 1461813 RX 081 STATUS open 4 68 02 1 1
14:50:23 1558831 TX 280 DOOR_ACTION a 01 01 0 1
14:50:23 1558835 TX 280 DOOR_ACTION b 00 01 0 0
14:50:24 1559678 RX 081 STATUS closing 0 68 02 5 1
14:50:50 1585778 RX 081 STATUS closed 7 68 02 2 1
14:50:50 1585870 RX 48c OPENINGS 9 1d 14 0 1
14:51:10 1605260 TX 280 DOOR_ACTION b 01 01 1 2
14:51:10 1605263 TX 280 DOOR_ACTION a 00 01 1 0
14:51:10 1605944 RX 081 STATUS opening 1 68 02 4 1
14:51:26 1621058 RX 081 STATUS open 4 68 02 1 1
14:52:02 1657229 TX 280 DOOR_ACTION a 01 01 0 1
14:52:02 1657233 TX 280 DOOR_ACTION b 00 01 0 0
14:52:03 1658449 RX 081 STATUS closing 0 68 02 5 1
14:52:29 1684555 RX 081 STATUS closed 7 68 02 2 1
14:52:29 1684754 RX 48c OPENINGS 8 1d 15 0 1
14:53:09 1724366 TX 280 DOOR_ACTION b 01 01 1 2
14:53:09 1724370 TX 280 DOOR_ACTION a 00 01 1 0
14:53:09 1724866 RX 081 STATUS opening 1 68 02 4 1
14:53:26 1740998 RX 081 STATUS open 4 68 02 1 1
14:54:03 1778438 RX 081 STATUS open 4 28 06 1 1
14:54:04 1779848 RX 081 STATUS open 0 68 06 1 1
14:54:20 1795066 RX 081 STATUS open 4 28 06 1 1
14:54:22 1796981 RX 081 STATUS open 0 68 06 1 1
14:55:00 1835722 RX 081 STATUS closing 0 68 02 5 1
14:55:00 1835831 RX 081 STATUS closing 0 68 02 5 0
14:55:09 1844788 RX 081 STATUS closing 0 28 06 5 1
14:55:10 1845801 RX 081 STATUS opening 1 28 06 4 1
14:55:11 1846155 RX 081 STATUS opening 5 68 06 4 1
14:55:19 1853962 RX 081 STATUS open 4 68 02 1 1
14:55:30 1865930 TX 281 LIGHT b 00 00 0 1
14:55:30 1865933 TX 080 GET_STATUS 8 00 00 0 1
14:55:31 1866038 TX 281 LIGHT b 00 00 0 1
14:55:31 1866042 TX 080 GET_STATUS 8 00 00 0 1
14:55:31 1866096 RX 081 STATUS open 6 68 00 1 1
14:55:31 1866493 RX 081 STATUS open 6 68 00 1 0
14:55:33 1868820 RX 081 STATUS open 6 68 00 1 0
14:55:39 1874665 RX 081 STATUS open 6 68 00 1 0
14:56:02 1897186 RX 081 STATUS closing 0 68 02 5 1
14:56:08 1903082 RX 081 STATUS closing 0 28 06 5 1
14:56:09 1904296 RX 081 STATUS opening 1 28 06 4 1
14:56:09 1904598 RX 081 STATUS opening 5 68 06 4 1
14:56:15 1910443 RX 081 STATUS open 4 68 02 1 1
14:56:35 1930396 RX 081 STATUS open 4 28 06 1 1
14:56:36 1931100 RX 081 STATUS open 0 68 06 1 1
14:56:36 1931403 RX 081 STATUS open 4 28 06 1 1
14:56:37 1932109 RX 081 STATUS open 0 68 06 1 1
14:56:37 1932445 RX 081 STATUS open 4 28 06 1 1
14:56:38 1933016 RX 081 STATUS open 0 68 06 1 1
14:56:38 1933266 RX 081 STATUS open 4 28 06 1 1
14:56:39 1933921 RX 081 STATUS open 0 68 06 1 1
14:56:39 1934324 RX 081 STATUS open 4 28 06 1 1
14:56:39 1934404 RX 081 STATUS open 4 28 06 1 0
14:56:40 1935030 RX 081 STATUS open 0 68 06 1 1
14:57:00 1955233 RX 081 STATUS open 4 28 06 1 1
14:57:00 1955890 RX 081 STATUS open 0 68 06 1 1
14:57:01 1956192 RX 081 STATUS open 4 28 06 1 1
14:57:01 1956897 RX 081 STATUS open 0 68 06 1 1
14:57:02 1957149 RX 081 STATUS open 4 28 06 1 1
14:57:02 1957805 RX 081 STATUS open 0 68 06 1 1
14:57:03 1958056 RX 081 STATUS open 4 28 06 1 1
14:57:03 1958713 RX 081 STATUS open 0 68 06 1 1
14:57:04 1959063 RX 081 STATUS open 4 28 06 1 1
14:57:04 1959720 RX 081 STATUS open 0 68 06 1 1
14:57:08 1963746 RX 081 STATUS closing 0 68 02 5 1
14:57:26 1981884 RX 081 STATUS closed 7 68 02 2 1
14:57:27 1982057 RX 48c OPENINGS b 1d 16 0 1
=== command counts ===
RX 081 STATUS 68
TX 280 DOOR_ACTION 10
RX 48c OPENINGS 4
TX 281 LIGHT 2
TX 080 GET_STATUS 2
=== STATUS frames (RX): which bits ever change ===
b1: values seen ['28', '68'] changing-bit mask 0x40
bit 0x40 set in door states ['closed', 'closing', 'open', 'opening']; clear in ['closed', 'closing', 'open', 'opening']
b2: values seen ['00', '02', '04', '06'] changing-bit mask 0x06
bit 0x02 set in door states ['closed', 'closing', 'open', 'opening']; clear in ['closed', 'open']
bit 0x04 set in door states ['closed', 'closing', 'open', 'opening']; clear in ['closed', 'closing', 'open', 'opening']
nib: values seen ['01', '02', '04', '05'] changing-bit mask 0x07
bit 0x01 set in door states ['closing', 'open']; clear in ['closed', 'opening']
bit 0x02 set in door states ['closed']; clear in ['closing', 'open', 'opening']
bit 0x04 set in door states ['closing', 'opening']; clear in ['closed', 'open']
parity nibble (bits 12-15, masked by gdolib) values seen: ['0', '1', '4', '5', '6', '7']
=== RX rolling-code steps (1 = no frame missing, 0 = repeat, 2+ = frames missing from this log) ===
step 0: 8
step 1: 62
step 2: 1
Decoded frames, session 2
frames decoded: 53 failed to decode: 0
wall t_ms dir cmd name door par b1 b2 nib roll_step
15:40:20 4555670 RX 081 STATUS opening 1 68 02 4
15:40:26 4561666 RX 081 STATUS stopped 6 68 02 3 1
15:40:35 4570734 RX 081 STATUS closing 0 68 02 5 1
15:40:43 4578795 RX 081 STATUS closed 7 68 02 2 1
15:40:43 4578889 RX 48c OPENINGS b 1d 16 0 1
15:41:11 4606010 RX 081 STATUS opening 1 68 02 4 1
15:41:16 4611048 RX 081 STATUS stopped 6 68 02 3 1
15:41:25 4620233 RX 081 STATUS closing 0 68 02 5 1
15:41:32 4627216 RX 081 STATUS closed 7 68 02 2 1
15:41:32 4627310 RX 48c OPENINGS b 1d 16 0 1
15:43:20 4735136 TX 280 DOOR_ACTION b 01 01 1
15:43:20 4735139 TX 280 DOOR_ACTION a 00 01 1 0
15:43:21 4736075 RX 081 STATUS opening 1 68 02 4 1
15:43:24 4739636 TX 280 DOOR_ACTION 9 01 01 3 1
15:43:24 4739639 TX 280 DOOR_ACTION 8 00 01 3 0
15:43:25 4740105 RX 081 STATUS stopped 6 68 02 3 1
15:43:37 4752382 TX 280 DOOR_ACTION a 01 01 0 1
15:43:37 4752386 TX 280 DOOR_ACTION b 00 01 0 0
15:43:38 4753389 RX 081 STATUS closing 0 68 02 5 1
15:43:51 4766401 RX 081 STATUS closed 7 68 02 2 1
15:43:51 4766636 RX 48c OPENINGS b 1d 16 0 1
15:45:00 4835317 TX 280 DOOR_ACTION b 01 01 1 2
15:45:00 4835321 TX 280 DOOR_ACTION a 00 01 1 0
15:45:00 4835853 RX 081 STATUS opening 1 68 02 4 1
15:45:05 4840638 TX 280 DOOR_ACTION 9 01 01 3 1
15:45:05 4840643 TX 280 DOOR_ACTION 8 00 01 3 0
15:45:06 4840944 RX 081 STATUS stopped 6 68 02 3 1
15:45:17 4852284 TX 280 DOOR_ACTION a 01 01 0 1
15:45:17 4852287 TX 280 DOOR_ACTION b 00 01 0 0
15:45:18 4853089 RX 081 STATUS closing 0 68 02 5 1
15:45:33 4868099 RX 081 STATUS closed 7 68 02 2 1
15:45:33 4868193 RX 48c OPENINGS b 1d 16 0 1
15:46:08 4903385 RX 081 STATUS opening 1 68 02 4 1
15:46:23 4918492 RX 081 STATUS open 4 68 02 1 1
15:47:00 4955793 RX 081 STATUS closing 0 68 02 5 1
15:47:06 4960936 RX 081 STATUS stopped 6 68 02 3 1
15:47:16 4970985 RX 081 STATUS opening 1 68 02 4 1
15:47:21 4975944 RX 081 STATUS open 4 68 02 1 1
15:47:21 4975959 TX 281 LIGHT b 00 00 0 2
15:47:21 4975964 TX 080 GET_STATUS 8 00 00 0 1
15:47:21 4976048 TX 281 LIGHT b 00 00 0 1
15:47:21 4976051 TX 080 GET_STATUS 8 00 00 0 1
15:47:21 4976139 RX 081 STATUS open 6 68 00 1 1
15:47:21 4976439 RX 081 STATUS open 6 68 00 1 0
15:47:23 4978813 RX 081 STATUS open 6 68 00 1 0
15:47:29 4984658 RX 081 STATUS open 6 68 00 1 0
15:47:36 4991074 RX 081 STATUS closing 0 68 02 5 1
15:47:40 4995098 RX 081 STATUS stopped 6 68 02 3 1
15:47:48 5003157 RX 081 STATUS opening 1 68 02 4 1
15:47:52 5007305 RX 081 STATUS open 4 68 02 1 1
15:48:06 5021355 RX 081 STATUS closing 0 68 02 5 1
15:48:24 5039496 RX 081 STATUS closed 7 68 02 2 1
15:48:24 5039589 RX 48c OPENINGS a 1d 17 0 1
=== command counts ===
RX 081 STATUS 32
TX 280 DOOR_ACTION 12
RX 48c OPENINGS 5
TX 281 LIGHT 2
TX 080 GET_STATUS 2
=== STATUS frames (RX): which bits ever change ===
b1: values seen ['68'] changing-bit mask 0x00
b2: values seen ['00', '02'] changing-bit mask 0x02
bit 0x02 set in door states ['closed', 'closing', 'open', 'opening', 'stopped']; clear in ['open']
nib: values seen ['01', '02', '03', '04', '05'] changing-bit mask 0x07
bit 0x01 set in door states ['closing', 'open', 'stopped']; clear in ['closed', 'opening']
bit 0x02 set in door states ['closed', 'stopped']; clear in ['closing', 'open', 'opening']
bit 0x04 set in door states ['closing', 'opening']; clear in ['closed', 'open', 'stopped']
parity nibble (bits 12-15, masked by gdolib) values seen: ['0', '1', '4', '6', '7']
=== RX rolling-code steps (1 = no frame missing, 0 = repeat, 2+ = frames missing from this log) ===
step 0: 3
step 1: 33