Bram
(Bram van den Hout)
February 15, 2021, 9:59pm
22
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!!
DeanoX
(Dean Smith)
February 16, 2021, 8:46am
23
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
aceindy
(Aceindy)
April 11, 2021, 5:04pm
25
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
igali
(Igali)
March 6, 2022, 7:51pm
26
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.
aceindy
(Aceindy)
March 7, 2022, 1:03pm
27
I assume this would be possible with restore_state
restore_state: on
And you’ll have to explain what you mean by ‘smooth switch’
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
igali
(Igali)
March 7, 2022, 4:48pm
28
Unfortunately I am a very beginner in the world of ESPHome and I have only minimal knowledge of English.
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!
aceindy
(Aceindy)
March 7, 2022, 6:45pm
29
Just a blind guess, commented out #on_press and changed brightness to fixed 1
aceindy:
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
restore_state: on
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
restore_state: on
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));
return 1;
}
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();
}
igali
(Igali)
March 7, 2022, 8:42pm
30
Unfortunately, I can’t try it now because I’m not home 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?
aceindy
(Aceindy)
March 8, 2022, 6:56am
31
Sorry, I don’t have the time to do that…maybe someone else has ideas ?
maxela
(Alexander Schliebner)
November 17, 2022, 8:11am
32
Thanks @aceindy !
I ported my MS-105B-220 2-Channel Dimmer from Tasmota to ESPHome with your implementation and it works like a charme
1 Like