Skip to content

feat: expose SHT40 ambient temperature and humidity - #89

Merged
g4bri3lDev merged 1 commit into
feat/clean-portfrom
feat/sht40-sensors
Aug 3, 2026
Merged

feat: expose SHT40 ambient temperature and humidity#89
g4bri3lDev merged 1 commit into
feat/clean-portfrom
feat/sht40-sensors

Conversation

@g4bri3lDev

Copy link
Copy Markdown
Member

Requires py-opendisplay 7.15.0 (manifest bumped), which added the SHT40 decoder and the tracker filtering this uses.

Based on feat/clean-port, since main is 124 commits behind and lacks the current beta tag.

Ambient temperature + humidity

Boards with an SHT40 — reTerminal E1001/E1002/E1003/E1004 and Sticky — broadcast the reading in the advertisement's dynamic block. The passive coordinator already receives it, so these entities need no connection and no polling.

They're primary entities: not diagnostic, enabled by default. Ambient room temperature is what a user expects to find on the device, unlike the MCU's own temperature.

The offset within the dynamic block is per-board and cannot be assumed — E1001/E1002/E1004 use 1 while the firmware default is 7 — so it comes from the device's own config and is captured per entity at setup. This matters: decoding at the wrong offset produces plausible-looking garbage rather than an obvious error (a real button byte of 0x28 decodes as -39.9 °C / 4.0 %RH).

Failed reads (FF FF FF) and never-written slots (00 00 00) both report unknown rather than a bogus value.

Chip temperature renamed

The existing temperature entity is the MCU's, and with device_class=TEMPERATURE and no translation_key it displays as plain "Temperature" — exactly the name the new ambient entity would take. Two entities called "Temperature" on one device.

It's now "Chip temperature". This is a translation_key change only: the entity key, and therefore the unique_id, stays "temperature", so existing entities keep their entity_id and history. It was already EntityCategory.DIAGNOSTIC and disabled by default, so nothing else changes.

Only strings.json and en.json are touched; the other languages come from the translation workflow.

Phantom button events

AdvertisementTracker now receives the configured button byte indices. Only the bytes a BinaryInputs packet claims are buttons; the rest belong to touch controllers and sensors, and every byte decodes into a valid-looking button report — so a refreshed SHT40 reading emitted phantom button_slot_changed events on every 30-second poll.

event.py filters by byte_index before firing an entity, so nothing was user-visible; the events were simply produced and discarded on every advertisement. This is hygiene, not a user-facing bugfix.

Verification

104 tests pass (94 existing + 10 new); ruff clean on the changed files.

New coverage: decode at the default and at a relocated offset, msd_data_start_byte=0 resolving to the default slot, both sentinels reporting unknown, the entity-category/enabled-by-default split between ambient and chip, the chip entity keeping its unique_id key, per-instance key uniqueness, and native_value before any advertisement arrives.

Verified end to end on a real E1003 earlier in this work: after adding the sensor packet to its config, the device reports 28.0 °C / 63.6 %RH.

Note for whoever lands the TouchTracker refactor

There's WIP that moves TouchTracker construction into OpenDisplayCoordinator.__init__ as a third positional parameter. This PR adds binary_inputs in that same slot. Whichever lands second should reconcile them — making one or both keyword-only is the obvious fix.

Boards with an SHT40 (reTerminal E1001/E1002/E1003/E1004, Sticky) broadcast
the reading in the advertisement's dynamic block, so these entities need no
connection and no polling -- the passive coordinator already has the data.

Requires py-opendisplay 7.15.0, which added the decoder.

Unlike the existing chip temperature these are primary entities: not
diagnostic, and enabled by default. Ambient room temperature is what a user
expects to find on the device.

The block's offset within the dynamic area is per-board and cannot be
assumed: E1001/E1002/E1004 use 1 while the firmware default is 7, and
decoding at the wrong offset yields plausible-looking garbage (a button byte
of 0x28 reads as -39.9 C). It is therefore taken from the device's own config
and captured per entity at setup.

Rename the chip temperature to "Chip temperature" so it is distinguishable
from the new ambient one, which would otherwise take the same device-class
name. This is a translation_key only; the entity key -- and so the unique_id
-- stays "temperature", so existing entities keep their id and history.

Also pass the configured button byte indices to AdvertisementTracker. Only
the bytes a BinaryInputs packet claims are buttons; the rest belong to touch
controllers and sensors and decode into valid-looking button reports, so
without this a refreshed SHT40 reading emits phantom button transitions.
event.py filters by byte_index before firing an entity, so nothing was
user-visible, but the events were produced on every advertisement.
@g4bri3lDev
g4bri3lDev merged commit d49339d into feat/clean-port Aug 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant