# 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.111" 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_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_debounce: "100" # threshold for in_use sensor sub_threshold: "3" # API reboot timeout sub_reboot_timeout: 0s # https://esphome.io/components/esp8266.html esp8266: board: esp01_1m restore_from_flash: true early_pin_init: false start_free: 76 # 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 friendly_name: $friendly_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: 2026.1.4 # https://esphome.io/components/external_components.html external_components: - source: type: git url: https://github.com/KaufHA/common ref: v2026.02.07.1 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 min_auth_mode: WPA2 # 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 reboot_timeout: $sub_reboot_timeout # https://esphome.io/components/ota.html ota: - platform: web_server on_error: - button.press: restart_button - 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 product: plf10 # 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: Button pin: number: $sub_button_pin mode: input: true pullup: true inverted: true on_press: - lambda: |- // toggle if configured for toggle on press if (id(select_button).current_option() == "Toggle on Press") { id(relay).toggle(); } - script.execute: $sub_on_press - script.execute: script_30s_timer on_release: - lambda: |- // toggle if configured on release and toggle check script is not running. if ( (id(select_button).current_option() == "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 $sub_debounce; # 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: 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).current_option() == "Power Status") || (id(select_bled).current_option() == "Error and Power") ) { id(blue_led).publish_state(id(relay).state); } // if blue led follows inverse of power status else if ( (id(select_bled).current_option() == "Invert Power Status") || (id(select_bled).current_option() == "Error and Invert Power") ) { id(blue_led).publish_state(!id(relay).state); } // if red led follows power status if ( (id(select_rled).current_option() == "Power Status") || (id(select_rled).current_option() == "Error and Power") ) { id(red_led).publish_state(id(relay).state); } // if red led follows inverse of power status else if ( (id(select_rled).current_option() == "Invert Power Status") || (id(select_rled).current_option() == "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).current_option() == "Error Status") || (id(select_bled).current_option() == "Error and Power") || (id(select_bled).current_option() == "Error and Invert Power") ) { id(blue_led).turn_on(); } // turn on red LED if configured for error status if ( (id(select_rled).current_option() == "Error Status") || (id(select_rled).current_option() == "Error and Power") || (id(select_rled).current_option() == "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).current_option() == "Error Status") || (id(select_bled).current_option() == "Error and Power") || (id(select_bled).current_option() == "Error and Invert Power") ) { id(blue_led).turn_off(); } // turn off red LED if configured for error status if ( (id(select_rled).current_option() == "Error Status") || (id(select_rled).current_option() == "Error and Power") || (id(select_rled).current_option() == "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-$sub_debounce); - 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: "" pin: $sub_relay_pin entity_category: '' forced_hash: 41191675 forced_addr: 2 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 - platform: template id: blue_led 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 - platform: template id: red_led 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 - platform: template id: switch_early_publish 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 # https://esphome.io/components/button/index.html # https://esphome.io/components/button/restart.html button: - platform: restart id: restart_button 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: Power unit_of_measurement: W state_class: measurement 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 >= $sub_threshold); current: name: Current unit_of_measurement: A state_class: measurement 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: Voltage unit_of_measurement: V state_class: measurement 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: Total Daily Energy id: tde power_id: wattage filters: - multiply: 0.001 ## convert Wh to kWh unit_of_measurement: kWh state_class: total_increasing forced_hash: 1903527169 forced_addr: 6 # https://esphome.io/components/sensor/uptime.html - platform: uptime name: Uptime state_class: total_increasing update_interval: 60s entity_category: diagnostic disabled_by_default: $disable_entities # 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: 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 - platform: template 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 - platform: template 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 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: 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 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: 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 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()); # https://esphome.io/components/select/index.html # https://esphome.io/components/select/template.html select: - platform: template 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 set_action: - script.execute: script_save_changes - platform: template 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 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: 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 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 # Send IP Address to HA # https://esphome.io/components/text_sensor/wifi_info.html text_sensor: - platform: wifi_info ip_address: 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 Interval # 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