Add Grohe Sense Guard integration for ESPHome - #81
Draft
patricknitsch wants to merge 14 commits into
Draft
Conversation
Implements a UART tap component that decodes the M-Bus-like protocol between the Grohe Sense Guard MCU and its WT-8266-S1 WiFi module. Protocol reverse-engineered from Saleae logic analyzer capture: - Frame: FE×4 preamble + 68 [6-byte addr] 68 + CI + payload + CS + 16 - MSG_INFO (0x01): device firmware/serial at startup - MSG_STATUS (0x05): valve state, snooze active, pressure test flag - MSG_CONFIG (0x04): sprinkler days (Mon-Sun), start/stop times (minutes) - MSG_HEARTBEAT (0x06): periodic keep-alive Phase 1 (listen only): ESP32 RX on MCU TX line → all sensors readable. Phase 2 (inject): ESP32 TX on MCU RX line + Grohe cloud blocked → full local control of valve, snooze, sprinkler mode. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
Every successfully parsed frame is now published as a hex string to the 'last_raw_frame' text sensor in Home Assistant, making it easy to spot new message types without needing a logic analyzer. Unknown frame types (new messages from Grohe firmware updates) are additionally published to 'last_unknown_frame' with a warning log. An on_frame_callback() API allows optional MQTT publishing of every frame by type (grohe/frame/0x04 etc.) for offline protocol analysis. Logger level VERBOSE enables raw frame output on serial for debugging. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
patricknitsch
marked this pull request as draft
June 3, 2026 05:31
- handle_status_: use flags bit 1 (0x02) as primary snooze indicator (live data shows flags=0x22 when snooze active, not just payload byte) - handle_water_data_: decode type 0x03 counter frames and log counter byte - verify_checksum_: log 4 CS candidates on every received frame so we can identify the correct algorithm once CH1 capture is available - send_frame_: log computed CS in TX path for cross-reference - set_sprinkler: guard against empty/short last_config_payload_ with clear error message instead of silently sending 00:00-00:00 https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
Empirically verified from two captured frames (type=0x03 and type=0x04): actual_cs == (sum of bytes from second 0x68 to last data byte) - 2 Previous implementation summed from addr[0] (Candidate C) which was wrong and caused MCU to reject all injected commands. CS debug logging now only warns on mismatch (VERBOSE otherwise) to keep logs clean. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
Previous hardcoded payload had wrong bytes at position [6] (0x07 vs observed 0x01) and didn't preserve snooze/other fields. Now caches the last received STATUS payload (same pattern as CONFIG) and uses it as the write template, changing only the target field. This ensures the MCU receives a valid packet structure it recognizes. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
Observed payload[75,76] = 05 9F for stop time. Little-endian read: (0x9F<<8)|0x05 = 40709 (wrong, shows 678:29) Big-endian read: (0x05<<8)|0x9F = 1439 (correct, 23:59) Fix both read (handle_config_) and write (set_sprinkler) paths. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
From captured app commands (seq=22..25): - flags=0x20 for all write commands, not 0x60 as assumed - payload[6]=0x07 in all app write frames (STATUS + CONFIG) - MCU broadcasts have payload[5]=0x01, payload[6]=0x00 — different! FLAG_WRITE constant changed to 0x20. build_status_cmd_ and set_sprinkler now explicitly set payload[5]=0x00, payload[6]=0x07 to match app format. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
Instead of aborting with a warning, populate last_status_payload_ with a safe default (16 bytes, valve=0, snooze=0, payload[6]=0x07) matching the observed app command format. Avoids "No STATUS cached" on first boot. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
- handle_status_: flags bit 0 (0x21) = short MCU ACK frame (7 bytes), log at DEBUG and return — stops "Status packet too short" warning - request_status(): sends type=0x03 poll frame to trigger MCU to send current STATUS+CONFIG; exposed as diagnostic button in HA - grohe_tap.yaml: add "Status abrufen" button https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
build_frame was adding '00 00 seq' before data AND data already contained '00 00 seq' as its first 3 bytes (from build_status_cmd_ and set_sprinkler). This sent 3 extra bytes per command, making the payload unrecognisable to MCU. Also fix length formula: data.size()+10 → data.size()+5, matching observed app frames (STATUS 16-byte payload → L=21, CONFIG 85-byte → L=90). STATUS commands now produce 39 bytes (was 42), matching app format. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
Without WiFi, the ESP8266 is silent and the MCU stops sending STATUS/CONFIG updates. The ESP32 now sends a type=0x03 poll frame every 30s (matching the format the MCU itself uses) to trigger MCU status broadcasts. Also start tx_seq_ at 0x01 (matching observed app frame sequence counters). https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
type=0x03 poll only triggers a type=0x03 response, not STATUS/CONFIG. Switching to a minimal STATUS read request (type=0x05, flags=0x20, 7-byte zero payload) to ask the MCU for its current state directly. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
The MCU sends periodic type=0x03 counter frames. The ESP8266 likely responds to these to remain the active controller. Without responses, the MCU ACKs commands but doesn't execute them. Now handle_water_data_ echoes the same frame back immediately when a heartbeat is received. This should cause the MCU to treat the ESP32 as the active controller and execute subsequent valve/snooze commands. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
App always sends payload[12]=0x01 in ALL control commands (valve open, valve close, both with snooze ON). Our default was 0x00. This byte appears to be a required 'active controller' flag - without it the MCU ACKs the frame but does not execute the command. https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a complete ESPHome integration for the Grohe Sense Guard smart water valve, enabling monitoring and control of the device via UART communication.
Key Changes
Protocol Implementation (
grohe_protocol.h): Defines the Grohe proprietary UART protocol with frame structure, message types (INFO, STATUS, CONFIG, HEARTBEAT, WATER_DATA), and helper functions for parsing incoming frames and building outgoing commands.Core Component (
grohe_sense_guard.h/cpp): Implements the mainGroheSenseGuardclass that:Sensor Integrations:
binary_sensor.py: Exposes valve state, snooze status, pressure test state, and sprinkler schedule (7 days)sensor.py: Exposes sprinkler start/stop times in minutestext_sensor.py: Exposes firmware version, raw frame hex dumps, and unknown frame diagnosticsExample Configuration (
grohe_tap.yaml): Complete working example showing UART wiring, sensor setup, and button controls for valve/snooze/sprinkler operations.Notable Implementation Details
https://claude.ai/code/session_014ddy1uWXvRhtvSD9oUf5RS