hencou / esphome_components

Collection of own ESPhome components
25 stars 15 forks source link

Feature Request: Implement transition argument #30

Closed nouser2013 closed 8 months ago

nouser2013 commented 8 months ago

Hi, truly loving the native ESPhome version for those who do not have an MQTT setup.

I do have a feature request though, not withstanding that I might be doing something wrong.

This is the (working) mi configuration on an ESP32:

mi:
  listen_repeats: 3
  id: mihub
  ce_pin: GPIO16
  csn_pin: GPIO5
  reset_pin: 0
  state_flush_interval: 5000
  packet_repeats: 50 #75
  packet_repeats_per_loop: 10 #75
  radio_interface_type: nrf24
  rf24_power_level: MAX
  rf24_listen_channel: LOW
  rf24_channels:
    - LOW
    - MID
    - HIGH
  packet_repeat_throttle_threshold: 2000
  packet_repeat_throttle_sensitivity: 0 #40
  packet_repeat_minimum: 2
  enable_automatic_mode_switching: false  

I'd like to have a custom effect on a specific device/group id which I call Hue-Swing.

While maintaining the currently set HSV saturation and value, the color of the light will then just

This is my light definition:

light:
  - platform: mi
    id: garden_test
    name: "Garden Test"
    device_id: 0xAAA1
    group_id: 1
    remote_type: rgb_cct
    default_transition_length: 5s
    effects:
      - lambda:
          name: Hue Swing (HS)
          // Make the transition time configurable by executing this request fast.
          update_interval: 0.1s
          lambda: |-
            static float curRed, curGreen, curBlue, curSat, curVal;
            static int curHue;
            static int state;
            static int counter;
            static BulbId bulbId = { 0xAAA1, 1, MiLightRemoteTypeHelpers::remoteTypeFromString("rgb_cct") };
            if (initial_run) {
              // Reset counters
              state = 0;
              // Get the current color as esphome::light::LightColorValues in the range [0.0, 1.0] ...
              auto currentValues = id(garden_test).current_values;
              curRed = currentValues.get_red();
              curGreen = currentValues.get_green();
              curBlue = currentValues.get_blue();
              // Transform the current color as HSV
              rgb_to_hsv(curRed, curGreen, curBlue, curHue, curSat, curVal);
              ESP_LOGI("effect-hs", "Starting Hue-Swing effect...");
              ESP_LOGI("effect-hs", "  * center: r=%1.4f g=%1.4f b=%1.4f | h=%03d s=%1.4f v=%1.4f", curRed, curGreen, curBlue, curHue, curSat, curVal);
            }
            // Do we need to do anything? Has the transition time from NUMBER expired and we need to set a new destination color?
            if (++counter < (int)(id(hs_tt).state * 10.0) && !initial_run) { return; }
            counter = 0;

            // Adapt transition length to slider value
            ESP_LOGD("effect-hs", "  * tl: %05d ms", (int)(id(hs_tt).state*1000.0));
            float r,g,b;
            char command[100];

            switch (state) {
              case 0:
                // Initial center value
                ESP_LOGD("effect-hs", "  * center: r=%1.4f g=%1.4f b=%1.4f | h=%03d s=%1.4f v=%1.4f", curRed, curGreen, curBlue, curHue, curSat, curVal);
                sprintf(command, "{\"color\":{\"r\":%d,\"g\":%d,\"b\":%d}, \"transition\":%d}", (int)(curRed*255), (int)(curGreen*255), (int)(curBlue*255), (int)(id(hs_tt).state));
                break;
              case 1:
                // Upper value
                hsv_to_rgb( (int)(curHue+id(hs_amount).state)%360, curSat, curVal, r, g, b);
                ESP_LOGD("effect-hs", "  * upper: r=%1.4f g=%1.4f b=%1.4f | h=%03d s=%1.4f v=%1.4f", r, g, b, (int)(curHue+id(hs_amount).state)%360, curSat, curVal);
                sprintf(command, "{\"color\":{\"r\":%d,\"g\":%d,\"b\":%d}, \"transition\":%d}", (int)(r*255), (int)(g*255), (int)(b*255), (int)(id(hs_tt).state));
                break;
              case 2:
                // Return to center value
                ESP_LOGD("effect-hs", "  * center: r=%1.4f g=%1.4f b=%1.4f | h=%03d s=%1.4f v=%1.4f", curRed, curGreen, curBlue, curHue, curSat, curVal);
                sprintf(command, "{\"color\":{\"r\":%d,\"g\":%d,\"b\":%d}, \"transition\":%d}", (int)(curRed*255), (int)(curGreen*255), (int)(curBlue*255), (int)(id(hs_tt).state));
                break;
              case 3:
                // Lower value
                hsv_to_rgb( (int)(curHue-id(hs_amount).state+360)%360, curSat, curVal, r, g, b);
                ESP_LOGD("effect-hs", "  * lower: r=%1.4f g=%1.4f b=%1.4f | h=%03d s=%1.4f v=%1.4f", r, g, b, (int)(curHue-id(hs_amount).state+360)%360, curSat, curVal);
                sprintf(command, "{\"color\":{\"r\":%d,\"g\":%d,\"b\":%d}, \"transition\":%d}", (int)(r*255), (int)(g*255), (int)(b*255), (int)(id(hs_tt).state));
                break;
            }
            id(mihub).write_state(bulbId, command);
            state = (state+1)%4;

