QS-WiFi-D01-TRIAC Dimmer

The state testing, and the regulation by prolonged pressing works well, but the on-off by a short press, when pressing CH1 the 2 lights come on and when pressing again they turn off, the same happens when pressing CH2.

I have modified 2 things from the original code, the first one may cause the CH1 light to come on, but the second one is only for the log.

correc_2

correc_2-1

I’m checking the code to see if I find the problem

1 Like

This Tasmota work-around using MQTT instead of auto-detect (SetOption19=0) worked for me on the D02:

are these able to dim led lights? or will they flicker

Guess that depends on the load.

I use them with LED’s, no flickering, although one has startup problems when I set dimming below 10%

This is awesome, great job. I have been using the sonoff version for this but have all my switches on ESPHome. I had planned to convert but you saved me a lot of work. :smiley:

Works flawlessly, I only changed minimum bright to 0.067 as some of my led bulbs are already quite bright at 10%

Thanks again!

This is working perfectly with QS-WiFi-D02-TRIAC Dimmer too ! Big thanks.
I only edited the switch1 as non-interal (for future use)

This is an alternative way of doing it without attaching a fake output to a pin. In my case this caused some artifacts. No doubt there’s some better way to do Serial.write :thinking:

uart_output.h:

#include "esphome.h"
using namespace esphome;

class MyCustomFloatOutput : public Component, public FloatOutput {
 public:
  void setup() override {
    // This will be called by App.setup()
    
  }

  void write_state(float state) override {
    // state is the amount this output should be on, from 0.0 to 1.0
    // we need to convert it to an integer first
    float zero = 15.0;
    float range = 255.0 - zero;
    int value = (state==0.0) ?  0 :int((state * range) +zero);
    ESP_LOGD("Uart_output","Uart output setting %d", value);
    Serial.write(0xFF);
    Serial.write(0x55);
    Serial.write((char) int(value));
    Serial.write(0x05);
    Serial.write(0xDC);
    Serial.write(0x0A);

  }
};


yaml:

esphome:
  name: ${device_name}
  platform: ESP8266
  board: esp01_1m
  includes:
    - uart_output.h

Hola, Como hago para actualizar a tasmota scripting?

Hi.

Could you please post your full YAML? Whats should I change, beyond the esphome: section and the uart_output.h to remove the fake output?

Here you go, my device has been modified with a rotary encoder so that my girlfriend can work it:

substitutions:
  device_name: 'qs_d01_triac'
  resolution: '66'
  
esphome:
  name: ${device_name}
  platform: ESP8266
  board: esp01_1m
  includes:
    - uart_output.h

  on_boot:
    then:
      lambda: |-
              
              id(encoder1).set_value((int)(id(light1).remote_values.get_brightness()* id(resolution)));

globals:
   - id: resolution
     type: int
     restore_value: no
     initial_value: '${resolution}'
  
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

uart:
  tx_pin: GPIO1
  rx_pin: GPIO3
  stop_bits: 1
  baud_rate: 9600

# Enable logging
logger:
  level: WARN
  baud_rate: 0
#  logs:
#    sensor: WARN
#    duty_cycle: ERROR


# Enable Home Assistant API
api:

ota:
  password: !secret ota_password

time:
  - platform: homeassistant
    id: homeassistant_time

binary_sensor:
  - platform: gpio
    internal: True
    name: "Switch"
    pin: 
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: True
    on_press:
      then:
        - light.toggle: light1
   
output:
  - platform: custom
    type: float
    lambda: |-
      auto my_custom_float_output = new MyCustomFloatOutput();
      App.register_component(my_custom_float_output);
      return {my_custom_float_output};
  
    outputs:
      id: uart_float_output

light:
  - platform: monochromatic
    name: "${device_name} Nursery Light"
    id: light1
    output: uart_float_output
    default_transition_length: 0 ms
    
sensor:
  - platform: rotary_encoder
    internal: True
    name: "Rotary Encoder"
    id: encoder1
    pin_a: 
      number: GPIO4
      mode: INPUT_PULLUP
      inverted: True
    pin_b: 
      number: GPIO2
      mode: INPUT_PULLUP
      inverted: True
    min_value: 0
    max_value: ${resolution}

# Synchronize the rotary encoder state with the light

