App that cycles a lights brightness between max and min while a button/switch is held down

Has anyone seen anything that achieves this? I’ve tried but I think it’s a little beyond my abilities at the moment and I’d love to see how it’s done!

first you have to have a button connected to the RPI and some python code to read the value from that button continiously.
but this is completely independant from HA, because HA cant read a switch that way.

Yeah I think I can get the events for the button on/off easily enough using appdaemon.

But making a loop that updates brightness with a delay between updates while pressed on. Then that loop being cancelled when the button is released.

I don’t know how to get the brightness to loop and then break it on the button release event all the while not using sleep.delay()!

a loop like:

  def initialize(...):
    self.going_up = True
    self.delay = 1
    self.minimum = 0
    self.maximum = 255
    self.step = 10
    self.listen_state(self.startfunc,"switch_id")

  def startfunc(self,entity,attributes,old,new,kwargs):
    if new == "on":
      self.run_in(self.runloop,self.delay)

  def runloop(self.kwargs):
    self.brightness = int(self.get_state("light.id","brightness")) #or float if that gives it back
    if self.going_up:
      self.brightness = self.brightness + self.lightstep
      if self.brightness > self.maximum:
        self.brightness = self.maximum
        self.going_up = False
    else:
      self.brightness = self.brightness - self.lightstep
        if self.brightness < self.minimum:
          self.brightness = self.minimum
          self.going_up = True
    self.set_state("light.id",brightness=brightness)
    if check_for_button_state(): 
      self.run_in(self.runloop,self.delay)

  def check_for_button_state():
    if self.get_state("switch.id"):
      return True
    else:
      return False
1 Like

I’ve done that once and it works quite well: Rotating brightness for lights triggered by events

1 Like

Thanks guys, I really appreciate the help!

I was really struggling to get my head around the recursion aspect of it all!

Got it working like this:

import appdaemon.appapi as appapi

class Test(appapi.AppDaemon):
    def initialize(self):
        self.log("Test App Started.")

        self.going_up = True
        self.delay = 1
        self.minimum = 0
        self.maximum = 255
        self.step = 10
        self.switch_id = "input_boolean.rain_switch"
        self.light_id = "light.adams_study"
        self.listen_state(self.startfunc, self.switch_id)


    def startfunc(self, entity, attributes, old, new, kwargs):
        if new == "on":
            self.handle = self.run_in(self.runloop, self.delay)


    def runloop(self, kwargs):
        self.brightness = int(self.get_state(self.light_id, "brightness"))  # or float if that gives it back
        if self.going_up:
            self.brightness += self.step
            if self.brightness > self.maximum:
                self.brightness = self.maximum
                self.going_up = False
        else:
            self.brightness = self.brightness - self.step
            if self.brightness < self.minimum:
                self.brightness = self.minimum
                self.going_up = True

        self.turn_on(self.light_id, brightness=self.brightness)
        if self.check_for_button_state():
            self.run_in(self.runloop, self.delay)


    def check_for_button_state(self):
        if self.get_state(self.switch_id) == "on":
            return True
        else:
            return False

Can I do a delay using run_in() with less that 1 second? eg. self.run_in(self.callback, 0.1)

It’s a little too slow for my liking and I’d like to speed it up!

1 Like

i am not sure.
could be that it works, so just try out.
@aimc could tell you if you can have a run in shorter then 1 second.

I have tried, it doesn’t appear to work when i try it. It takes the value and defaults to 1 second and im definitely sending in a float < 1.0. I’d like it to be a little more responsive. Every second is really clunky

1 Like

The AppDaemon scheduler runs at 1 second intervals, and I have no current plans to change this. Trying to send multiple ZWave or whatever messages to a light in a second is probably going to tax your network anyway.

Ok no worries, thanks for the response. I did originally try to achieve this using sleep.delay() and the light was surprisingly responsive, I had is strobing with 0.1 second delays and everything! But obviously that isn’t a real solution…

Not necessarily … you could have the strobing effect in a separate thread altogether. :slight_smile: But then you’d need a new thread for each light you wish to strobe.

Does using the run_in() create a new thread in which I can do this? Can you suggest the best way you would achieve this?

I’m not at all familiar with creating a multiple thread app!

For anyone that may be interested, I finally learnt how to thread and can get delays below 1 second using time.sleep without breaking appdaemon!

Code:

import appdaemon.appapi as appapi
import threading
import time

### Comments ###

### Args ###
"""
# switch_id: {entity_id of switch}
# light_id: {entity_id of light}
# delay: {1 = one second. Can go below 1 second. Anything below 0.4 was not working well for me}
# step: {how many brightness steps to go up/down per change}
# minimum: {brightness to go to. Lowest = 0}
# maximum:{brightness to go to. Highest = 255}

EXAMPLE appdaemon.yaml entry:

adams_bedside_light_switch_LCB:
  module: lights_cycle_brightness
  class: Lights_Cycle_Brightness
  switch_id: binary_sensor.switch_158
  light_id: light.bedside_one
  delay: 0.4
  step: 25
  minimum: 25
  maximum: 250
"""

class Lights_Cycle_Brightness(appapi.AppDaemon):
    def initialize(self):
        self.log("LCB Started.")
        self.going_up = True

        self.delay = float(self.args["delay"])
        self.minimum = int(self.args["minimum"])
        self.maximum = int(self.args["maximum"])
        self.step = int(self.args["step"])
        self.switch_id = self.args["switch_id"]
        self.light_id = self.args["light_id"]
        
        for switch in self.split_device_list(self.switch_id):
            self.listen_state(self.start_func, switch)


    def start_func(self, entity, attributes, old, new, kwargs):
        if new == "on":
            self.t = threading.Thread(target=self.run_thread, args=(entity,))
            self.t.start()

    def run_thread(self, switch):
        while(self.get_state(switch) == "on"):
            if self.get_state(self.light_id) == "off":
                self.turn_on(self.light_id)

            try:
                self.brightness = int(self.get_state(self.light_id, "brightness")) # get lights current brightness
            except:
                while(self.brightness == None and self.get_state(switch) == "on"):
                    try:
                        self.brightness = int(self.get_state(self.light_id, "brightness"))
                    except:
                        pass

            if self.going_up:
                self.brightness += self.step
                if self.brightness > self.maximum:
                    self.brightness = self.maximum
                    self.going_up = False
            else:
                self.brightness = self.brightness - self.step
                if self.brightness < self.minimum:
                    self.brightness = self.minimum
                    self.going_up = True

            self.log("{} changed to brightness: {}".format(self.light_id, self.brightness))

            self.turn_on(self.light_id, brightness=self.brightness)
            time.sleep(self.delay)

that only works when you have 2 different entities for the switch and the light, but a nice workaround.

1 Like

What do you mean? It works with one or more switch entitiy_id’s seperated by a comma?

i mean that it wont work if you dont have a seperate switch.

i dont have seperate switches and lights in HA.

but never mind, because without a seperate switch the app is useless, so i was thinking wrong :wink:

Oh, yeah that’s totally right. I just couldn’t think of using it any other way than with a held down switch :stuck_out_tongue:

1 Like