Several Questions:

Does that make sense?

hencou commented 8 months ago

Hi,

In this component the original transition code from ESPMH is removed, to avoid a double transition configuration from ESPHome and the MiLight code.

That mean whn you call "id(mihub).write_state(bulbId, command);", there will be no transition, besides the transition built-in in the Milight bulbs itself, I expect that will be your 0.5s.

From my experiences in the past transitions with ESPHome were not very succesfull, I am not using it at all. You have to play with different setup parameters like repeats (what you did already) and find the most acceptable settings.

nouser2013 commented 8 months ago

Hi,

thanks for your response. I did change the logic a bit, and now repurposed the hs_tt: this number now designates the delay between hue updates to be sent to the light channel. As I want slow changes anyway, this is acceptable, and the only parameter to be transitioned is hue. I can therefore calculate the intermediate steps easily.

light:
  #########################################################
  # Garden Hue-Swing TEST
  #########################################################
  - platform: mi
    id: garden_test
    name: "Garden Test"
    device_id: 0xAAA1
    group_id: 1
    remote_type: rgb_cct
    default_transition_length: 0s
    effects:
      - lambda:
          name: Hue Swing (HS)
          update_interval: 0.1s
          lambda: |-
            // configure which channel to send on (Gateway ID and channel ID)
            static BulbId bulbId = { 0xAAA1, 1, MiLightRemoteTypeHelpers::remoteTypeFromString("rgb_cct") };

            // This is the color the light had when starting the effect.
            static float initSat, currentBrightness;
            static int initHue, currentHue; // Careful, currentHue remains SIGNED int, and is converted only before sending!
            static LightColorValues currentValues;

            // === Helper Static Vars
            static int counter; // Define a counter to check if this loop needs to do something...
            static int direction; // Define the direction to check if hue needs to count up or down

            // === START MAIN EFFECT PROGRAM
            currentValues = id(garden_test).current_values;

            // Test if this is an initial run...
            if (initial_run) {
              rgb_to_hsv(currentValues.get_red(), currentValues.get_green(), currentValues.get_blue(), initHue, initSat, currentBrightness);
              // Get the current color as esphome::light::LightColorValues
              currentHue = initHue;
              currentBrightness = currentValues.get_brightness();
              direction = 1;
              ESP_LOGI("effect-hs", "### Starting Hue-Swing effect with h=%d, s=%1.6f, v=%1.6f...", initHue, initSat, currentBrightness);
            }

            // Do we need to do anything?
            if (++counter < (int)(id(hs_tt).state * 10.0) && !initial_run) { return; }
            counter = 0;

            // === Yes.
            // Calculate the hue, then send, then increase / decrease hue basis. Keep it in the integer interval [0; 359].
            currentHue += direction;

            char command[200];
            // Now prepare the command to be sent...
            sprintf(command, "{\"hue\":%d,\"brightness\":%d}", (currentHue + 360) % 360, (int)(currentValues.get_brightness()*255));
            // Send the command...
            ESP_LOGD("effect-hs", "--- sending: h=%03d s=%1.4f v=%1.4f | hue=%d|%d saturation=%d level=%d", currentHue, initSat, currentBrightness, currentHue, (currentHue + 360) % 360, (int)(initSat*100), (int)(currentBrightness*100));
            id(mihub).write_state(bulbId, command);

            if (direction == 1 && currentHue >= (int)(initHue + id(hs_amount).state)) {
              ESP_LOGI("effect-hs", ">>> Reached upper color Hue boundary. Reversing direction to -1...");
              direction = -1;
            } else if (direction == -1 && currentHue <= (int)(initHue - id(hs_amount).state)) {
              ESP_LOGI("effect-hs", ">>> reached lower color Hue boundary. Reversing direction to +1...");
              direction = 1;
            }

It now does work sort of, but strangely enough, when triggering from Home Assistant, I seem to have two issues:

Number 1

// Config output ....
[20:42:01][C][api:142]:   Using noise encryption: YES

