Leggin / dirigera

This repository provides an unofficial Python client for controlling the IKEA Dirigera Smart Home Hub.
MIT License
118 stars 22 forks source link

Switching a light on at a specified light level #80

Closed famichiki closed 1 month ago

famichiki commented 2 months ago

Is there a way to specify the light level that the light will switch on at?

For example, my light is set to 100% when it is switched off, I'd like to switch it back on again but at 50% brightness.

If I specify light_level before lamp_on it is ignored.

If I specify lamp_on then light_level, the light comes on at full brightness before quickly dimming to my specified level.

I know I could use a scene but I'd like to know if the above is possible.

Leggin commented 2 months ago

Hey, I think if setting light_level before lamp_on does not work, there is no way to do it. In the app it is also not possible to set the light level if the light is not on so it looks like it is not possible.

famichiki commented 2 months ago

Do the lights accept multiple attributes being sent at once, rather than separately setting lamp_on, then light_level, then color_temperature etc? I wonder how the scenes do it.

So something like: light.set_attributes(lamp_on=True, light_level=90)

Leggin commented 2 months ago

Uh that is a good point, that should probably work! I will test this in the upcoming days.

Leggin commented 2 months ago

Sadly, it does not seem to work. So what I tried is the following with the current version of the package (you can do this as well since the dirigera_hub exposes patch()):

  1. Set the attribute field with values of isOn and lightLevel:
l = dirigera_hub.get_light_by_name("lamp1")
data = [
    {
        "attributes": {
            "lightLevel": 100,
            "isOn": False,
        }
    },
]

dirigera_hub.patch(route=f"/devices/{l.id}", data=data)

With this only the first attribute is executed, in this case the lightLevel. The isOn has no effect whatsoever on the status of the lamp. It seems when changing the order always the first attribute is executed.

  1. I used two attribute objects and sent them:
    
    l = dirigera_hub.get_light_by_name("lamp1")
    data = [
    {
        "attributes": {
            "isOn": True,
        }
    },
    {
        "attributes": {
            "lightLevel": 10,
        }
    },
    ]

dirigera_hub.patch(route=f"/devices/{l.id}", data=data)


In this case, the behavior is very strange. 
 - When putting `lightLevel` first and `isOn` second it does not work
 - When putting `isOn` first and `lightLevel` second it seems to do the commands correctly, but only when switching to On. When switching `isOn: False` and setting `lightLevel` to I.e. 20 and then switching back on, the light level will not be 20.

@famichiki can you test test this an let me know if this helps? If it seems to behave deterministically I could add it to the library.
famichiki commented 2 months ago

I'm seeing the same behaviour as you described, so can only set the brightness after the light is on. However when using two attribute objects the transition is much faster as it doesn't do a fade.

But I've found that if you trigger a scene which has only lightLevel set without specifying isOn then the next time the bulb is switched on it will be at your desired brightness without switching if off if it's already on.

The following will create a hidden scene that specifies brightness only, triggers it, then immediately deletes it. If the light is on, the brightness will change, if it's off it will stay off.

from dirigera.devices.scene import Info, Icon, SceneType, Action, ActionAttributes

light = dirigera_hub.get_light_by_name("bathroom_light")

scene = dirigera_hub.create_scene(
    info=Info(name="temp_scene", icon=Icon.SCENES_BRIGHTNESS_UP),
    scene_type=SceneType.CUSTOM_SCENE,
    triggers=[],
    actions=[Action(id=light.id, type="device", enabled=True, attributes=ActionAttributes(lightLevel=30))],
)

scene.trigger()
dirigera_hub.delete_scene(scene.id)

I initially tried this with a USER_SCENE but the app caches programmatically deleted scenes weirdly, even though they are removed in the hub they reappear when the app loses connection with the hub (eg: wifi dropout) and clearing the app's cache doesn't help.

Perhaps the existing set_light_level method could be modified to check if isOn=False and if so then use technique?

My original goal with this was to set overnight hours where a light's default brightness was dimmer than during the daytime. For example, between midnight and 6am whenever I switched on my bathroom light it would switch on at 30% brightness.

Now that I know this is possible I've tried making a scheduled scene to achieve this, but had a few hiccups.

First of all, time: Optional[str] = None needs to be added to TriggerDetails, could you rectify that please?

class TriggerDetails(BaseIkeaModel):
    days: Optional[List[str]] = None
    time: Optional[str] = None
    controllerType: Optional[ControllerType] = None
    buttonIndex: Optional[int] = None
    clickPattern: Optional[ClickPattern] = None
    deviceId: Optional[str] = None
    offset: Optional[int] = None
    type: Optional[str] = None

Also could you please add support for endTriggerEvent and endTrigger to facilitate things like the following:

Ends at 6:00am 'endTriggerEvent': {'type': 'time', 'trigger': {'time': '06:00'}}

Ends after 6 hours. 'endTrigger': {'type': 'duration', 'trigger': {'duration': 21600}}, 'endTriggerEvent': {'type': 'duration', 'trigger': {'duration': 21600}}

So ideally I'd like to be able to create a scene as below that dims a switched off light to 30% between midnight and 6:00am, after which it will be restored to the previous brightness level.

from dirigera.devices.scene import Info, Icon, SceneType, Trigger, endTriggerEvent, TriggerDetails, Action, ActionAttributes

light = dirigera_hub.get_light_by_name("bathroom_light")

scene = dirigera_hub.create_scene(
    info=Info(name="bathroom_night_start", icon=Icon.SCENES_MOON),
    scene_type=SceneType.CUSTOM_SCENE,
    triggers=[
        Trigger(type="app", disabled=False),
        Trigger(type="time", disabled=False, 
            trigger=TriggerDetails(time='00:00'))],
        endTriggerEvent(type="time", disabled=False, 
            trigger=TriggerDetails(time='06:00'))],
    actions=[Action(id=light.id, type="device", enabled=True, attributes=ActionAttributes(lightLevel=30))],
)

It's important to create this as a CUSTOM_SCENE so it will be hidden. If you make a USER_SCENE, even if you simply go into the scene in the app and back out without editing any settings it will update the scene and add the isOn: False attribute.

Edit: I played around a lot and rewrote a lot, maybe it's helpful to someone.

Leggin commented 1 month ago

I will investigate your solution and make changes to the lib as soon as I find the time. If you want to contribute, you're very welcome!

famichiki commented 1 month ago

Thanks, and I'd like to contribute some additional info to the readme when I also have some spare time.

Leggin commented 1 month ago

@famichiki I created a PR for the scene changes, can you check if this is correct?