interval: 
  - interval: 300ms
    then:
      - lambda: |-
            static float prev_bright = 0.0;  // previously set brightness
            float light_bright= (float)id(light1).remote_values.get_brightness();;
            float enc_bright_low = min(1.0f,(float(id(encoder1).state / ${resolution})));
            float enc_bright_high = min(1.0f,float(1.0 + id(encoder1).state ) / ${resolution});
            float enc_bright_mean = (enc_bright_high+enc_bright_low)/2.0;


            if ((enc_bright_low >  prev_bright) || (enc_bright_high < prev_bright)) {
              ESP_LOGD("Poll loop","Prev: %3.1f\n", prev_bright);
              ESP_LOGD("Poll loop","Light: %3.1\n", light_bright);
              ESP_LOGD("Poll loop","Enc: %3.1\n", enc_bright_high);

              auto call =  id(light1).turn_on();
              if (id(light1).remote_values.get_state() != 0.0){ //Light is on
                  ESP_LOGD("Poll loop","Setting brightness %3.1f\n",enc_bright_mean);
                  call.set_brightness(enc_bright_mean);
                  call.perform();
              } else { // Light is off
                  ESP_LOGD("Poll loop","Set brightness (off) %3.1f\n",enc_bright_high);
                  id(light1).remote_values.set_brightness(enc_bright_mean);
              } 
              prev_bright = enc_bright_mean;

            }else if (light_bright != prev_bright) {
              id(encoder1).set_value((int)(id(light1).remote_values.get_brightness()* id(resolution)));
              prev_bright = light_bright;
            }

1 Like

Hi,
I’ve been enjoying the QS-WIFI-D01-TRIAC dimmer with your ESPhome YAML for quite some while now. I’m looking to tweak it a little bit, but unfortunately after many attempts I now resort to your original post.
I’m looking to add a light.turn_off command with a nice fade out instead of immediate turn off.
Somehow the above command doesn’t affect the module.
Probably has to do something with the fact that this dimmer module needs direct writes to some other module (MCU via UART?).

Is this possible, and if so could you give me a hint how to do so?

Thanks!!

If the fade off is the only time you want the effect, should be possible to add an additional template switch. Use the timer loop to detect when its turned off - just as if the button were held. Some extra logic required to turn fully off at min brightness and Ensure it’s turned on whenever the main light object is on.

Perhaps more elegant and would allow a generic “fade to any level” would be to create a second light that you actually expose to HA to set a target brightness…then use the timer to track the difference between the actual light and the target brightness and adjust accordingly with a maximum step each cycle to create the ramp.

I have found that different bulbs/lights need different blends of the timing frequency and brightness step to get a smooth dim. So would expect to have to tweak the fine details.

The 2nd approach sounds like an interesting enhancement so may that give a go myself. DM if you’d be interested in testing.

1 Like

Seems to work fine on QS-Wifi-D02-2C nowadays…
Finally found time and replaced my Tasmota script for this one, which works flawless for me:

esphome:
  name: qs-wifi-ds02-2c
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: "MyWiFi_SSID"
  password: "MyWiFi_pw"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Qs-Wifi-Ds02-2C"
    password: "MyFallBack_pw"

captive_portal:

# Enable Home Assistant API
api:
  password: "my-ha-api-password"

ota:

# Example configuration entry (optional web server)
web_server:
  port: 80

# Make sure logging is not using the serial port
logger:
  baud_rate: 0
  logs:
    sensor: ERROR
    duty_cycle: ERROR
    binary_sensor: ERROR
    light: ERROR

# level: VERBOSE
time:
  - platform: homeassistant

substitutions:
  switch_id: "dim_2ch_01"

# globals:
# Dummy light brightness tracker Global

globals:
  # Dim direction for Switch 1: 0=Up (brighten) 1=down (dim)
  - id: g_direction_1
    type: int
    restore_value: no
    initial_value: "1"
  # Counter for time pressed for switch 1
  - id: g_counter_1
    type: int
    restore_value: no
    initial_value: "0"
  # initial brightness
  # Dim direction for Switch 2: 0=Up (brighten) 1=down (dim)
  - id: g_direction_2
    type: int
    restore_value: no
    initial_value: "1"
  # Counter for time pressed for switch 2
  - id: g_counter_2
    type: int
    restore_value: no
    initial_value: "0"
  # initial brightness
  
# Uart definition to talk to MCU dimmer
uart:
  tx_pin: GPIO1
  rx_pin: GPIO3
  stop_bits: 1
  baud_rate: 9600

