# https://esphome.io/guides/configuration-types.html#substitutions substitutions: # substitutions can be changed here if you are using this file directly in the ESPHome dashboard. The better approach is # to incorporate this file as a package using the following packages: configuration, and then overwrite these substitutions # in your local yaml file by redefining them. # # packages: # kauf.plf10: github://KaufHA/PLF10/kauf-plug.yaml # name: kauf-plug # **** CHANGE DEVICE NAME TO SOMETHING UNIQUE PER DEVICE. RENAME YAML FILE TO SAME NAME. **** # **** USE DASHES (-) INSTEAD OF SPACES OR UNDERSCORE (_). USE ONLY LOWER CASE LETTERS. **** friendly_name: Kauf Plug # **** CHANGE FRIENDLY NAME TO SOMETHING UNIQUE PER DEVICE **** # https://esphome.io/components/esphome.html#esphome-creators-project project_name: Kauf.PLF10 project_ver_num: "2.07" project_ver_let: y # https://esphome.io/components/switch/gpio.html?highlight=restore_mode sub_restore_mode: RESTORE_DEFAULT_ON # overwrite to change boot up behavior of relay disable_entities: "true" # set to "false" to have all entities show up in Home Assistant automatically disable_webserver: "false" # set to "true" to disable the webserver listening on port 80. # substitutions for button actions. on_press and on_release implement a timer scheme with configurable delay. # on_hold_30s re-enables the AP and captive portal for the precompiled update binary. # any length of hold can be implemented with just on_press and on_release using the following directions. This # is basically how this yaml file works. # * have a delay for the desired hold in the on_press script # * stop the on_press script in the on_release script # * place actions in the on_press script after the delay to perform them while still holding the button # * place actions in the on_release script, with the condition that the on_press script is not running, to # perform them on release of the button. sub_on_press: script_do_nothing # executes right when button is initially pressed sub_on_release: script_do_nothing # executes right when button is released sub_on_hold_30s: script_force_ap # executes right when button has been held for 5s, while button is still being held sub_on_turn_on: script_do_nothing sub_on_turn_off: script_do_nothing # an extra script that, if running, will stop the relay from toggling on button release. # used to not toggle when WiFi AP is re-enabled on update bin file. sub_toggle_check: script_force_ap # made this a substitution so that the update bin file and yaml compiled versions can have different defaults default_button_config: "Toggle on Press" # substitutions for power monitoring calibration. Allows end users to change calibration in their yaml and still # incorporate this file as a package to get all the updates we release. current_resistor_val: "0.001" voltage_divider_val: "2401" sub_hlw_model: HLW8012 power_cal_val1_in: "0.0" power_cal_val1_out: "0.0" power_cal_val2_in: "333.8" power_cal_val2_out: "60" current_cal_val1_in: "0.0" current_cal_val1_out: "0.0" current_cal_val2_in: "0.6" current_cal_val2_out: "0.515" voltage_cal_val1_in: "0.0" voltage_cal_val1_out: "0.0" voltage_cal_val2_in: "302.1" voltage_cal_val2_out: "117.1" sub_default_scale_power: "100" sub_default_scale_current: "100" sub_default_scale_voltage: "100" # set default power monitoring update interval in yaml. # this will only be effective as long as the select entity is set to "YAML Configured" sub_update_interval: 10s sub_pm_initial_option: YAML Configured ($sub_update_interval) sub_hlw_timeout: 3s # for early sensor publishing sub_early_publish_percent: "25" sub_early_publish_percent_min_power: "0.5" sub_early_publish_absolute: "16.68" # for PLF10, 1w is approximately 5.56. 16.68 is approximately 3w. # GPIO definitions sub_pm_sel_pin: GPIO12 sub_pm_cf_pin: GPIO5 sub_pm_cf1_pin: GPIO14 sub_button_pin: GPIO13 sub_blue_led_pin: GPIO2 sub_red_led_pin: GPIO0 sub_relay_pin: GPIO4 # debounce specifications sub_default_debounce: "100" sub_min_debounce: "50" # https://esphome.io/components/esp8266.html esp8266: board: esp01_1m restore_from_flash: true early_pin_init: false start_free: 76 global_addr: global_forced_addr # https://esphome.io/guides/automations.html#global-variables globals: - id: global_forced_addr type: int restore_value: no initial_value: '12345' - id: global_press_time type: int restore_value: no initial_value: '0' - id: global_has_debounce type: boolean restore_value: no initial_value: "false" # https://esphome.io/components/esphome.html#adjusting-flash-writes preferences: # setting interval to 5 minutes since this defines writing total daily energy to flash, which will occur # every interval length 24/7/365. Created a specific save script that is executed for things that need # to save more quickly. flash_write_interval: 5min # https://esphome.io/components/esphome.html esphome: name: $name project: name: $project_name version: $project_ver_num($project_ver_let) on_boot: then: # implementing on_boot automation as a script makes it run in parallel # with any other on_boot scripts. - script.execute: script_set_power_leds - script.execute: on_wifi_connect min_version: 2024.6.0 # https://esphome.io/components/external_components.html external_components: - source: type: git url: https://github.com/KaufHA/common ref: v2024.08.22 refresh: never # https://esphome.io/components/wifi.html wifi: # **** ENTER WI-FI CREDENTIALS HERE, USING SECRETS.YAML RECOMMENDED **** ssid: initial_ap # !secret wifi_ssid password: asdfasdfasdfasdf # !secret wifi_password # enable wifi ap with ridiculous timeout so it does not normally turn on. ap: ap_timeout: 2147483647ms # maximum 32 bit value. About 3.5 weeks in milliseconds. forced_addr: 8 global_addr: global_forced_addr # https://esphome.io/components/captive_portal.html captive_portal: # https://esphome.io/components/logger.html logger: # https://esphome.io/components/api.html api: id: kauf_api # https://esphome.io/components/ota.html ota: platform: esphome on_error: - button.press: restart_button safe_mode: # https://esphome.io/components/web_server.html web_server: disable: $disable_webserver js_url: https://kaufha.com/v2/www.js # https://esphome.io/components/binary_sensor/index.html binary_sensor: # button input toggles relay and thereby power led # https://esphome.io/components/binary_sensor/gpio.html - platform: gpio id: button_in name: $friendly_name Button pin: number: $sub_button_pin mode: input: true pullup: true inverted: true on_press: - lambda: |- // store time of press and clear duration sensor id(global_press_time) = millis(); id(sensor_press_duration).publish_state(0); // toggle if configured for toggle on press if (id(select_button).state == "Toggle on Press") { id(relay).toggle(); } - script.execute: $sub_on_press - script.execute: script_30s_timer on_release: - lambda: |- // set duration sensor id(sensor_press_duration).publish_state(millis() - id(global_press_time) + id(number_debounce).state); // toggle if configured on release and toggle check script is not running. if ( (id(select_button).state == "Toggle on Release") && !id($sub_toggle_check).is_running() ) { id(relay).toggle(); } - script.execute: $sub_on_release - script.stop: script_30s_timer filters: - delayed_on: !lambda return id(number_debounce).state; # indicates whether plugged-in device is running based on configurable threshold. # https://esphome.io/components/binary_sensor/template.html - platform: template id: in_use name: $friendly_name Device In Use # https://esphome.io/guides/automations.html#script-component script: # sets LEDs to proper state based on LED configuration and relay state - id: script_set_power_leds then: - lambda: |- // if blue led follows power status if ( (id(select_bled).state == "Power Status") || (id(select_bled).state == "Error and Power") ) { id(blue_led).publish_state(id(relay).state); } // if blue led follows inverse of power status else if ( (id(select_bled).state == "Invert Power Status") || (id(select_bled).state == "Error and Invert Power") ) { id(blue_led).publish_state(!id(relay).state); } // if red led follows power status if ( (id(select_rled).state == "Power Status") || (id(select_rled).state == "Error and Power") ) { id(red_led).publish_state(id(relay).state); } // if red led follows inverse of power status else if ( (id(select_rled).state == "Invert Power Status") || (id(select_rled).state == "Error and Invert Power") ) { id(red_led).publish_state(!id(relay).state); } - script.execute: script_save_changes - id: blink_status_led mode: queued then: - lambda: |- // turn on blue LED if configured for error status if ( (id(select_bled).state == "Error Status") || (id(select_bled).state == "Error and Power") || (id(select_bled).state == "Error and Invert Power") ) { id(blue_led).turn_on(); } // turn on red LED if configured for error status if ( (id(select_rled).state == "Error Status") || (id(select_rled).state == "Error and Power") || (id(select_rled).state == "Error and Invert Power") ) { id(red_led).turn_on(); } - delay: 350ms - lambda: |- // turn off blue LED if configured for error status if ( (id(select_bled).state == "Error Status") || (id(select_bled).state == "Error and Power") || (id(select_bled).state == "Error and Invert Power") ) { id(blue_led).turn_off(); } // turn off red LED if configured for error status if ( (id(select_rled).state == "Error Status") || (id(select_rled).state == "Error and Power") || (id(select_rled).state == "Error and Invert Power") ) { id(red_led).turn_off(); } - delay: 1150ms - if: condition: - lambda: return ( ( (App.get_app_state() & STATUS_LED_ERROR) != 0u) || ((App.get_app_state() & STATUS_LED_WARNING) != 0u) ); then: - script.execute: blink_status_led # repeat as long as error/warning exists else: - script.execute: script_set_power_leds # done with status LED, restore light power status - id: script_setting_reboot mode: restart # only reboot plug settings are static for 10s. Another change restarts timer. then: - lambda: ESP_LOGCONFIG("kauf-plug.yaml","Setting change requiring reboot detected, rebooting in 10 seconds to effect change."); - delay: 10s - button.press: restart_button - id: script_30s_timer then: - delay: !lambda return (30000-id(number_debounce).state); - script.execute: $sub_on_hold_30s - id: on_wifi_connect then: # wait until wifi connects - wait_until: wifi.connected - lambda: |- ESP_LOGD("KAUF on_boot","------------------->>>>>>>>>>>>>>>>> wifi connected, cranking ap timeout back up"); wifi_wificomponent_id->set_ap_timeout(2147483647); - id: script_force_ap then: - logger.log: "------------------->>>>>>>>>>>>>>>>> HELD BUTTON 30 SECONDS, FORCING AP" # overwrite software defined credentials to force ap to turn on. - lambda: wifi::global_wifi_component->save_wifi_sta("initial_ap","asdfasdfasdfasdf"); # blink LED for 10s then restart to get captive portal to turn on. - script.execute: blink_led - delay: 10s - button.press: restart_button # blink LED forever. Used when button is held to re-enable AP. Stops blinking because plug restarts. - id: blink_led mode: queued then: - switch.toggle: blue_led - delay: 333ms - script.execute: blink_led - id: script_do_nothing then: - lambda: return; - id: script_save_changes mode: restart then: - delay: 3s - lambda: global_preferences->sync(); # pwm outputs for LEDs so they can be dimmed # https://esphome.io/components/output/esp8266_pwm.html output: - platform: esp8266_pwm id: blue_led_pwm frequency: 200 Hz pin: $sub_blue_led_pin inverted: true - platform: esp8266_pwm id: red_led_pwm frequency: 200 Hz pin: $sub_red_led_pin inverted: true # https://esphome.io/components/switch/index.html switch: # relay output # https://esphome.io/components/switch/gpio.html - platform: gpio id: relay name: $friendly_name pin: $sub_relay_pin entity_category: '' forced_hash: 41191675 forced_addr: 2 global_addr: global_forced_addr restore_mode: $sub_restore_mode on_turn_on: - script.execute: script_set_power_leds - script.execute: $sub_on_turn_on on_turn_off: - script.execute: script_set_power_leds - script.execute: $sub_on_turn_off # https://esphome.io/components/switch/template.html - platform: template id: switch_no_hass name: $friendly_name No HASS optimistic: true entity_category: config disabled_by_default: $disable_entities icon: mdi:toggle-switch-off-outline restore_mode: RESTORE_DEFAULT_ON on_turn_on: - lambda: |- id(kauf_api).set_reboot_timeout(0); // 0 disables auto rebooting and also new status led blinking. doesn't stop current status led blinking id(kauf_api).status_clear_warning(); // stops current status led blinking, timeout 0 keeps it from restarting - script.execute: script_save_changes on_turn_off: - lambda: id(kauf_api).set_reboot_timeout(900000); - script.execute: script_save_changes forced_hash: 657159011 forced_addr: 42 global_addr: global_forced_addr - platform: template id: blue_led name: $friendly_name Blue LED optimistic: true entity_category: config disabled_by_default: $disable_entities icon: mdi:led-outline restore_mode: RESTORE_DEFAULT_OFF on_turn_on: - lambda: id(blue_led_pwm).set_level(id(blue_led_brightness).state/100); on_turn_off: - lambda: id(blue_led_pwm).set_level(0.0); forced_hash: 1329407616 forced_addr: 44 global_addr: global_forced_addr - platform: template id: red_led name: $friendly_name Red LED optimistic: true entity_category: config disabled_by_default: $disable_entities icon: mdi:led-outline restore_mode: RESTORE_DEFAULT_OFF on_turn_on: - lambda: id(red_led_pwm).set_level(id(red_led_brightness).state/100); on_turn_off: - lambda: id(red_led_pwm).set_level(0.0); forced_hash: 261191305 forced_addr: 48 global_addr: global_forced_addr - platform: template id: switch_early_publish name: $friendly_name Early Publish optimistic: true entity_category: config disabled_by_default: $disable_entities restore_mode: RESTORE_DEFAULT_OFF on_turn_on: - lambda: id(hlw_main).enable_early_publish(); on_turn_off: - lambda: id(hlw_main).disable_early_publish(); forced_hash: 1334841796 forced_addr: 62 global_addr: global_forced_addr # https://esphome.io/components/button/index.html # https://esphome.io/components/button/restart.html button: - platform: restart id: restart_button name: $friendly_name Restart Firmware entity_category: diagnostic disabled_by_default: $disable_entities # clock input from Home Assistant used to calculate total daily energy # https://esphome.io/components/time.html#home-assistant-time-source time: - platform: homeassistant id: homeassistant_time # https://esphome.io/components/sensor/index.html sensor: # Power monitoring sensors output to Home Assistant # https://esphome.io/components/sensor/hlw8012.html - platform: kauf_hlw8012 id: hlw_main sel_pin: number: $sub_pm_sel_pin inverted: True cf_pin: $sub_pm_cf_pin cf1_pin: $sub_pm_cf1_pin current_resistor: $current_resistor_val voltage_divider: $voltage_divider_val update_interval: $sub_update_interval timeout: $sub_hlw_timeout early_publish_percent: $sub_early_publish_percent early_publish_percent_min_power: $sub_early_publish_percent_min_power early_publish_absolute: $sub_early_publish_absolute model: $sub_hlw_model power: name: $friendly_name Power unit_of_measurement: W id: wattage filters: - calibrate_linear: - $power_cal_val1_in -> $power_cal_val1_out - $power_cal_val2_in -> $power_cal_val2_out - lambda: |- // filter out extremely large values that are incorrect float return_val = x * id(scale_power).state/100.0f; if ( return_val > 5000.0f) return {}; else return return_val; on_value: # set or clear in_use template binary sensor depending on whether power usage is over threshold - lambda: id(in_use).publish_state(x >= id(threshold).state); current: name: $friendly_name Current unit_of_measurement: A id: current filters: - calibrate_linear: - $current_cal_val1_in -> $current_cal_val1_out - $current_cal_val2_in -> $current_cal_val2_out - lambda: return x * id(scale_current).state/100.0f; voltage: name: $friendly_name Voltage unit_of_measurement: V id: voltage filters: - calibrate_linear: - $voltage_cal_val1_in -> $voltage_cal_val1_out - $voltage_cal_val2_in -> $voltage_cal_val2_out - lambda: return x * id(scale_voltage).state/100.0f; # Reports the total Power so-far each day, resets at midnight # https://esphome.io/components/sensor/total_daily_energy.html - platform: total_daily_energy name: $friendly_name Total Daily Energy power_id: wattage filters: - multiply: 0.001 ## convert Wh to kWh unit_of_measurement: kWh forced_hash: 1903527169 forced_addr: 6 global_addr: global_forced_addr # https://esphome.io/components/sensor/uptime.html - platform: uptime name: $friendly_name Uptime update_interval: 60s entity_category: diagnostic disabled_by_default: $disable_entities # https://esphome.io/components/sensor/template.html - platform: template name: $friendly_name Button Press Duration id: sensor_press_duration entity_category: diagnostic disabled_by_default: $disable_entities unit_of_measurement: ms icon: mdi:timer-outline # https://esphome.io/components/number/index.html # https://esphome.io/components/number/template.html number: # used as a threshold for whether the plugged-in devices is running. - platform: template name: $friendly_name Use Threshold min_value: 1 max_value: 100 step: 1 initial_value: 3 id: threshold entity_category: config optimistic: true restore_value: true unit_of_measurement: Watt(s) mode: box disabled_by_default: $disable_entities forced_hash: 3932521563 forced_addr: 4 global_addr: global_forced_addr set_action: - script.execute: script_save_changes on_value: # set or clear in_use template binary sensor depending on whether power usage is above threshold - lambda: id(in_use).publish_state(id(wattage).state >= x); - platform: template name: $friendly_name Blue LED Brightness min_value: 1 max_value: 100 step: 1 initial_value: 75 id: blue_led_brightness entity_category: config optimistic: true restore_value: true unit_of_measurement: "%" mode: slider icon: mdi:brightness-percent set_action: - script.execute: script_save_changes on_value: - lambda: if ( id(blue_led).state ) id(blue_led_pwm).set_level(x/100); forced_hash: 2635887862 forced_addr: 46 global_addr: global_forced_addr - platform: template name: $friendly_name Red LED Brightness min_value: 1 max_value: 100 step: 1 initial_value: 75 id: red_led_brightness entity_category: config optimistic: true restore_value: true unit_of_measurement: "%" mode: slider icon: mdi:brightness-percent set_action: - script.execute: script_save_changes on_value: - lambda: if ( id(red_led).state ) id(red_led_pwm).set_level(x/100); forced_hash: 4192312897 forced_addr: 50 global_addr: global_forced_addr - platform: template name: $friendly_name Scale Power min_value: 50 max_value: 200 step: .1 initial_value: $sub_default_scale_power id: scale_power entity_category: config optimistic: true restore_value: true unit_of_measurement: "%" mode: box disabled_by_default: $disable_entities forced_hash: 3565176138 forced_addr: 56 global_addr: global_forced_addr set_action: - script.execute: script_save_changes on_value: # republish value. Sensor automation applies new scaling factor. - lambda: id(wattage).publish_state(id(wattage).get_raw_state()); - platform: template name: $friendly_name Scale Current min_value: 50 max_value: 200 step: .1 initial_value: $sub_default_scale_current id: scale_current entity_category: config optimistic: true restore_value: true unit_of_measurement: "%" mode: box disabled_by_default: $disable_entities forced_hash: 2293595686 forced_addr: 58 global_addr: global_forced_addr set_action: - script.execute: script_save_changes on_value: # republish value. Sensor automation applies new scaling factor. - lambda: id(current).publish_state(id(current).get_raw_state()); - platform: template name: $friendly_name Scale Voltage min_value: 50 max_value: 200 step: .1 initial_value: $sub_default_scale_voltage id: scale_voltage entity_category: config optimistic: true restore_value: true unit_of_measurement: "%" mode: box disabled_by_default: $disable_entities forced_hash: 254525215 forced_addr: 60 global_addr: global_forced_addr set_action: - script.execute: script_save_changes on_value: # republish value. Sensor automation applies new scaling factor. - lambda: id(voltage).publish_state(id(voltage).get_raw_state()); - platform: template name: $friendly_name Debounce Time min_value: $sub_min_debounce max_value: 2000 step: 1 initial_value: $sub_default_debounce id: number_debounce entity_category: config optimistic: true restore_value: true unit_of_measurement: "ms" mode: box disabled_by_default: $disable_entities forced_hash: 2232959069 forced_addr: 40 global_addr: global_forced_addr # https://esphome.io/components/select/index.html # https://esphome.io/components/select/template.html select: - platform: template name: $friendly_name Button Config id: select_button optimistic: true options: - Toggle on Press - Toggle on Release - Don't Toggle initial_option: $default_button_config restore_value: true icon: mdi:circle-double entity_category: config forced_hash: 3616613943 forced_addr: 34 global_addr: global_forced_addr set_action: - script.execute: script_save_changes - platform: template name: $friendly_name Blue LED Config id: select_bled optimistic: true entity_category: config options: - Power Status - No Automation - Invert Power Status - Error Status - Error and Power - Error and Invert Power initial_option: Power Status restore_value: true icon: mdi:led-on forced_hash: 3104663617 forced_addr: 36 global_addr: global_forced_addr set_action: - script.execute: script_save_changes on_value: - if: condition: - lambda: return ( x == "Error Status" ); then: - switch.turn_off: blue_led - script.execute: script_set_power_leds - platform: template name: $friendly_name Red LED Config id: select_rled optimistic: true entity_category: config options: - Power Status - No Automation - Invert Power Status - Error Status - Error and Power - Error and Invert Power initial_option: Error Status restore_value: true icon: mdi:led-on forced_hash: 261191305 forced_addr: 38 global_addr: global_forced_addr set_action: - script.execute: script_save_changes on_value: - if: condition: - lambda: return ( x == "Error Status" ); then: - switch.turn_off: red_led - script.execute: script_set_power_leds # change update interval for power monitoring sensors - platform: template name: $friendly_name Monitoring Update Interval id: select_monitor_interval optimistic: true options: - 60s - 30s - 10s - 5s - 2s - YAML Configured ($sub_update_interval) initial_option: $sub_pm_initial_option restore_value: true icon: mdi:wrench-clock entity_category: config disabled_by_default: $disable_entities forced_hash: 1025003091 forced_addr: 0 global_addr: global_forced_addr set_action: - script.execute: script_save_changes - script.execute: script_setting_reboot on_value: - lambda: |- if ( x == "60s" ) { id(hlw_main).set_update_interval(60000); } else if ( x == "30s" ) { id(hlw_main).set_update_interval(30000); } else if ( x == "10s" ) { id(hlw_main).set_update_interval(10000); } else if ( x == "5s" ) { id(hlw_main).set_update_interval(5000); } else if ( x == "2s" ) { id(hlw_main).set_update_interval(2000); } - platform: template name: $friendly_name Boot State id: select_boot_state optimistic: true options: - "Restore Power Off State" - "Invert Power Off State" - "Always On" - "Always Off" - "YAML Configured ($sub_restore_mode)" initial_option: "Restore Power Off State" restore_value: true icon: mdi:restart-alert entity_category: config forced_hash: 2390278151 forced_addr: 74 global_addr: global_forced_addr on_value: - lambda: |- if ( x == "Restore Power Off State") relay->set_restore_mode(SWITCH_RESTORE_DEFAULT_OFF); else if ( x == "Invert Power Off State") relay->set_restore_mode(SWITCH_RESTORE_INVERTED_DEFAULT_OFF); else if ( x == "Always On") relay->set_restore_mode(SWITCH_ALWAYS_ON); else if ( x == "Always Off") relay->set_restore_mode(SWITCH_ALWAYS_OFF); else relay->set_restore_mode(SWITCH_$sub_restore_mode); // republish state so it gets saved if necessary // but make sure relay already set up, don't want to publish here on boot if ( id(relay).is_setup() ) id(relay).publish_state(id(relay).state, true); - script.execute: script_save_changes # Send IP Address to HA # https://esphome.io/components/text_sensor/wifi_info.html text_sensor: - platform: wifi_info ip_address: name: $friendly_name IP Address disabled_by_default: $disable_entities # emulate status_led # https://esphome.io/guides/automations.html#interval-component interval: - interval: 5s then: - lambda: |- if ( ( ((App.get_app_state() & STATUS_LED_ERROR ) != 0u) || ((App.get_app_state() & STATUS_LED_WARNING) != 0u) ) && !id(blink_status_led).is_running() ) id(blink_status_led).execute(); # Current reserved flash memory: # 00-01: Power Monitoring Mode # 02-03: Relay output # 04-05: Use Threshold # 06-07: Total Daily Energy # 08-33: Wi-Fi Credentials # 34-35: Button Config select entity # 36-37: Blue LED select entity # 38-39: Red LED select entity # 40-41: Debounce number entity # 42-43: No HASS switch # 44-45: Blue LED # 46-47: Blue LED Brightness # 48-49: Red LED # 50-51: Red LED Brightness # 56-57: Scale Power # 58-59: Scale Current # 60-61: Scale Voltage # 62-63: Early Publish Switch # 74-75: Boot State select entity