krahabb / meross_lan

Home Assistant integration for Meross devices
MIT License
415 stars 45 forks source link

Support light transition on LED strips #404

Closed lupusbytes closed 3 months ago

lupusbytes commented 6 months ago

I am an owner of two msl320cp LED strips. In the Meross app, when I change the brightness or color, the lights smoothly transition from one state to the other. Using meross_lan, it instantly switches from one state to the other. Automations cannot take advantage of the transitions, like:

  - service: light.turn_on
    data:
      entity_id: light.bedroom_1
      brightness: 75
      transition: 300

Looking around for clues in the code, I found lines like this in the emulator_traces. 2021/10/12 - 19:19:31 http GETACK Appliance.System.All {"all": {"system": {"hardware": {"type": "msl320cpr", "subType": "un", "version": "4.0.0", "chipType": "rtl8710cm", ... "light": {"capacity": 6, "channel": 0, "rgb": 255, "temperature": 46, "luminance": 100, "transform": 0}}}}

Is it possible that the transform property is the secret to smooth transitions?

The device is certainly capable of creating smooth transitions, with the Meross app. If you use the effects tab, and choose Party, it goes crazy with flashing all the colors of the rainbow, and you can even set the Flash speed and mode. This is another thing that is not supported by meross_lan, but not a big deal for me; You can see the list of effects in homeassistant, but not trigger them...

krahabb commented 6 months ago

Hello @lupusbytes, Yep, you're pointing right into one of the 'long standing misunderstands' in my knowledge of lights. Namely 'effects' and 'transitions'. I've seen too that "transform" key in some lights traces here and there but I hadn't any chance to understand its behavior so, this might be the time to dig into it. You might use the meross_lan.request service to manually craft and send your own commands in order to investigate this. In the end, you might even use this feature in your automations once you understand which payload to send but, nevertheless, we could insert support for transitions in core meross_lan too.

In order to use the service, beside the obvious fields (following the developer service panel UI) you'll have to setup a meaningful payload. To start off you might query the actual light state with:

service: meross_lan.request
data:
  protocol: auto
  method: GET
  namespace: Appliance.Control.Light
  payload: "{}"
  device_id: YOUR_DEVICE_UUID

then, in order to send a command just switch the method to SET and put a valid/experimental payload in it. In yaml the payload would need to be escaped (for the ") a bit so it might be easier to use the visual UI to fill that service field. The paylaod for a Light command is usually a kind of

{ "light": {
      "capacity": 6,
      "channel": 0,
      "rgb": 16739924,
      "temperature": 71,
      "luminance": 81
      }
}

Keep in mind, some lights automatically turn on when receiving any Light payload some other not (they need a Togglex command), some other need an "onoff" key (with value set to 0/1) in the payload. The "capacity" field sets the mode (rgb, temperature, effect) and is a bit field value - you'll be able to lookup the constants in code)

As for effects, that's a mess since meross_lan tries to implement sending the command for setting an effect but there were reports it wasn't working (#232) and I had never received any hint or help to try fix it. Basically, in my understanding, the selection of an effect is carried by sending the "effect" key in the payload together with an index into the list of effects available (which meross_lan queries and is able to show in the HA effects list) but this is failing. It maybe worked on legacy firmwares but at this point I'm not sure) The fact is I've also seen traces where the "effect" key was carrying a string instead of an integer index (that's why there's some euristics in code trying to overcome this when sending the command). There's also the fact that, when an effect is in place, the "capacity" key carries the bit-field value 8 (together with some other flags maybe) and so it might be needed to also set this when trying to setup an effect.

I hope you could experiment this a bit so that we can (finally) fix this long standing issue and maybe add support for transitions too ;)

lupusbytes commented 4 months ago

Dear @krahabb Sorry for the late reply. I have been toying a bit with the service: meross_lan.request like you suggested. My finding is that the transform property does nothing at all. But it is possible that I'm just using it in a wrong way... Perhaps it's used in effects?

After trying to call the API in many different ways I think that I can conclude that it doesn't support transitions in the traditional sense. The effects and "transition" that I am seeing in the app is simply a smooth brightness transition that takes around 1 second and cannot be configured. I uploaded a video of the Meross "Party" Effect for you to see: https://www.youtube.com/shorts/IZyXmvV1zys They seem to just crank the brightness up, then down, then change color and repeat. I can replicate this with behaviour with meross_lan.request

alias: Meross Test
sequence:
  - service: meross_lan.request
    data:
      method: SET
      namespace: Appliance.Control.Light
      payload: |-
        {
          "light": {
            "capacity": 4,
            "onoff": 1,
            "channel": 0,
            "luminance": 10
          }
        }
      host: 172.16.0.21
  - delay:
      hours: 0
      minutes: 0
      seconds: 3
      milliseconds: 0
  - service: meross_lan.request
    data:
      method: SET
      namespace: Appliance.Control.Light
      payload: |-
        {
          "light": {
            "capacity": 4,
            "onoff": 1,
            "channel": 0,
            "luminance": 100
          }
        }
      host: 172.16.0.21

This will smoothly turn down the brightness over 1 second, wait 3 seconds and then smoothly turn it up again. The script is very simple. I am wondering what meross_lan is doing different when it's changing the brightness that makes it instant change brightness. I have been looking into your source code for clues again for why this is, but I don't understand all the bitwise operations related to capacity.

I found another repo that was easier for me to read: https://github.com/bwp91/homebridge-meross/blob/latest/lib/device/light-rgb.js Here they (also?) keep track of the current light mode and send the correct capacity in payload when they change a value

CAPACITIES
1 - rgb to rgb
2 - cct to cct
4 - brightness
5 - cct to rgb
6 - rgb to cct

Could it be related to this?

While this feature may not enable us to use the normal HomeAssistant transition function, implementing the smooth change would still allow us to script much smoother (longer) transitions ourselves, so that not every brightness step is as instant and noticeable.

EDIT: The effects tab actually lets you define the flash speed on a slider, which can be turned very far down to make it extremely slow... :thinking:

lupusbytes commented 4 months ago

I have been trying for hours to use meross_lan.request to set the luminance in a loop.

The following script works on my meross LED strips, but the brightness steps are instant and disturbing and this is why I started researching this thing :smile:

alias: Dynamic Transition
action:
  - repeat:
      count: "{{ (end_brightness - start_brightness) / step }}"
      sequence:
        - service: light.turn_on
          target:
            entity_id: "{{ light_entities }}"
          data_template:
            brightness_pct: "{{ start_brightness + repeat.index * step }}"
        - delay:
            seconds: >-
              {{ transition_time / ((end_brightness - start_brightness) / step)
              }}
variables:
  start_brightness: 1
  end_brightness: 70
  step: 2
  transition_time: 60
  light_entities:
    - light.smart_light_2104062991838990848548e1e969c8ca

I tried so many variations, but I can't make this work with meross_lan.request I can't figure out how to use variables inside the json payload:

alias: Dynamic Transition Meross
action:
  - repeat:
      count: "{{ (end_brightness - start_brightness) / step }}"
      sequence:
        - service: meross_lan.request
          data:
            method: SET
            namespace: Appliance.Control.Light
            payload: |-
              {
                "light": {
                  "capacity": 4,
                  "onoff": 1,
                  "channel": 0,
                  "luminance": "{{ start_brightness + repeat.index * step }}"
                }
              }
            host: 172.16.0.21
        - delay:
            seconds: >-
              {{ transition_time / ((end_brightness - start_brightness) / step)
              }}
variables:
  start_brightness: 1
  end_brightness: 70
  step: 2
  transition_time: 60
krahabb commented 4 months ago

Hello @lupusbytes, Sorry for not being active tracking this but I was trying to finish off support for msl320 effects and I've finally released a preview

Meross lights are driving me mad (still) since, even if they (different models/fw) all expose the kind-of-same interface, their behavior is so erratic...

I've bought an msl320 Pro and, beside being finally able to implement effect selection I'm now more concerned than before (more on this later)

Back to your findings: In my knowledge, the capacity represents the 'working mode' of the light. It is composed of those bit-fields (1: rgb, 2: temperature, 4: luminance) you've found and it usually works like this:

I've tried issuing commands with capacity=1 to just control the rgb value but in the end it is like issuing a command with capacity=5 and replying the current luminance (which is the approach used in meross_lan - I always (re)send the current 'cached' luminance if it is not changed through an HA UI request

If I send capacity=7 (which should mean both rgb and cct active at the same time) the light just flashes-out something and then ignores the command actually rejecting this combo as a valid working mode.

I'll try experiment a bit more on these capacity mess to see if there are some combo/transitions available but I'm not so confident.

As for why your script using meross_lan.request doesn't work, at first sight I see those double-quotes (") around the template calculation for the "luminance" key value which might be producing a string instead of an int for the json value to send and this is likely rejected by the device. I'm not sure about the syntax for the script but you should ensure you're sending an int (correctly bounded though) for the "luminance"...again, not sure if this is the issue

krahabb commented 4 months ago

I think I've found while you can't invoke the service with the template...fact is, the payload is accepted as a string parameter in the service interface and then converted to json inside the service call... This leads to the template not being rendered by the HA template engine but just being passed in as a simple string

krahabb commented 4 months ago

I was able to finally make it work... The script I've used is:

alias: New Script (Duplicate)
sequence:
  - repeat:
      count: "{{ (end_brightness - start_brightness) / step }}"
      sequence:
        - service: meross_lan.request
          data:
            method: SET
            namespace: Appliance.Control.Light
            payload: 
              {
                "light": {
                  "capacity": 4,
                  "channel": 0,
                  "luminance": "{{ start_brightness + repeat.index * step }}"
                }
              }

            device_id: 2104293378215590850648e1e96ec013
        - delay:
            seconds: >-
              {{ transition_time / ((end_brightness - start_brightness) / step)
              }}
variables:
  start_brightness: 10
  end_brightness: 50
  step: 1
  transition_time: 5
mode: restart

but it currently doesn't work on the published release since, as I've stated before, the current/old implementation always tries to interpret the passed in payload parameter as a string since in my experiments/tests the UI always passed in a string (even if it contained a json structured value) Now I've fixed it to be prepared for 'real' dict parameters when the service invocation passes in data this way so I'm going to quickly push the fix (if you like you could directly pull the commit

lupusbytes commented 4 months ago

This sounds very exciting. Did you also notice a difference of how the light changes more smoothly, like I did, when changing luminance with a crafted meross_lan.request compared to "regular" methods?

I'm still kind of new to Home Assistant. I'm running it in a docker container and not sure how to use git to pull integration updates, instead of the normal release channels, but I am very excited to try the feature out!

krahabb commented 3 months ago

@lupusbytes

Did you also notice a difference of how the light changes more smoothly, like I did, when changing luminance with a crafted meross_lan.request compared to "regular" methods?

I cannot see any difference at the moment but maybe I didn't get your question right....

For the sake of 'completness', the new Moonlight.1 (5.1.0) carries a whole bunch of improvements/features for lights so you could test it a bit. Namely, beside other stuff, transitions are now 'natively' managed by leveraging your idea of sending multiple updates to carry on the transition. The 'only' drawback now, is that if meross_lan detects the device as being Meross cloud paired, it lowers the update rate in order to not cause high frequency spikes in cloud traffic (this is because the device, on its own, pushes light updates to the cloud whenever its light settings change).

You could bypass this 'enforced rate limitation' by using your idea of the script...keep in mind your 'high frequency requests', possibly caused by this approach, might raise an 'high traffic warning' at Meross Headquarters for your device(s) and you risk banning due to this.

lupusbytes commented 3 months ago

Thank you @krahabb, this is very cool. I've been using the 'native' transition for a while to sync my lights with sunset, and it's good! One time it got stuck at 1% brightness and never completed the transition. I don't know what that was about, but I have not been able to replicate this behavior again.

I cannot see any difference at the moment but maybe I didn't get your question right....

About the smooth light change, let me try explain it again... When I use the brightness slider in the UI, as seen on the picture, it instantly changes in the blink of an eye. smaller

I noticed that when I send a request with meross_lan to turn my light from like 10% to 100%, the brightness step is not instant. It gradually turns power up over 1 second until it's full bright. This looks pleasant to the eye.

I was hoping this smoothing behavior could be leveraged somehow in a transition, but upon further testing, it's only really noticeable when the brightness is turned up or down by 50% in a single step. So it's not useful at all when longer transitions are needed.