sensor:
  - platform: wifi_signal
    name: "${switch_id} WiFi Signal Sensor"
    update_interval: 60s
  # Primary template sensor to track Brightness of light object for "on_value" sending to MCU dimmer
  # CH1
  - platform: template
    name: "${switch_id} Brightness Sensor CH1"
    id: sensor_g_bright_1
    internal: true
    update_interval: 20ms
    # Ensure on_value only triggered when brightness (0-255) changes
    filters:
      delta: 0.8
    # Read brightness (0 - 1) from light , convert to (0-255) for MCU
    lambda: |-
      if (id(light_main_1).remote_values.is_on()) {
        return (int(id(light_main_1).remote_values.get_brightness() * 255));
      }
      else {
        return 0;
      }
    # On Change send to MCU via UART
    on_value:
      then:
        - uart.write: !lambda |-
            return {0xFF, 0x55, 0x01, (char) id(sensor_g_bright_1).state, 0x00, 0x00, 0x00, 0x0A};
        - logger.log:
            level: INFO
            format: "CH1 Sensor Value Change sent to UART %3.1f"
            args: ["id(sensor_g_bright_1).state"]
  # Sensor to detect button push (via duty_cycle of 50hz mains signal)
  - platform: template
    name: "${switch_id} Brightness Sensor CH2"
    id: sensor_g_bright_2
    internal: true
    update_interval: 20ms
    # Ensure on_value only triggered when brightness (0-255) changes
    filters:
      delta: 0.8
    # Read brightness (0 - 1) from light , convert to (0-255) for MCU
    lambda: |-
      if (id(light_main_2).remote_values.is_on()) {
        return (int(id(light_main_2).remote_values.get_brightness() * 255));
      }
      else {
        return 0;
      }
    # On Change send to MCU via UART
    on_value:
      then:
        - uart.write: !lambda |-
            return {0xFF, 0x55, 0x02, 0x00, (char) id(sensor_g_bright_2).state, 0x00, 0x00, 0x0A};
        - logger.log:
            level: INFO
            format: "CH2 Sensor Value Change sent to UART %3.1f"
            args: ["id(sensor_g_bright_2).state"]
  # Sensor to detect button push (via duty_cycle of 50hz mains signal)
  - platform: duty_cycle
    pin: GPIO13
    internal: true
    id: sensor_push_switch_1
    name: "${switch_id} Sensor Push Switch 1"
    update_interval: 20ms
  - platform: duty_cycle
    pin: GPIO5
    internal: true
    id: sensor_push_switch_2
    name: "${switch_id} Sensor Push Switch 2"
    update_interval: 20ms

binary_sensor:
  #Binary sensor (on/off) which reads duty_cyle sensor readings. CH1
  - platform: template
    id: switch1
    internal: true
    name: "${switch_id} Switch Binary Sensor 1"
    # read duty_cycle, convert to on/off
    lambda: |-
      if (id(sensor_push_switch_1).state < 95.0) {
        return true;
      } else {
        return false;
      }
    # Short Click - toggle light only
    on_click:
      max_length: 300ms
      then:
        light.toggle: light_main_1
    # Generic On_Press - log press, toggle DIM Direction and reset press interval counter
    on_press:
      then:
        - logger.log: "Switch 1 Press"
        - lambda: |-
            if (id(g_direction_1) == 0) {
              id(g_direction_1) = 1;
            } else {
              id(g_direction_1) = 0;
            }
            id(g_counter_1) = 0;
  #Binary sensor (on/off) which reads duty_cyle sensor readings. CH2
  - platform: template
    id: switch2
    internal: true
    name: "${switch_id} Switch Binary Sensor 2"
    # read duty_cycle, convert to on/off
    lambda: |-
      if (id(sensor_push_switch_2).state < 95.0) {
        return true;
      } else {
        return false;
      }
    # Short Click - toggle light only
    on_click:
      max_length: 300ms
      then:
        light.toggle: light_main_2
    # Generic On_Press - log press, toggle DIM Direction and reset press interval counter
    on_press:
      then:
        - logger.log: "Switch 2 Press"
        - lambda: |-
            if (id(g_direction_2) == 0) {
              id(g_direction_2) = 1;
            } else {
              id(g_direction_2) = 0;
            }
            id(g_counter_2) = 0;

# Dummy light output to allow creation of light object
output:
  - platform: esp8266_pwm
    pin: GPIO14
    frequency: 800 Hz
    id: dummy_pwm1
  - platform: esp8266_pwm
    pin: GPIO16
    frequency: 800 Hz
    id: dummy_pwm2

# Primary Light object exposed to HA
light:
  - platform: monochromatic
    default_transition_length: 20ms
    name: "${switch_id} Light 1"
    output: dummy_pwm1
    id: light_main_1
  - platform: monochromatic
    default_transition_length: 20ms
    name: "${switch_id} Light 2"
    output: dummy_pwm2
    id: light_main_2

switch:
  - platform: restart
    name: "${switch_id} Restart"