[20:42:03][D][light:036]: 'Garden Test' Setting:
[20:42:03][D][light:109]:   Effect: 'Hue Swing (HS)'
[20:42:03][I][effect-hs:150]: ### Starting Hue-Swing effect with h=10, s=1.000000, v=0.670588...
[20:42:03][D][effect-hs:166]: --- sending: h=011 s=1.0000 v=0.6706 | hue=11|11 saturation=100 level=67
[20:42:03][D][mi:446]: Send Milight request: {"hue":11,"brightness":171}
[20:42:03][D][mi:128]: Received Milight request: {"hue":11,"brightness":171}
[20:42:03][D][mi:446]: Send Milight request: {"effect":"Hue Swing (HS)"}
[20:42:03][D][mi:128]: Received Milight request: {"hue":11}
[20:42:03][D][mi:128]: Received Milight request: {"brightness":171}
[20:42:04][D][mi:128]: Received Milight request: {"mode":0} // <<<<<<<<<<< Where is this mode 0 coming from? It causes the first disco effect to be played, in my opinion unnecessarily, because I'm using my own effect?

Number 2 Changing the brightness in Home Assistant will again trigger the mode=0 output? This should also not happen?

[20:45:25][D][effect-hs:166]: --- sending: h=025 s=1.0000 v=0.6706 | hue=25|25 saturation=100 level=67
[20:45:25][D][mi:446]: Send Milight request: {"hue":25,"brightness":171}
[20:45:25][D][mi:128]: Received Milight request: {"hue":25,"brightness":171}
[20:45:25][D][mi:128]: Received Milight request: {"hue":25}
[20:45:25][D][mi:128]: Received Milight request: {"brightness":171}
[20:45:26][D][light:036]: 'Garden Test' Setting:
[20:45:26][D][light:051]:   Brightness: 21%
[20:45:26][D][mi:446]: Send Milight request: {"effect":"Hue Swing (HS)"} // <<<<<<<<<<<<< Where is this coming from? HA requested the brightness, not the effect, right?
[20:45:26][D][mi:128]: Received Milight request: {"mode":0} // <<<<<<<<<<<<< Where is this coming from?
[20:45:26][D][effect-hs:166]: --- sending: h=026 s=1.0000 v=0.6706 | hue=26|26 saturation=100 level=67
[20:45:26][D][mi:446]: Send Milight request: {"hue":26,"brightness":54}
[20:45:26][D][mi:128]: Received Milight request: {"hue":26,"brightness":54}
[20:45:26][D][mi:128]: Received Milight request: {"hue":25}
[20:45:26][D][mi:128]: Received Milight request: {"brightness":54}
[20:45:27][D][effect-hs:166]: --- sending: h=027 s=1.0000 v=0.6706 | hue=27|27 saturation=100 level=67

Am I still doing something wrong? Could this be a bug?

hencou commented 8 months ago

In such cases I would suggest installing a local esphome, vscode and platformio plugin for vscode on your PC. Then connect an esp module by a serial connection to the PC, and then you should be able to "local" build and debug by adding some "serial.println('message xyz')" commands on several places in the code, log variables on this way from the different places in the code, and see how the commands go through the code.

I expect "mode=0" will occur because the ESPhome component will send every x period the effect again. I think the reason its "mode=0" because your own effect is registered as the first effect, so the first effect will be counted from zero and thus be 0. But this are only assumptions from my side, somebody need to digg in the code, unfortunately ;-)

nouser2013 commented 8 months ago

Hi, ok, thanks again. I took some time to try and fix it, and while testing, found some more things I could fix. If you want, do have a look: https://github.com/nouser2013/esphome_components/tree/bugfix/milight-light-effects

At least of my FUTC04 and FUTC02 it seems to be working now. It would be nice to see the changes in the official component, then I don't need to keep a local copy and can use my docker based build system. If you want me to create a PR, let me know...

hencou commented 8 months ago

Hi, nice feature, descriptive names for the effects!

Will set "0xfffffff)" on the effect completely disable the repeats of the effect command, or will it only takes more time inbetween now?

nouser2013 commented 8 months ago

Will set "0xfffffff" on the effect completely disable the repeats of the effect command, or will it only takes more time in between now?

Well, technically, there does not seem to be a way to code a "one-shot-only" LambdaEffect. With the timeout set to 0xFFFFFFFF, a re-triggering of the same effect will occur after 2^32 milliseconds => 1193 hours => roughly 49 days. As good as it gets 🤷

PS: do note that -to my knowledge- the single lights will drift apart eventually, because their clocks are not synchronized and they also so not synchronize built-in effects by themselves.

hencou commented 8 months ago

Thanks for your PR, tested it here, seems working well. Of course needed to adjust the night_mode command to the new "described" string but after changing that, the scenes works as expected again.

nouser2013 commented 8 months ago

Any time, I tested the building on my ESPhome docker thingi with the URL pointing to this repo, and it seems to be working well. Closing as resolved...