I'd put native ESPHome support near the top of a wish list. @support_team
1 Like
its going to be really tricky for them - since ESPHome is now almost a competitor the foundation is closely tied to HA Foundation. Also I looked at the encrpytion support and it will be pretty much impossible to do on older Hubitat hw. Think HADB is the way to go
1 Like
Wonder why I didn't see it - will look again thanks.
here is the updated driver @bill.d - I added your espHomeConnectRequest changes to it as well also fixed a continous disconnect that you see in ESPHome device longs @wyn.carlton took changes from @nate fork as well as @nclark fork -- Please test if you can and let me know if you find bugs, I will raise a PR for @jonathanb
change log
/**
* ============================================================================
* CHANGE LOG (relative to Bradshaw v1.2 baseline)
* ============================================================================
*
* v1.3 2026-02-28 (contributors: enishoca, nclark, heythisisnate, community fixes)
*
* FIX-1 openSocket()
* Clear espReceiveBuffer BEFORE connect to prevent stale bytes from a
* prior session corrupting the first frame after reconnect.
*
* FIX-2 socketStatus()
* Ignore errno=11 (EAGAIN / EWOULDBLOCK). ESP8266 non-blocking TCP
* stack emits this transiently; treating it as a hard error caused
* unnecessary disconnects on that platform.
*
* FIX-3 espHomeSwitchCommand()
* key cast to Long (was Integer). ESPHome entity keys are uint32 on
* the wire; values > 2147483647 overflowed to negative with Integer,
* sending the wrong key and silently failing the command.
*
* FIX-4 espHomeButtonCommand()
* Same Long key fix as FIX-3.
*
* FIX-5 All other command functions (cover, fan, light, lock, media, number,
* select, siren, executeService)
* Promoted key to Long for consistency. No existing device is broken
* by this; it only fixes devices whose key happens to be > 2^31-1.
*
* FIX-6 espHomeConnectRequest() / Handshake stall + missing ping scheduler
* Two related bugs fixed in the same method:
*
* (a) HANDSHAKE STALL: ESPHome API >= 1.12 (2024.12+) does NOT send a
* ConnectResponse (type 4) on success, even with a password set.
* Waiting for it caused the connection to stall indefinitely.
* Fixed: branch now checks negotiated API version from HelloResponse
* (state.apiVersionMajor/Minor). API <= 1.11 waits for
* ConnectResponse as before. API >= 1.12 advances immediately.
*
* (b) MISSING PING SCHEDULER: The new API >= 1.12 fast path was not
* calling espHomeSchedulePing() before runInMillis('espHomeDeviceInfoRequest').
* Without it, healthCheck() was never registered by runIn(), so no
* keepalive pings were ever sent. The Hubitat raw socket platform
* silently dropped the idle TCP connection every ~120 seconds.
* Symptom: ESPHome device logs showed repeated
* "Reading failed CONNECTION_CLOSED errno=11" every ~2 minutes.
* Fixed: espHomeSchedulePing() added to the API >= 1.12 path.
*
* FIX-7 espHomeBluetoothLeResponse()
* Null-safe UUID in manufacturer-data block. Devices that advertise
* a manufacturer-data record with an empty UUID field threw a
* NullPointerException when .toLowerCase() was called on null.
*
* FIX-8 hexDecode()
* payload.length instead of payload.size(). On a Java primitive
* byte[] array, .size() is not defined and silently returns 0 in
* some Groovy/JVM configurations, causing buffer.write() to copy
* zero bytes and drop every incoming packet.
*
* FIX-9 stashBuffer()
* Re-write the 0x00 frame-delimiter byte before stashing the partial
* payload. Without it, when parse() resumes on the next TCP chunk it
* reads the first varint byte as the delimiter, miscounts the length,
* and corrupts every reassembled multi-chunk message.
*
* FIX-10 espHomeDeviceInfoResponse()
* Removed duplicate updateDataValue 'Board Model' call (wrote the
* same key twice in one device.with{} block).
*
* FIX-11 handleNoiseProtocolDetected() (new private helper)
* The prior 0x01 Noise-byte handler in parse() only logged a warning
* and returned. Problems:
* (a) The socket stayed open, so the hub hammered the device every
* PING_INTERVAL_SECONDS with more failed frames.
* (b) The warning was easy to miss; no fix instructions were given.
* (c) reconnectDelay was never raised, causing rapid reconnect loops.
* Fixed:
* • parse() now calls handleNoiseProtocolDetected() on 0x01.
* • That method logs an ERROR with a one-line fix instruction
* (remove api_encryption: from YAML + re-flash).
* • Calls closeSocket() then raises reconnectDelay to
* MAX_RECONNECT_SECONDS before scheduling the next connect.
* • Sets state.noiseDetected = true so drivers/UIs can query it.
* Backward compatible: drivers on plaintext devices are untouched.
* Drivers on encrypted devices now fail fast with a clear fix path
* instead of looping silently.
*
* FIX-12 PING_INTERVAL_SECONDS reduced from 120 to 60
* Hubitat's raw socket platform silently closes TCP connections after
* ~120 seconds of inactivity. With the prior value of 120s the
* healthCheck/ping was scheduled to fire at 120-179s — almost always
* AFTER the platform had already killed the socket. Reducing to 60s
* ensures a keepalive ping is sent well within Hubitat's idle window
* and also satisfies ESPHome's own 60s server-side API timeout.
* Symptom: ESPHome device logs showed "Reading failed CONNECTION_CLOSED"
* every ~120 seconds followed by a double reconnect sequence.
*
* ADD-1 espHomeListEntitiesClimateResponse() [msg 46]
* Full climate entity decoder. Fields 5-25 per current api.proto
* including humidity support, temperature step, entityCategory, icon.
* NOTE: supportedPresets reads tag 16 (nclarck had tag 15, which
* collides with supportedCustomFanModes — that bug is fixed here).
*
* ADD-2 espHomeClimateState() [msg 47]
* Climate state decoder with field numbers verified against live
* ESPHome device captures and current api.proto. nclarck's version
* had all fields after target_temperature offset by -3 due to not
* accounting for the removal of legacy dual-setpoint fields.
* Correct mapping:
* 1=key 2=mode 4=target_temperature 5=current_temperature
* 6=fan_mode 7=swing_mode 8=action 9=custom_fan_mode
* 10=preset 11=custom_preset 12=current_humidity 13=target_humidity
*
* ADD-3 espHomeClimateCommand() [msg 48]
* Climate command encoder. key uses Long (FIX-3/4 consistency).
* Uses explicit containsKey() guards so only fields the caller
* actually sets are included in the wire message — avoids sending
* null/zero values that would reset modes the caller did not intend
* to change.
*
* ADD-4 parseMessage() routing
* Added case MSG_LIST_CLIMATE_RESPONSE (46) and
* MSG_CLIMATE_STATE_RESPONSE (47) to the switch dispatcher.
*
* ADD-5 CLIMATE_FAN_QUIET = 9
* Missing enum constant present in ESPHome firmware but absent from
* all known Hubitat forks of this library.
*
* ADD-6 toClimateSupportedModes()
* Static helper to convert a ClimateMode int to a human-readable
* string. Useful for drivers that log or display supported modes.
*
* ADD-7 MSG_AUTHENTICATION_REQUEST / MSG_AUTHENTICATION_RESPONSE
* ESPHome 2025.10 renamed ConnectRequest→AuthenticationRequest in the
* proto source. Wire message type numbers (3 and 4) are UNCHANGED so
* this is a naming-only update. Backward-compatible aliases
* MSG_CONNECT_REQUEST and MSG_CONNECT_RESPONSE are retained so all
* existing drivers continue to compile without modification.
*
* ADD-8 parseEntity() — deviceId field (ESPHome 2025.7+ sub-device support)
* parseEntity() now returns a fifth key:
* deviceId: String — empty string ('') on pre-2025.7 devices
* (field absent → getStringTag default)
* Wire proto field number is defined by ENTITY_DEVICE_ID_PROTO_FIELD.
* See that constant's block comment for verification instructions.
* Backward compatible: all existing drivers that do not reference
* deviceId continue to compile and run without modification.
* Drivers that want sub-device routing check:
* if (entity.deviceId) { route to child }
*
* ADD-9 ENTITY_DEVICE_ID_PROTO_FIELD constant
* Centralises the proto field number for device_id so a one-line
* change updates all entity decoders simultaneously.
* *** MUST BE VERIFIED against api.proto before sub-device routing
* is activated in a driver — see the constant's block comment. ***
*
* ============================================================================
* KNOWN LIMITATIONS (remaining)
* ============================================================================
*
* - Noise protocol encryption (ESPHome api_encryption:) now fails fast with
* a clear fix instruction and throttled reconnects (FIX-11 above). Full
* Noise handshake / decryption is still not implemented; remove
* api_encryption: from the ESPHome YAML to use this driver.
*
* - Sub-device routing (ESPHome 2025.7+) is now DECODED (ADD-8) — the
* deviceId field is present in every entity map. However, acting on it
* (creating child devices, routing state to them) is a driver-level
* responsibility outside this library. The library change is zero-risk
* for single-device YAML configs where deviceId is always ''.
*
* - ENTITY_DEVICE_ID_PROTO_FIELD field-number verification (ADD-9):
* The ESPHome api.proto adds device_id to each ListEntitiesXxxResponse at
* a field number that must not conflict with existing entity-specific
* fields. The constant is set to the value inferred from api.proto
* inspection; confirm by searching "device_id" in the ESPHome or
* aioesphomeapi api.proto before enabling child-device routing in a driver.
* On pre-2025.7 devices the field is simply absent → deviceId = ''.
*
* - ESPHome 2025.10 HomeassistantServiceResponse was renamed
* HomeassistantActionRequest at the proto source level. Wire message
* type number (35) is unchanged; no functional impact.
*/