# Polling object for long press handling of switch for dim/brighten cycle
interval:
  - interval: 20ms
    then:
      - if:
          condition:
            binary_sensor.is_on: switch1
          then:
            # Ramp rate for dim is product of interval (20ms) * number of intervals
            # Every 20ms Dimmer is increased/decreased by 2/255
            # Lower limit = 10%
            # Upper limit = 100%
            # 100% - 10% = 90% = 230/255. Therefore 230/2 * 20ms = 2.3 seconds for full range
            # At full/min brightness - further 16x20ms = 0.32 Seconds "dwell" by resetting counter to 0
            # Initial pause for 16x20ms = 0.32s to allow "on_click" to be discounted 1st
            # g_direction_1 = 0 (Increasing brightness)
            # g_direction_1 = 1 (decreasing brightness)
            # g_counter_1 = Interval pulse counter

            lambda: |-
              float curr_bright = id(light_main_1).remote_values.get_brightness();
              id(g_counter_1) += 1; 

              // If max bright, change direction
              if (curr_bright >= 0.999 && id(g_direction_1) == 0) {
                id(g_direction_1) = 1;
                id(g_counter_1) = 0;
              }

              // If below min_bright, change direction
              if (curr_bright < 0.1 && id(g_direction_1) == 1) {
                id(g_direction_1) = 0;
                id(g_counter_1) = 0;
              }

              if (id(g_direction_1) == 0 && id(g_counter_1) > 15) {
                // Increase Bright
                auto call = id(light_main_1).turn_on();
                call.set_brightness(curr_bright + (2.0/255.0));
                call.perform();
              }

              else if(id(g_direction_1) == 1 && id(g_counter_1) > 15) {
                // Decrease Bright
                auto call = id(light_main_1).turn_on();
                call.set_brightness(curr_bright - (2.0/255.0));
                call.perform();
              }
      - if:
          condition:
            binary_sensor.is_on: switch2
          then:
            # Ramp rate for dim is product of interval (20ms) * number of intervals
            # Every 20ms Dimmer is increased/decreased by 2/255
            # Lower limit = 10%
            # Upper limit = 100%
            # 100% - 10% = 90% = 230/255. Therefore 230/2 * 20ms = 2.3 seconds for full range
            # At full/min brightness - further 16x20ms = 0.32 Seconds "dwell" by resetting counter to 0
            # Initial pause for 16x20ms = 0.32s to allow "on_click" to be discounted 1st
            # g_direction_1 = 0 (Increasing brightness)
            # g_direction_1 = 1 (decreasing brightness)
            # g_counter_1 = Interval pulse counter

            lambda: |-
              float curr_bright = id(light_main_2).remote_values.get_brightness();
              id(g_counter_2) += 1; 

              // If max bright, change direction
              if (curr_bright >= 0.999 && id(g_direction_2) == 0) {
                id(g_direction_2) = 1;
                id(g_counter_2) = 0;
              }

              // If below min_bright, change direction
              if (curr_bright < 0.1 && id(g_direction_2) == 1) {
                id(g_direction_2) = 0;
                id(g_counter_2) = 0;
              }

              if (id(g_direction_2) == 0 && id(g_counter_2) > 15) {
                // Increase Bright
                auto call = id(light_main_2).turn_on();
                call.set_brightness(curr_bright + (2.0/255.0));
                call.perform();
              }

              else if(id(g_direction_2) == 1 && id(g_counter_2) > 15) {
                // Decrease Bright
                auto call = id(light_main_2).turn_on();
                call.set_brightness(curr_bright - (2.0/255.0));
                call.perform();
              }        

Thanks!

Edit also seems to work on MS-105B-220 2-Channel Dimmer

1 Like

Dear aceindy,

Thanks for the information!

I found an error.
I want to set it to switch to the last state after a power failure, when there is power again. Or return to the off state by default.
Unfortunately, it now works by always turning on, that is, turning on CH1 and CH2.

How can it be set that, for example, CH1 can be dimmable, but CH2 acts as a smooth switch?

Please also share the code line described above.

I assume this would be possible with restore_state

restore_state: on

And you’ll have to explain what you mean by ‘smooth switch’ :woozy_face:

What exactly do you want me to share ??

PS: I’m not an ESPHome expert, I just managed to combine the yaml from here and there and made it work on 2 channels

Unfortunately I am a very beginner in the world of ESPHome and I have only minimal knowledge of English. :pensive:

Where should I enter the following?
restore_state: on

smooth switch = all you need to know is to turn it on and off. No dimmability.

Please share the full modified ESPhome code line with me!
Gang1: physical switch = pushbutton. If I press it briefly, turn on the dimmable bulb. If I press and hold for a long time, adjust the brightness. If I press it briefly, turn off the dimmable bulb.

Gang2: physical switch = pushbutton. If I press it briefly, turn on the bulb (no dimmable, simple bulb). If I press / hold for a long time, do nothing. If I press it briefly, turn off the bulb (no dimmable, simple bulb).

Thank you very much in advance!

Just a blind guess, commented out #on_press and changed brightness to fixed 1

Unfortunately, I can’t try it now because I’m not home :pensive: but it would be great to know if your suggestion works.

Another:
‘Restore_state: on’ in line ‘sensor’ is incorrect, indicates an error.

Can you check, can you try?

Sorry, I don’t have the time to do that…maybe someone else has ideas ?

Thanks @aceindy !
I ported my MS-105B-220 2-Channel Dimmer from Tasmota to ESPHome with your implementation and it works like a charme :smiley:

1 Like