diff --git a/esphome/esp-6buttontest-pmb.yaml b/esphome/esp-6buttontest-pmb.yaml index 06df71f..4a4c7d3 100644 --- a/esphome/esp-6buttontest-pmb.yaml +++ b/esphome/esp-6buttontest-pmb.yaml @@ -1,32 +1,51 @@ ########################################################################################## ########################################################################################## # PMB Electronics 6 Button ESP32 Switch +# +# V1.1 2025-08-06 Various function updates, Ext V calibrated (for my unit) # V1.0 2025-08-04 Initial Version ########################################################################################## # PMB Product Details Page https://rcbeacon.com/blog/?p=5488 # # DEVICE GPIO +# ------------------------------------------ +# GPIO36 ADC1 (knob) GPIO39 ADC2 (Supply V) +# GPIO19 Button1 GPIO21 LED1 +# GPIO35 Button2 GPIO25 LED2 +# GPIO05 Button3 GPIO18 LED3 +# GPIO33 Button4 GPIO26 LED4 +# GPIO16 Button5 GPIO17 LED5 +# GPIO12 Button6 GPIO13 LED6 (6 button/led may not be Populated) # -# OPERATION (as set up at V1.0) -# 1. There are 4 buttons that toggle a virtual relay and turn on the -# associated LED when pressed. Inputs are debounced. -# 2. Button 5 does the same, but also if double pressed, a different V relay switches on -# and the LED slowly pulses. If held down for 2 seconds, a 3rd V relay switches on and the -# LED fast pulses (the 3 relays are exclusive, ie interlocked) -# 3. The knob gives a reported value of 0-100% when turned (as well as the adc value). -# this is calibratable -# 4. The system supply voltage is reported (10-20V) anbd is also calibratable. +# OPERATION (as at V1.1) +# 1. Buttons 1-3: These toggle a virtual relay (v_relay1 to 3) and also turn on the +# associated LED when pressed. Inputs are debounced. +# 2. Button 4: As with buttons1-3,but also if double pressed, the v_relay only +# stays on for a short period (default 30s) then switches off. The LED fast flashes. +# If it is held down, it stays on for longer (1hr default) and the LED slow flashes. +# 3. Button 5: As with Buttons1-3 BUT if double pressed, a different v_relay switches on +# (5B) and the LED slowly pulses. If held down for 2 seconds, a 3rd v_relay switches +# on (5C) and the LED fast pulses. The 3 v_relays are exclusive, only one can be +# on at a time. +# 4. The knob gives a reported value of 0-100% when turned (as well as the adc value). +# this can be calibrated in the substitutions section +# 5. The system supply voltage is reported (~10-20V). +# 6. Power Loss: LEDs and v_relays are restored on power loss (restore settings saved +# every 10s). If v_relay4 was turned on with a timer (double press or hold), the +# state isn't restored on power loss. +# 7. There is an overall brigntness slider/variable. This allows the LEDs (except when +# flashing) to have a maximum brightness (eg for night mode) # # NOTES # 1. To Flash via ESPHome, you likely have to connect to the computer, start the flash -# then hold down I00 button. I think GPIO12 connection is preventing the flash process. +# then hold down I00 button. I think GPIO12 connection is preventing the flash process. # 2. OTA flash to an Existing Tasmota device is likely a bit hard with and ESP32 as they -# expect a signed Tasmota binary... and partition layout needs to be fixed anyway +# expect a signed Tasmota binary... and partition layout needs to be fixed anyway # 3. ESPHome warns on compiling: "legacy adc driver is deprecated, please migrate to use -# esp_adc/adc_oneshot.h and esp_adc/adc_continuous.h for oneshot mode and continuous mode -# drivers respectively" [-Wcpp]" (I'm not bothered with this) -# 4. Normally I'd include SNTP for timing functions, and uptime diagnostics etc. -# I have commented this out in the packages section as not sure it is really needed here +# esp_adc/adc_oneshot.h and esp_adc/adc_continuous.h for oneshot mode and continuous mode +# drivers respectively" [-Wcpp]" (I'm not bothered with this) +# 4. Normally I'd include SNTP for timing/scheduling functions, and uptime diagnostics etc. +# I have commented this out in the packages section as not sure it is really needed here. # ########################################################################################## ########################################################################################## @@ -196,39 +215,6 @@ safe_mode: network: enable_ipv6: ${ipv6_enable} -########################################################################################## -# SCRIPT -# Restart Networking every x hours + rand mins. Starts on reboot and always runs -# This ensure that the device is connected to the best AP, but no need for it -# if one AP and it is always reliable. -########################################################################################## -script: - - id: random_reconnect - mode: restart - then: - - lambda: |- - // Compute total delay: base hours + random offset minutes - uint32_t extra; - #if defined(ESP32) - // ESP32 (both Arduino & IDF builds) uses esp_random() - extra = esp_random() % (${random_offset_max_minutes} + 1); - #elif defined(ESP8266) - // ESP8266 Arduino core - extra = os_random() % (${random_offset_max_minutes} + 1); - #else - // Fallback to esp_random() on other platforms - extra = esp_random() % (${random_offset_max_minutes} + 1); - #endif - uint32_t total_s = ${base_interval_hours} * 3600 + extra * 60; - ESP_LOGI("random_reconnect", "Next reconnect in %u seconds", total_s); - // Delay inside lambda (blocks script execution but OK for reconnect timing) - delay(total_s * 1000); - - logger.log: "network_check: performing reconnect" - - wifi.disable: {} - - delay: 1s - - wifi.enable: {} - - script.execute: random_reconnect - ########################################################################################## # MQTT CLIENT COMPONENT # https://esphome.io/components/mqtt.html?highlight=mqtt @@ -275,6 +261,20 @@ esphome: friendly_name: ${friendly_name} comment: ${description_comment} # Appears on the ESPHome page in HA area: ${device_area} + on_boot: + then: + - lambda: |- + // if the user had manually left it ON, re-apply it now + if (id(vrelay4_manual_state)) { + id(vrelay_4).turn_on(); + } + // now restore all your _other_ relays as you already had them + if (id(vrelay_1_state)) id(vrelay_1).turn_on(); + if (id(vrelay_2_state)) id(vrelay_2).turn_on(); + if (id(vrelay_3_state)) id(vrelay_3).turn_on(); + if (id(vrelay_5_state)) id(vrelay_5).turn_on(); + if (id(vrelay_5b_state)) id(vrelay_5b).turn_on(); + if (id(vrelay_5c_state)) id(vrelay_5c).turn_on(); ########################################################################################## # ESP PLATFORM AND FRAMEWORK @@ -290,7 +290,7 @@ esp32: version: recommended # recommended, latest or dev preferences: - flash_write_interval: 5min # not too important but anything written for reboot mem will wear the flash + flash_write_interval: 10s # not too important but anything written for reboot mem will wear the flash ########################################################################################## # GLOBAL VARIABLES @@ -316,6 +316,14 @@ globals: type: bool restore_value: yes initial_value: 'false' + - id: vrelay4_manual_state + type: bool + restore_value: yes + initial_value: 'false' + - id: vrelay4_timer_state + type: bool + restore_value: no + initial_value: 'false' - id: vrelay_5_state type: bool restore_value: yes @@ -329,6 +337,11 @@ globals: restore_value: yes initial_value: 'false' + - id: max_led_brightness + type: float + restore_value: yes + initial_value: '1.0' # default to 100% on first run + ########################################################################################## # LOGGER COMPONENT # https://esphome.io/components/logger.html @@ -349,6 +362,84 @@ status_led: number: GPIO2 inverted: yes +########################################################################################## +# SCRIPT COMPONENT +# https://esphome.io/components/script.html +# Scripts can be executednearly anywhere in your device’s configuration with a single call. +########################################################################################## +script: + # Restart Networking every x hours + rand mins. Starts on reboot and always runs + # This ensure that the device is connected to the best AP, but no need for it + # if one AP and it is always reliable. + - id: random_reconnect + mode: restart + then: + - lambda: |- + // Compute total delay: base hours + random offset minutes + uint32_t extra; + #if defined(ESP32) + // ESP32 (both Arduino & IDF builds) uses esp_random() + extra = esp_random() % (${random_offset_max_minutes} + 1); + #elif defined(ESP8266) + // ESP8266 Arduino core + extra = os_random() % (${random_offset_max_minutes} + 1); + #else + // Fallback to esp_random() on other platforms + extra = esp_random() % (${random_offset_max_minutes} + 1); + #endif + uint32_t total_s = ${base_interval_hours} * 3600 + extra * 60; + ESP_LOGI("random_reconnect", "Next reconnect in %u seconds", total_s); + // Delay inside lambda (blocks script execution but OK for reconnect timing) + delay(total_s * 1000); + - logger.log: "network_check: performing reconnect" + - wifi.disable: {} + - delay: 1s + - wifi.enable: {} + - script.execute: random_reconnect + # Button 4: Double-press short timer (should NOT restore after power loss) + - id: button4_short + then: + - lambda: |- + // Timed mode (not manual) → will NOT be restored on reboot + id(vrelay4_manual_state) = false; + - switch.turn_off: vrelay_4 # keep the virtual relay OFF during timer + - lambda: |- + // Start 30 s fast-flash timer + id(vrelay4_timer_state) = true; + { + auto call = id(led4).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.set_effect("Flash Fast"); + call.perform(); + } + - delay: 30s + - lambda: |- + // Timer expired → clear + id(vrelay4_timer_state) = false; + - light.turn_off: led4 + + # Button 4: Hold long timer (should NOT restore after power loss) + - id: button4_long + then: + - lambda: |- + // Timed mode (not manual) → will NOT be restored on reboot + id(vrelay4_manual_state) = false; + - switch.turn_off: vrelay_4 # keep the virtual relay OFF during timer + - lambda: |- + // Start 60 min slow-flash timer + id(vrelay4_timer_state) = true; + { + auto call = id(led4).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.set_effect("Flash Slow"); + call.perform(); + } + - delay: 60min + - lambda: |- + // Timer expired → clear + id(vrelay4_timer_state) = false; + - light.turn_off: led4 + ########################################################################################## # BINARY SENSORS # https://esphome.io/components/binary_sensor/ @@ -370,6 +461,7 @@ binary_sensor: - delayed_off: 50ms on_press: - switch.toggle: vrelay_1 + - platform: gpio pin: number: GPIO35 @@ -381,6 +473,7 @@ binary_sensor: - delayed_off: 50ms on_press: - switch.toggle: vrelay_2 + - platform: gpio pin: number: GPIO05 @@ -392,6 +485,7 @@ binary_sensor: - delayed_off: 50ms on_press: - switch.toggle: vrelay_3 + - platform: gpio pin: number: GPIO33 @@ -401,33 +495,67 @@ binary_sensor: filters: - delayed_on: 50ms - delayed_off: 50ms - on_press: - - switch.toggle: vrelay_4 + on_multi_click: + # Single click: cancel timers, then toggle relay+LED + - timing: + - ON for 50ms to 400ms + - OFF for at least 250ms + then: + - script.stop: button4_short + - script.stop: button4_long + - switch.toggle: vrelay_4 + # Double click → short sequence + - timing: + - ON for 50ms to 400ms + - OFF for 50ms to 300ms + - ON for 50ms to 400ms + then: + - script.stop: button4_long + - script.execute: button4_short + # Hold → long sequence + - timing: + - ON for at least 2s + then: + - script.stop: button4_short + - script.execute: button4_long + - platform: gpio pin: number: GPIO16 mode: INPUT inverted: true name: "Button 5: ${switch_5_name}" + filters: + - delayed_on: 30ms + - delayed_off: 30ms on_multi_click: - # Double click + # Single click: if any of 5/5B/5C is on → turn them all off; else turn on 5 - timing: - - ON for 50ms to 500ms - - OFF for 50ms to 500ms - - ON for 50ms to 500ms + - ON for 50ms to 400ms + - OFF for at least 250ms + then: + - lambda: |- + if (id(vrelay_5_state) || id(vrelay_5b_state) || id(vrelay_5c_state)) { + id(vrelay_5).turn_off(); + id(vrelay_5b).turn_off(); + id(vrelay_5c).turn_off(); + } else { + id(vrelay_5).turn_on(); + } + + # Double click → vrelay_5b + - timing: + - ON for 50ms to 400ms + - OFF for 50ms to 300ms + - ON for 50ms to 400ms then: - switch.toggle: vrelay_5b - # Hold + + # Hold → vrelay_5c - timing: - ON for at least 2s then: - switch.toggle: vrelay_5c - # Single click - - timing: - - ON for 50ms to 500ms - - OFF for at least 300ms - then: - - switch.toggle: vrelay_5 # FUTURE: Board allows for 6 buttons #- platform: gpio @@ -481,17 +609,16 @@ output: # FUTURE: Board allows for 6 buttons #- platform: ledc # id: led6_output - # pin: GPIOXX + # pin: GPIO13 # frequency: ${pwm_freq} ########################################################################################## # LIGHT COMPONENT # https://esphome.io/components/light/index.html ########################################################################################## -# We are just going to use simple monochromatic component as only one colour to PWM control -# This doesn't actually do anything by itself, you need to tied it to an OUTPUT component. -########################################################################################## light: + # We are just going to use simple monochromatic component as only one colour to PWM control + # This doesn't actually do anything by itself, you need to tied it to an OUTPUT component. - platform: monochromatic id: led1 output: led1_output @@ -508,19 +635,40 @@ light: id: led4 output: led4_output name: "LED 4: ${switch_4_name}" + effects: + - strobe: + name: "Flash Slow" + colors: + - state: ON + duration: 1s + - state: OFF + duration: 0.5s + - strobe: + name: "Flash Fast" + colors: + - state: ON + duration: 500ms + - state: OFF + duration: 100ms - platform: monochromatic id: led5 output: led5_output name: "LED 5: ${switch_5_name}" effects: - - strobe: + # Pulse 0.5s: smooth ramp between 0% and current brightness + - pulse: name: "Pulse 0.5s" - colors: - - state: ON - brightness: 100% - duration: 500ms - - state: OFF - duration: 500ms + transition_length: + on_length: 0.5s + off_length: 0.5s + update_interval: 1s + # Pulse 0.1s: ramp up 0.5s / down 0.1s + - pulse: + name: "Pulse 0.1s" + transition_length: + on_length: 0.5s + off_length: 0.1s + update_interval: 600ms # FUTURE: Board allows for 6 buttons #- platform: monochromatic @@ -532,20 +680,24 @@ light: # SWITCH COMPONENT # https://esphome.io/components/switch/ ########################################################################################## -# Normally SWITCH would be for things like a relay output and turning that on and off. -# We are using it for virtual relays, that show up in Home Assistant etc, and toggling -# them along with the LEDs when the buttons are pressed. -# Separating them from the LEDs as outputs means we can do thinks like light blinking etc -# but still leave the switch outputs toggled on or off. -########################################################################################## switch: + # Normally SWITCH would be for things like a relay output and turning that on and off. + # We are using it for virtual relays, that show up in Home Assistant etc, and toggling + # them along with the LEDs when the buttons are pressed. + # Separating them from the LEDs as outputs means we can do thinks like light blinking etc + # but still leave the switch outputs toggled on or off. - platform: template name: "Output 1: ${switch_1_name}" id: vrelay_1 + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF lambda: |- return id(vrelay_1_state); turn_on_action: - - light.turn_on: led1 + - lambda: |- + auto call = id(led1).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.perform(); - lambda: |- id(vrelay_1_state) = true; turn_off_action: @@ -556,10 +708,15 @@ switch: - platform: template name: "Output 2: ${switch_2_name}" id: vrelay_2 + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF lambda: |- return id(vrelay_2_state); turn_on_action: - - light.turn_on: led2 + - lambda: |- + auto call = id(led2).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.perform(); - lambda: |- id(vrelay_2_state) = true; turn_off_action: @@ -570,10 +727,15 @@ switch: - platform: template name: "Output 3: ${switch_3_name}" id: vrelay_3 + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF lambda: |- return id(vrelay_3_state); turn_on_action: - - light.turn_on: led3 + - lambda: |- + auto call = id(led3).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.perform(); - lambda: |- id(vrelay_3_state) = true; turn_off_action: @@ -584,62 +746,98 @@ switch: - platform: template name: "Output 4: ${switch_4_name}" id: vrelay_4 + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF lambda: |- + // our single "source of truth" is vrelay_4_state return id(vrelay_4_state); + turn_on_action: - - light.turn_on: led4 - lambda: |- - id(vrelay_4_state) = true; + // a true manual toggle + id(vrelay4_manual_state) = true; + // clear any leftover timer (shouldn't happen) + id(vrelay4_timer_state) = false; + id(vrelay_4_state) = true; + { + auto call = id(led4).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.perform(); + } + turn_off_action: - - light.turn_off: led4 - lambda: |- - id(vrelay_4_state) = false; + // manual switch-off + id(vrelay4_manual_state) = false; + id(vrelay4_timer_state) = false; + id(vrelay_4_state) = false; + - light.turn_off: led4 - platform: template name: "Output 5: ${switch_5_name}" id: vrelay_5 + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF lambda: |- return id(vrelay_5_state); turn_on_action: - - light.turn_on: led5 + - switch.turn_off: vrelay_5b + - switch.turn_off: vrelay_5c + - lambda: |- + auto call = id(led5).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.perform(); - lambda: |- id(vrelay_5_state) = true; turn_off_action: - light.turn_off: led5 - lambda: |- id(vrelay_5_state) = false; - #- switch.turn_off: vrelay_5c - platform: template name: "Output 5B: ${switch_5b_name}" id: vrelay_5b + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF lambda: |- return id(vrelay_5b_state); turn_on_action: - - logger.log: "vrelay_5b ON" + - switch.turn_off: vrelay_5 + - switch.turn_off: vrelay_5c + - lambda: |- + auto call = id(led5).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.set_effect("Pulse 0.5s"); + call.perform(); - lambda: |- id(vrelay_5b_state) = true; turn_off_action: - - logger.log: "vrelay_5b OFF" + - light.turn_off: led5 - lambda: |- id(vrelay_5b_state) = false; - platform: template name: "Output 5C: ${switch_5c_name}" id: vrelay_5c + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF lambda: |- return id(vrelay_5c_state); turn_on_action: - - light.turn_on: - id: led5 - effect: "Pulse 0.5s" + - switch.turn_off: vrelay_5 + - switch.turn_off: vrelay_5b + - lambda: |- + auto call = id(led5).turn_on(); + call.set_brightness(id(max_led_brightness)); + call.set_effect("Pulse 0.1s"); + call.perform(); - lambda: |- id(vrelay_5c_state) = true; turn_off_action: - light.turn_off: led5 - lambda: |- id(vrelay_5c_state) = false; - #- switch.turn_off: vrelay_5 + # FUTURE: Board allows for 6 buttons #- platform: template @@ -695,17 +893,18 @@ sensor: pin: GPIO39 name: "External Supply Voltage" id: supply_voltage - attenuation: 11db # Up to ~3.9V at ADC pin + attenuation: 12db update_interval: 2s filters: - calibrate_linear: # https://esphome.io/components/sensor/#calibrate-linear # Format: raw_value -> real_voltage_at_pin - - 00.66 -> 10.00 # Example: ADC reads X when pin is -> actually Y - - 00.99 -> 15.00 - - 01.30 -> 20.00 + - 1.80 -> 20.00 # Example: ADC reads X when pin is -> actually Y + - 1.35 -> 15.00 + - 0.89 -> 10.00 #- multiply: 4.00 # Voltage divider ratio unit_of_measurement: "V" + ## DIAGNOSTIC ONLY SENSORS BELOW ## - platform: uptime # Uptime for this device in seconds name: "Uptime (s):" @@ -773,4 +972,50 @@ button: - platform: factory_reset name: "FACTORY RESET:" entity_category: "diagnostic" - disabled_by_default: true \ No newline at end of file + disabled_by_default: true + +########################################################################################## +# NUMBER COMPONENT +# https://esphome.io/components/number/ +########################################################################################## +number: + - platform: template + name: "LED Max Brightness" + id: led_max_brightness_number + optimistic: true + restore_value: true + initial_value: "1.0" # show 100% on first boot + min_value: 0.2 + max_value: 1.0 + step: 0.05 + unit_of_measurement: "%" + set_action: + - lambda: |- + // store the new max + id(max_led_brightness) = x; + // if any LED is currently on, re-apply at new brightness + if (id(vrelay_1_state)) { + auto call = id(led1).turn_on(); + call.set_brightness(x); + call.perform(); + } + if (id(vrelay_2_state)) { + auto call = id(led2).turn_on(); + call.set_brightness(x); + call.perform(); + } + if (id(vrelay_3_state)) { + auto call = id(led3).turn_on(); + call.set_brightness(x); + call.perform(); + } + if (id(vrelay_4_state)) { + auto call = id(led4).turn_on(); + call.set_brightness(x); + call.perform(); + } + if (id(vrelay_5_state)) { + auto call = id(led5).turn_on(); + call.set_brightness(x); + call.perform(); + } \ No newline at end of file