You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Statistics characteristic (fc540013) decoded: on-device drink log, plus a de-bounce that drops it in debug mode #97
I decoded the statistics characteristic (fc540013, MugCharacteristic.STATISTICS) against an Ember Mug 2 (14 oz, firmware 406, hardware 10) and wrote it up here: https://github.com/gwhillhouse/ember-mug-app/blob/main/docs/statistics-stream.md. Sharing in case it's useful for the library, along with one thing I noticed in _notify_callback that would stop anyone from seeing the stream through debug=True.
What the stream is
It is the mug's on-device drink log, not live telemetry. The mug buffers records as things happen and drains the whole backlog to the first client that subscribes; a second subscribe afterwards gets only the 05 end marker. New records then arrive live on the open subscription, ~15–20 s after the event. (This looks like the feed behind the Ember app's analytics: it subscribes on connect and uploads to collector.embertech.com, so whichever client subscribes first gets the log.)
Framing. Each notification is one of four packet types, working within the 20-byte ATT MTU:
First byte
Meaning
01 00 LL …
Complete record, LL = payload length (8 or 10 seen)
02 00 10 …
First fragment of a longer record, always 16 payload bytes
04 01 NN …
Final fragment of the preceding 02 record
05
End of batch
Records (after reassembling 02+04): 00 | kind | sub | t (u32 BE, seconds since the pour) | ff | fields. Temperatures are big-endian °C×100 (the opposite of the GATT characteristics); 7fff is null.
kind
meaning
fields
05
liquid state change (sub = the LiquidState code)
–
07
target set by the app
target temp
0f
heater engaged
target temp
10
10-minute sample
level (0–30), temp
15
snapshot at the pour (partly decoded)
see doc
Everything marked confirmed in the doc was matched second-for-second against my own app's log of the same drink; the 0x15 snapshot fields and one constant are still hypotheses (one mug, one day). Raw capture available on request (stats-capture.jsonl, ~50 packets per drink).
The de-bounce in _notify_callback
event_id=data[0]
now=time()
if (last_time:=self._latest_events.get(event_id)) andnow-last_time<5:
return
This runs before the characteristic check, so it also applies to statistics packets. The backlog arrives as ~48 packets in about five seconds whose first byte is almost always 01, 02 or 04, so with debug=True the library logs three of them and drops the rest, and the 02/04 fragments can never be paired. Skipping the de-bounce when characteristic.uuid == MugCharacteristic.STATISTICS.uuid (or keying it on the characteristic as well as the byte) fixes it. Happy to send a PR for that if you'd like; I held off because it touches behaviour you may want to shape differently once the stream is actually parsed.
Related
Small PR alongside this one (#96): battery temperature from bytes 2–3 of fc540007, which orlopau documented and which explains the "on the coaster but not charging" behaviour on a hot mug.
The clock changes the log. Writing fc540006 (uint32 LE Unix time + int8 UTC offset, the phone-app format) makes every subsequent statistics record carry an absolute Unix timestamp in its t field instead of seconds-since-pour. Reading the characteristic back only returns what was written, but the mug keeps time from then on. Confirmed with two target writes stamped 1789511796 / 1789511822. So a library that sets the clock on connect gets exact pour times for backlog drinks.
Control register (fc540010/fc540011) is address-select-then-read. Scanned 0–255 (address side only): 00 → empty, 01 → the MAC little-endian, 02 → five 32-bit words that are nRF SRAM pointers (a debug view), 03 → ASCII TJO ;-), everything from 04 up → 0x12345678. Which means get_battery_voltage() (data register at address 0) reads 0 bytes on firmware 406; the attribute may be worth dropping or guarding.
Wow! Thank you very much for this documentation as well as your PR for battery temp!
Between this and orlopau's page there are a few more things that can be added to this library :)
Thanks for your hard work and excellent documentation :)
Activity
gwhillhouse commentedon Sep 15, 2026
Two follow-ups from today, both now in the same doc (https://github.com/gwhillhouse/ember-mug-app/blob/main/docs/statistics-stream.md):
The clock changes the log. Writing
fc540006(uint32 LE Unix time + int8 UTC offset, the phone-app format) makes every subsequent statistics record carry an absolute Unix timestamp in itstfield instead of seconds-since-pour. Reading the characteristic back only returns what was written, but the mug keeps time from then on. Confirmed with two target writes stamped1789511796/1789511822. So a library that sets the clock on connect gets exact pour times for backlog drinks.Control register (
fc540010/fc540011) is address-select-then-read. Scanned 0–255 (address side only):00→ empty,01→ the MAC little-endian,02→ five 32-bit words that are nRF SRAM pointers (a debug view),03→ ASCIITJO ;-), everything from04up →0x12345678. Which meansget_battery_voltage()(data register at address 0) reads 0 bytes on firmware 406; the attribute may be worth dropping or guarding.sopelj commentedon Sep 16, 2026
Wow! Thank you very much for this documentation as well as your PR for battery temp!
Between this and orlopau's page there are a few more things that can be added to this library :)
Thanks for your hard work and excellent documentation :)