ControllerX. Bring full functionality to light and media player controllers

Tags: #<Tag:0x00007f77fe259068>

Hi guys,

I would like to share an AppDaemon I have been working on. In my setup, I use Zigbee2MQTT and some IKEA devices. I bought 2 IKEA E1524/E1810 and 1 IKEA E1743. When trying to integrate the controllers I faced two problems:

  • I couldn’t find any integration for these controllers to work smoothly to dim and brighten the light (brightness and color temperature). So I created a very long spaghetti automation yaml to solve. You can find the solution in here
  • Once I had a working solution. I had to copy and paste the same automation for the other controller and light, which I did not like.

With all this, I came up with the idea to create an app for AppDaemon so I could share it with everyone. The app can be found in xaviml/controllerx.

This automation brings the following functionalities to IKEA E1524/E1810:

  • Toggle light
  • Manual increase/decrease of brightness and color temperature
  • Smooth increase/decrease (holding button) of brightness and color temperature

This automation brings the following functionalities to IKEA E1743:

  • Turn on/Turn off light
  • Manual increase/decrease of brightness
  • Smooth increase/decrease (holding button) of brightness

The integration is now on HACS and ready to use.
Let me know what you guys think or if you have any suggestions.

EDIT:
This project has evolved and now it supports many more devices and can be integrated with zigbee2mqtt and deCONZ and ZHA. You can check the latest features in here

13 Likes

First of all, this is Awesome. I really appreciate you sharing your work. I think a lot of people are looking for this functionality and this provides it in an elegant way.

The only issue I can see is time.sleep().

Your comment is correct, if in control, time.sleep() is okay to use. The problem I see is that, when the action is “hold”, your while loop will run until it reaches min/max or until the action is “release”. But, the way AppDaemon works, because that callback (to state() from the listen_state() call) will never end until min/max is reached or self.on_hold becomes False, the thread is not available to receive the “release” action until the while loop ends. AppDaemon threads can only execute one method at a time, so the call to state() with an action of “release” won’t be received until the while loop ends.

At least, in theory, that’s how it works.

So, if you’re actually able to “release” the button before it reaches min/max and have it stop at that level, then you’ve found a bug/loophole that may not always function correctly because this isn’t how it’s supposed to work.

The only way to properly detect the “release” is to not use the while loop or the time.sleep(). But, as you indicated, the scheduler functions don’t accept sub-second times.

There are two options to fix this.

  1. Move to AD4.0 (in beta now). It supports sub-second intervals for run_in(). It also supports async apps (so you could use asyncio.sleep() with a float).

  2. Write your own version of run_in(). The AppDaemon code for run_in() looks like this:

    def run_in(self, callback, seconds, **kwargs):
        name = self.name
        self.AD.log(
            "DEBUG",
            "Registering run_in in {} seconds for {}".format(seconds, name)
        )
        # convert seconds to an int if possible since a common pattern is to
        # pass this through from the config file which is a string
        exec_time = self.get_now_ts() + int(seconds)
        handle = self.AD.insert_schedule(
            name, exec_time, callback, False, None, **kwargs
        )
        return handle

I’ve not tried this, but I THINK you could add this method to your App Class and call it repeatedly (instead of using a while loop and time.sleep()):

    def run_in_float(self, callback, seconds, **kwargs):
        name = self.name
        self.AD.log(
            "DEBUG",
            "Registering run_in in {} seconds for {}".format(seconds, name)
        )

        exec_time = self.get_now_ts() + float(seconds)
        handle = self.AD.insert_schedule(
            name, exec_time, callback, False, None, **kwargs
        )
        return handle

You’d have to test since there may be some error handling needed if your delay is so small that the scheduler doesn’t get to it until it has elapsed.

A third option, I just thought of.

Continue to use time.sleep() as you have. But, move the hold/release functionality into a separate callback that takes kwargs to inform it of what it’s doing.

So, at the end of state(), if you’ve determined you’re doing a hold/release action you’d call:

self.run_in(self.hold, 0, attribute=attribute, value=value, sign=sign)

The 0 tells it to run right away.

Then you’d have:

def hold(self, kwargs):
  if self.on_hold is False:
    return
  max_ = attribute_minmax[attribute]["max"]
  min_ = attribute_minmax[attribute]["min"]
  if min_ > kwargs['value'] or max_ < kwargs['value']:
    return
  new_value = self.turn_on_light(kwargs['attribute'], kwargs['value'], kwargs['sign'], self.automatic_steps)
  time.sleep(self.delay / 1000)
  self.run_in(self.hold, 0, attribute=kwargs['attribute'], value=new_value, sign=kwargs['sign'])

I’ve not tested this code, so there are probably bugs/typos. But, it should provide better detection of “release” in a timely manner while still giving you fairly accurate control over the delay.

This would allow each iteration of the delay to give control back to AppDaemon, allowing it to process the “release” action and this ending the loop.

Hi @swiftlyfalling,

Thank you for your feedback. My setup for AppDaemon was the default one (10 threads), so the light stop dimming if I release the button (as expected). Since the “release” action comes from another call, another thread is the one acting on the same object and makes the loop stop. However, I found a small bug and the program stays in a loop if you keep pressing the button until the light reach the limits. This is fixed now and I will push a hotfix for it.

