Cancel_timer confusion

I’m trying to make motion light app and now i’m completely lost. It works most of time but sometimes if just fails. I use xiaomi motion sensor and when there is motion it stays on for 1 minute then wait for 100 seconds to turn light off when it goes to off. Here is my log when it works ok:

2017-12-27 17:33:20.342212 INFO bathroom_light_day: Light on because motion is on
2017-12-27 17:38:36.029955 INFO bathroom_light_day: Set brightness to 30 and turn off light in 10 seconds
2017-12-27 17:38:37.362599 INFO bathroom_light_day: Light on because motion is on

But sometimes even if it detects motion brightness is set to 30 and it just turn off light. Like this:

2017-12-27 17:18:58.747447 INFO bathroom_light_day: Light on because motion is on
2017-12-27 17:26:33.546852 INFO bathroom_light_day: Light on because motion is on
2017-12-27 17:26:46.029043 INFO bathroom_light_day: Set brightness to 30 and turn off light in 10 seconds
2017-12-27 17:26:56.026499 INFO bathroom_light_day: Light turn off
2017-12-27 17:26:56.227448 INFO bathroom_light_day: reset handle, because light.bathroom was turned off

Why it behaves like this?

import appdaemon.appapi as appapi
class MotionLight(appapi.AppDaemon):
  def initialize(self):
    self.handle = None

    if "sensor" in self.args:
      self.listen_state(self.motion_on, self.args["sensor"], new="on")
      self.listen_state(self.motion_off, self.args["sensor"], new="off", duration=self.args["delay"])
    else:
      self.log("No sensor specified, doing nothing")

    if "entity_id" in self.args:
      self.listen_state(self.off, self.args["entity_id"])
    else:
      self.log("No light specified, doing nothing") 

  def off(self, entity, attribute, old, new, kwargs):
    if self.get_state(self.args["entity_id"]) == "off":
      self.log("reset handle, because {} was turned off".format(self.args["entity_id"]))
      self.cancel_timer(self.handle)
      self.handle = None
    
  def motion_on(self, entity, attribute, old, new, kwargs):
    self.log("Light on because motion is on")
    self.turn_on(self.args["entity_id"], brightness = int(self.args["brightness"]))
    self.cancel_timer(self.handle)
    self.handle = None

  def motion_off(self, entity, attribute, old, new, kwargs):
    if self.get_state(self.args["entity_id"]) == "on":
      self.log("Set brightness to 30 and turn off light in 10 seconds")
      self.call_service("light/turn_on", entity_id = (self.args["entity_id"]), brightness = 30)
      self.cancel_timer(self.handle)
      self.handle = self.run_in(self.light_off, 10)
   
  def light_off(self, kwargs):
    self.log("Light turn off")
    self.turn_off(self.args["entity_id"])

bathroom_light_day:
  module: bathroom_light
  class: MotionLight
  sensor: binary_sensor.motion_sensor_xxxx
  entity_id: light.bathroom
  constrain_start_time: '07:00:00'
  constrain_end_time: '20:59:59'
  delay: 100
  brightness: 255

Looks like some confusion around timers and the duration parameter of listen_state().

I don;t see anywhere where you are actually setting a timer to turn the light off after a given period of time - you should use one of the scheduler callbacks to do this.

self.listen_state(self.motion_off, self.args["sensor"], new="off", duration=self.args["delay"])

This probably isn’t doing what you think it is - it will fire the event when the light has been off for delay seconds - it isn’t setting any kind of timer and you can’t cancel it with cancel_timer() - it really isn’t meant for that kind of use.

Instead use run_in() or something similar - that allows you to save the handle and later cancel if necessary.

1 Like

to clarify it a bit more, what you want is:

if trigger (listen_state) for some time (duration)
  set a timer(run_in in callback)

if trigger again(same listen_state) for some time (duration)
  cancel timer (cancel_timer)
  set a timer(run_in in callback)

there is a motion app in the example apps that does all that.

1 Like

Thank you @aimc and @ReneTode. Yes i thought that this

self.listen_state(self.motion_off, self.args["sensor"], new="off", duration=self.args["delay"])

sets timer. :frowning: I know there is example app for light but because how xiaomi motion sensor works i need to check off state to set a timer and also i wanted to change brightness for 10 seconds before it turns off light. And that was beyond my knowledge. I will try with example motion light once again to see if i could set it right.

1 Like

read this part of the docs carefully again and i think you will get far :wink:
http://appdaemon.readthedocs.io/en/latest/APIREFERENCE.html

in particular the parts about

self.handle = self.run_in(self.run_in_c)
self.cancel_timer(self.handle)

1 Like

Thanks again. I will try. :slight_smile:

1 Like