HassOS deconz addon missing refresh of sensors

I’m struggling with this problem too: I have no idea is my water leak sensor alive or not. Is there a PR created for adding last updated information to sensor attributes? This way everyone - depending on the use case - can determine when a sensor is considered to be unavailable.

If it is truly unavailable deconz will tell hass and hass will mark it as unavailable

What means “truly”? I removed the battery of the sensor and after 9 hours it is still available. This is not acceptable for sensor that should alert a water leak. Acceptable would be something like 5-30 minutes. Even then unavailability wouldn’t always tell wether the sensor is out of coverage from time to time. Note that water sensor is often in difficult places from radio point of view; under machines, closets, etc.

Well it would be deconz logic that needs to be looked over in that case

…and that means that PR should be created, right? Another way would be checking the last update via REST API ( as suggested in #9), but why not supporting it by default because there are use cases where that information is somewhat mandatory in order to build reliable alerting system.

I solved it for me by making a custom component which periodically checks the latest sensor refresh. The xiaomi temperature sensor also has humidity and pressure aside temperature so I check all three per sensor. The refresh rate of the three is quite high (within 3 hours) thus allows short check intervals. When the interval time is exceeded a telegram message is send.

Lete know if you would like the code …

See here for the discussion with deconz in case you hadn’t found it yet

https://github.com/dresden-elektronik/deconz-rest-plugin/issues/970#issuecomment-441373286

I’m interested in your solution. Can you show your code? I suppose you need to use the REST API? Would be much more easier if it was supported by default somehow. Seems that the deconz issue you linked has already been closed without doing any changes…

it runs in appdaemon:

in apps.yaml

sensors_update:
  module: sensor_updates
  class: SaveSensorState
  event: deconz_event
  file: /config/data/.sensors
  check_interval: 10800
  blacklist: ""
  whitelist: [[sensor.temp_1t,sensor.temp_1h,sensor.temp_1p],
              [sensor.temp_2t,sensor.temp_2h,sensor.temp_2p],
              [sensor.temp_3t,sensor.temp_3h,sensor.temp_3p],
              [sensor.temp_4t,sensor.temp_4h,sensor.temp_4p],
              [sensor.temp_5t,sensor.temp_5h,sensor.temp_5p],
              [sensor.temp_6t,sensor.temp_6h,sensor.temp_6p]] 

sensor.temp_*t,sensor.temp_*h,sensor.temp_*p is one xiaomi sensor

in sensor_updates.py (note it is not cleaned yet :wink: )

import appdaemon.plugins.hass.hassapi as hass
import shelve
from datetime import datetime
import os

#
# App to notify if a sensor/device_tracker is dead for check_interval seconds
# Source: https://community.home-assistant.io/t/app-to-monitor-sensors/22470/8
# Args:
#
#file: db file to save state and lastchanged to persist HA shutdown
#blacklist: list of sensors/device_trackers not to track.
#check_interval: time interval between check in seconds
# Release Notes
#

def index_2d(data, search):
    
    for i, e in enumerate(data):
        try:
            return i, e.index(search)
        except ValueError:
            pass
    # raise ValueError("{} is not in list".format(repr(search)))

class SaveSensorState(hass.Hass):



  
  def initialize(self):
    
    
    self.timeout_length = self.args["check_interval"]
    # self.dec_sensor = self.args['target']['sensor']
    self.blacklist = self.args["blacklist"]
    self.whitelist = self.args["whitelist"]

    try:
       self.device_db = shelve.open(self.args["file"],flag='n')       
    except:
       os.remove(self.args["file"])
       self.device_db = shelve.open(self.args["file"],flag='n')

       
    # if 'event' in self.args:
       # self.listen_event(self.state_change2, self.args['event'])
       
    self.log("starting live check thermostat sensors")
    self.listen_state(self.state_change, "sensor") 
    # self.listen_event(self.state_change2, "deconz_event")
    
    # if 'event' in self.args:
        # self.listen_event(self.state_change2, self.args['event'])
    #self.listen_state(self.state_change, "device_tracker")
    start_time = datetime.now()
    self.run_every(self.check_if_updated,start_time, self.timeout_length)


  def state_change(self,entity, attribute, old, new, kwargs):
    
    
    if not self.whitelist :
        if not entity in self.blacklist:
          #self.log("State change in {}: from {} to {}".format(entity, old, new))
          now = datetime.now()
          data = {'state': new, 'time': now}
          self.device_db[entity] = data
          #self.log("Sensor {} value changed to {} at {}".format(entity, new, now))
    else:
        # self.log("checking {}".format(entity))
        if index_2d(self.whitelist, entity):
          # self.log("state change entity: {}".format(entity))
        # if entity in self.whitelist:
          group_id,sens_id =index_2d(self.whitelist, entity)
          #self.log("State change in {}: from {} to {}".format(entity, old, new))
          now = datetime.now()
          elapsed =(now - self.device_db[str(group_id)]['time'] ).microseconds
          if (elapsed>0):
              data = {'entity': entity , 'state': new, 'time': now}
              # self.log("group {}, logging: {}".format(group_id,data))
              self.device_db[str(group_id)] = data
          #self.log("Sensor {} value changed to {} at {}".format(entity, new, now)) 
  
  def check_if_updated(self, kwargs):
    #self.log("Checking sensor health")
    message = ""
    state = self.get_state()
    for entity in state:
      if not self.whitelist :
          if not entity in self.blacklist:
            type, id = entity.split(".")
            #if type == "sensor" or type == "device_tracker":
            if type == "sensor":
              if entity in self.device_db:
                oldState = self.device_db[entity]['state']
                oldTime = self.device_db[entity]['time']
                #self.log("Old state for {} was {} and old time was {}".format(entity, oldState, oldTime))
                if (self.device_db[entity]['state'] == state[entity]["state"]):
                  now = datetime.now()
                  elapsedTime = now - oldTime
                  if elapsedTime.seconds > self.timeout_length:
                    if message =="":
                      message =  entity + ' (' + str(round(elapsedTime.seconds/60,0))  + ' min)'
                    else:
                      message = message + ', ' + entity + ' (' + str(round(elapsedTime.seconds/60,0))  + ' min)'
                    #self.log("Sensor {} is in {} state for 24 hours.".format(entity, oldState))
                else:
                  now = datetime.now()
                  data = {'state': state[entity]["state"], 'time': now}
                  self.device_db[entity] = data
              else:
                # self.log("Adding {}, setting value to current state ({})".format(entity, state[entity]["state"]))
                now = datetime.now()
                data = {'state': state[entity]["state"], 'time': now}
                self.device_db[entity] = data
      else :
          if index_2d(self.whitelist, entity):
            # self.log("checking {}".format(entity))
            group_id,sens_id =index_2d(self.whitelist, entity)
            type, id = entity.split(".")
            #if type == "sensor" or type == "device_tracker":
            if type == "sensor":
              if str(group_id) in self.device_db:
                # self.log("updating {}-{}".format(group_id,entity))
                oldEntity =self.device_db[str(group_id)]['entity']
                oldState = self.device_db[str(group_id)]['state']
                oldTime = self.device_db[str(group_id)]['time']
                # self.log("Old state for group_id {} by {} was {} ".format(group_id, oldEntity,oldTime))
                if (self.device_db[str(group_id)]['entity'] == entity):
                  now = datetime.now()
                  elapsedTime = now - oldTime
                  if elapsedTime.seconds > self.timeout_length:
                    if message =="":
                      message =  str(group_id) + '-'  + entity + ' (' + str(round(elapsedTime.seconds/60,0))  + ' min)'
                    else:
                      message = message + ', ' + str(group_id) + '-'  + entity + ' (' + str(round(elapsedTime.seconds/60,0))  + ' min)'
                    # self.log("group_id {} sensor {} is last updated in {} ago.".format(group_id,entity, elapsedTime))
                # else:
                  # now = datetime.now()
                  # elapsed =(now - self.device_db[str(group_id)]['time'] ).microseconds
                  # self.log("other entity: {}, oldtime : {} ,newtime {}, dff {}".format(entity,oldTime,now,elapsed)) 
                  
                  # if (elapsed>0):
                      # self.log("group {}, logging: {}".format(group_id,data))
                      # data = {'entity':entity,'state': state[entity]["state"], 'time': now}
                      # self.device_db[str(group_id)] = data
              else:
                  # self.log("group_id not included yet, adding group_id {} by sensor {}".format(group_id,entity)) 
                  now = datetime.now()
                  data = {'entity':entity,'state': state[entity]["state"], 'time': now}
                  self.device_db[str(group_id)] = data           
                
                
                
    if message != "":
      self.log("Sensor {} has not changed for too much time. Consider reseting.".format(message))
      #self.notify("Sensors {} have not changed for 24 hours".format(message), name = "as_email", title = "Message from AppDaemon")
      tgmmessage = "{} has not changed for too much time. Consider reseting.".format(message)
      self.call_service("notify/notify_alex",message = tgmmessage)


I think that hass has a last updated state for the entity, that should be the same as for the sensors

It is the same if sensor is unavailable, but if available then phoscon shows the latest refresh that is different than in hassio for some reason. Hassio seems to show time from the latest change in value.

I have had not tried the code provided by axax, but it seems that the situation has changed a bit, but still not ok.

Phoscon still shows latest updates, but HA shows unavailable. Is this still the same issue?

Would you have some guidance to show how to do this with websockets? I’m already using nodered.

Use a websocket node with the adress ws://YOURIP:8443 (or whatever your deconz web socket port is). Then you add a json-convert node, and pass that into a function. For instance

if (msg.payload.id === "39") {
    
    msg.payload = msg.payload.state.buttonevent;
    return msg;
} 

and in this case the id 39 refers to an ikea button I think.

But all of these things I added way back when deconz was new more or less. I think now the HA implementation is pretty instantaneous. And I also think that back then my database was ballooned and made everything slow within HA.

1 Like

Thanks! I’ll give it a go once I have a chance.

I’m not able to access REST API.

For some reason

https://dresden-light.appspot.com/discover

returns internal address and port as:

172.30.33.0 and 40850

Surely this is not correct? I’m running Hassio and deconz in a single rspi. Those work with each other though.

Even though not able to access the API i managed to receive status updates to Node-Red using the existing deconz palette. There seem to be updates with the same timestamp than in Phoscon, even though any value did not change - which is missing at Hassio.

My first thought was that I will periodically ask state of the device and check the last updated timestamp, but for that I would need to get connection working to deconz server and that is not working for some reason. This is seen so that it fails to fetch the list of devices from deconz. But as long as Node-Red receives updates from Deconz I guess the second option is to verify that time between the received updates is not too long. I will give it a try.

Edit.

In case someone else is trying to figure this out here is how I got it working.

Edit2.

The longest sleep period Xiaomi water sensor seems to have is around 50 minutes, so the best value for the timers in the above flow is about 52 minutes. With that value I get notified of unavailable sensor average 26 minutes but latest 52 minutes after sensor failure etc.

1 Like

Just bought myself an xiaomi water leak sensor, added it to deconz
when i place it in water i see the binary_sensor changing to on, when i take i out, it changes to off?

so i dont understand the issue in this thread?

Hi,
I’m using the Marthoc deconz docker container on Raspian Buster with HA 0.103.5. The issue is the same. If I remove the battery from a Xiaomi sensor (e.g. water leak sensor) for hours I still see the sensor as beeing available in HA. Does anyone know if zigbee2mqtt is smarter in that respect? Or even ZHA in HA?
Thanks

A bit late, but for anyone else who stumbles onto this thread I had this same problem and was able to solve it by creating a REST sensor that gets its values directly from deCONZ. See the solution here.

Turns out the last_changed value reported by deCONZ to HA is not the same value as reported in Phoscon, but the correct value is available via the REST API.