I also found the following:

  • If I change AppDaemon to work with 1 or 2 thread and I hold the button and release it, it won’t stop dimming as you said
  • If I change AppDaemon to work with 3 or more threads, the app works as expected, so when released, it stops dimming.

I will check now on removing the time.sleep and keep the same functionality with the advices you gave me. I will keep you posted.

Ah yes! I forget that in AD3 “app pinning” (where AD makes the same app run in the same thread every time) isn’t a thing. So, because of that, your app will work with enough threads. The “run_in” idea I suggested should allow it to work regardless of AD version or the number of available threads.

The general rule is, don’t use time.sleep(). But, if you must use time.sleep(), don’t also do it in a while loop.

I look forward to seeing your improvements and I really appreciate you sharing code with the community.

1 Like

Awesome work, I’m working on something similiar for the Hue Dimmer Switch. One thing, if you are using DeCONZ to integrate your lights into Home Assistant, there is a nice method working with deconz service calls that allows smooth dimming when button is pressed. More info can be found here:

2 Likes

ok --sorry but iam 4 weeks old with HA - i just installed and dont understand
sensor-do i add the word sensor in front of my entity and end with action ?if not were can i find that value
& for light do i just put the light entity ??
thanks for you help !!!

nameOfYourInstanceApp:
module: z2m_ikea_controller
class: E1810Controller
sensor:
light:

Hey @goldbe4804 you can go to: Configuration>Integrations>MQTT>Your remote and then you will see a section “Entities” with the following:
example
The entity you are looking for is the one highlighted in the picture. It normally ends with “_action”. Here is my apps.yaml from AppDaemon:

livingroom_light:
  module: z2m_ikea_controller
  class: E1810Controller
  sensor: sensor.livingroom_controller_action
  light: light.livingroom
bedroomjx_light:
  module: z2m_ikea_controller
  class: E1810Controller
  sensor: sensor.bedroomjx_controller_action
  light: light.bedroomjx

This apps.yaml configuration controls my livingroom light and my bedroom light.
And yes, for the lights you just need the entity_id.

I hope this can help you.

yes thanks - thought it may work with out mqtt – so i will try to set mqtt

Do you have Zigbee2MQTT set up? The app doesn’t do any MQTT call, but t requires the integration of the devices through MQTT with Z2M.

just ordered the items for zigbee2mqtt - i will set that up first

1 Like

i’m getting this error when i add to HACS:

An error ocoured while prosessing
[‘Repostitory structure not compliant’]
Could not add this repository, make sure it is compliant with HACS.

Any way to resolve it?
thanks!

Hey @pitoganzado,

Could you please answer the following questions:

  1. HA version?
  2. HACS version?
  3. How did you install AppDaemon?
  4. Z2M - IKEA controllers version?

These are my answers:

  1. Home Assistant 0.101.3
  2. HACS 0.17.3
  3. Through the Hass.io addon
  4. v1.0.1
  1. 0.101.3
  2. 0.17.4 ( upgraded 5 minutes ago)
  3. Hass.io add on
  4. Not installed :expressionless:
    Maybe I’m installing in a wrong way…
    how do you installed? Thanks!

I just upgraded HACS. I uninstalled “Z2M - Ikea controllers” and then installed it back again through HACS. I didn’t get any error when installed. I go to HACS inside HA, then “AppDaemon apps” tab and find “Z2M - Ikea controllers”. Then I click on it and then “Install”. This is how I installed it.

Are you installing from the store or as a custom repository?

My bad :expressionless: missing “appdaemon: true” in configuration.yaml
hacs:
token: xxxxxxxxxxxxxxxxxxxxx
appdaemon: true
python_script: true
theme: true

now is installed :slight_smile:

but add in apps.yaml

ikea_light:
module: z2m_ikea_controller
class: E1810Controller
sensor: sensor.0x90fd9ffffe4c5723_action
light: light.0x000b57fffebd903d_light

but still not working
any tip?

The configuration looks correct to me. These are thing you could check to identify the problem:

  • The code under “ikea_light” is indented with 2 spaces.
  • You have AppDaemon addon up and running (check logs and make sure you see that AppDaemon initialized “ikea_light”. It should something like “Initializing app ikea_light class E1810Controller from module z2m_ikea_controller”).
  • You introduce the entity id of your controller and light with the proper name.

When you say “still not working” you mean anything works or toggle does work? Just as a reminder if you had any automation set up to toggle the light, you should remove it, this app does that for you already. Otherwise, you will see the light turning on and off instantly.

1 - copy/paste issue, the code in apps.yaml is:

ikea_light:
  module: z2m_ikea_controller
  class: E1810Controller
  sensor: sensor.0x90fd9ffffe4c5723_action
  light: light.0x000b57fffebd903d_light

2 - where i can find AppDaemon logs?
3 - did not understand what is wrong

You can go to Hass.io tab. Then click on the AppDaemon add-on that you installed already.
Check then that the addon is running and in the logs (down the bottom) you should see: Initializing app ikea_light class E1810Controller from module z2m_ikea_controller.

thanks for the support! is working now…!

1 Like