Problem With BassChipper's Hygrostat

Hi All, I’d appreciate some help diagnosing a problem, I think I’m missing something obvious…

I’m trying to use basschipper’s custom hygrostat

but it won’t run.
The home-assistant.log shows at startup

2024-03-10 12:36:23.629 WARNING (MainThread) [custom_components.generic_hygrostat.binary_sensor] Unknown humidity sensor 'sensor.bathroom_sensor_aqara_t1_humidity'

then every 5 mins

2024-03-10 12:41:23.631 ERROR (MainThread) [homeassistant] Error doing job: Exception in callback _run_async_call_action(<HomeAssistant RUNNING>, <Job track ti...t.py:1506>>)>>) at /usr/src/homeassistant/homeassistant/helpers/event.py:1506
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/src/homeassistant/homeassistant/helpers/event.py", line 1510, in _run_async_call_action
    hass.async_run_hass_job(job, time_tracker_utcnow())
  File "/usr/src/homeassistant/homeassistant/core.py", line 727, in async_run_hass_job
    hassjob.target(*args)
  File "/usr/src/homeassistant/homeassistant/helpers/event.py", line 1604, in _interval_listener
    hass.async_run_hass_job(self._run_job, now)
  File "/usr/src/homeassistant/homeassistant/core.py", line 727, in async_run_hass_job
    hassjob.target(*args)
  File "/config/custom_components/generic_hygrostat/binary_sensor.py", line 147, in _async_update
    self.update_humidity()
  File "/config/custom_components/generic_hygrostat/binary_sensor.py", line 187, in update_humidity
    self.sensor_humidity = float(sensor.attributes[self.sensor_attribute])
                                 ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
KeyError: 'humidity'

I’m guessing the two problems are related, i.e. it can’t see the sensor. But I can’t work out why, any suggestions? I can see the sensor just fine from the HA UI, and have added a card to the dashboard to display humidity, works fine. I’ve double-checked the entity ID is “sensor.bathroom_sensor_aqara_t1_humidity”

Any pointers gratefully received. Debugging this sort of stuff in HA is a bit beyond me…

I have the same issue - and sadly not the solution. But I do know that this error is happening now that I’ve switched from Zigbee2mqtt to the HA Zigbee solution (to simplify the setup as I’ve sold the house and will leave the HA setup to the new owner). The humidity sensor (Aqara WSDCGQ11LM) is the same so I assume certain values are labeled or transmitted differently something something.

I managed to generate a corrected version of binary_sensor.py with the help of AI (Claude - how bloody terrific that is). So I have it working now. I’ve made a fork on my own Github - but it requires manual installation as it isn’t submitted to HACS:

And these are the changes as summarised by Claude himself:
I’ll highlight the key differences between the original and updated code. Here are the main changes:

  1. Update Mechanism Changes:
# Original
@callback
def _async_update(self, now=None):
    self.update_humidity()
    if self.sensor_humidity is None:
        return

# Updated
@callback
async def _async_update_ha(self, now=None):
    """Update the entity."""
    self._async_update(now)
    self.async_write_ha_state()

@callback
def _async_update(self, now=None):
    """Update the entity."""
    self.update_humidity()
  1. Entity Lifecycle Management (Added):
# Added in Updated Version
async def async_added_to_hass(self):
    """Run when entity about to be added."""
    await super().async_added_to_hass()
    
    # Perform initial update
    self.update_humidity()
    
    # Start periodic updates
    self._remove_update_interval = async_track_time_interval(
        self.hass, self._async_update_ha, self._sample_interval
    )

async def async_will_remove_from_hass(self):
    """Run when entity will be removed."""
    if self._remove_update_interval is not None:
        self._remove_update_interval()
  1. Humidity Update Logic:
# Original
def update_humidity(self):
    sensor = self.hass.states.get(self.sensor_id)
    if sensor is None:
        raise ValueError("Unknown humidity sensor")
    if sensor.state == STATE_UNKNOWN:
        raise ValueError("Humidity sensor has unknown state")
    try:
        if self.sensor_attribute:
            self.sensor_humidity = float(sensor.attributes[self.sensor_attribute])
        else:
            self.sensor_humidity = float(sensor.state)
        self.add_sample(self.sensor_humidity)
    except ValueError:
        raise ValueError("Unable to update from sensor")

# Updated
def update_humidity(self):
    sensor = self.hass.states.get(self.sensor_id)
    if sensor is None:
        _LOGGER.warning("Unable to find sensor %s", self.sensor_id)
        return
    try:
        value = float(sensor.state)
        self.sensor_humidity = value
        self.add_sample(value)
    except (ValueError, KeyError) as ex:
        _LOGGER.error("Unable to update from sensor: %s", ex)
        return
  1. Enhanced Logging:
# Added throughout the code
_LOGGER.debug("%s: Sample added. Total samples: %d, Values: %s", 
    self.name,
    len(self.samples),
    list(self.samples)
)

_LOGGER.debug(
    "Updating humidity for %s from sensor %s (current state: %s)", 
    self.name, 
    self.sensor_id,
    sensor.state if sensor else "None"
)
  1. State Management:
# Original
def set_state(self, state):
    if self._state is not state:
        self._state = state
        self.schedule_update_ha_state()

# Updated
def set_state(self, state):
    if self._state != state:
        self._state = state
        _LOGGER.debug("%s: State changed to %s", self.name, state)

Key Improvements:

  1. Error Handling:

    • Changed from raising exceptions to logging warnings
    • Added more graceful error recovery
    • Better error messages
  2. Lifecycle Management:

    • Added proper initialization and cleanup
    • Better management of update intervals
    • Proper cleanup when entity is removed
  3. Debugging:

    • Added extensive logging
    • Better visibility into state changes
    • More detailed error information
  4. State Management:

    • More robust state handling
    • Better handling of sensor updates
    • Improved sample collection
  5. Code Organization:

    • Better separation of concerns
    • More consistent method naming
    • Better documentation

These changes make the component:

  • More robust (better error handling)
  • Easier to debug (more logging)
  • More maintainable (better code organization)
  • More reliable (better state management)
  • More efficient (better update mechanism)