# 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: "1.96" project_ver_let: y # https://esphome.io/components/switch/gpio.html?highlight=restore_mode sub_restore_mode: RESTORE_DEFAULT_OFF # overwrite to change boot up behavior of relay disable_entities: "true" # set to "false" to have all entities show up in Home Assistant automatically # substitutions for button actions. on_press and on_release implement a timer scheme with configurable delay. # on_hold_5s 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_5s: script_do_nothing # executes right when button has been held for 5s, while button is still being held # 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_do_nothing # 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" 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" # set power monitoring mode in yaml, selecting the "yaml_configured" option in the select entity will cause these # values to be used after next reboot. # https://esphome.io/components/sensor/hlw8012.html#configuration-variables sub_change_mode_every: "1" sub_update_interval: 10s sub_initial_mode: CURRENT # https://esphome.io/components/esp8266.html esp8266: board: esp01_1m restore_from_flash: true early_pin_init: false # https://esphome.io/guides/automations.html#global-variables globals: - id: global_press_time type: int restore_value: no initial_value: '0' # https://esphome.io/components/esphome.html#adjusting-flash-writes preferences: flash_write_interval: 3s # https://esphome.io/components/esphome.html esphome: name: $name project: name: $project_name version: $project_ver_num($project_ver_let) on_boot: priority: 700 then: # implementing on_boot function as a script so that any other on_boot automations in other files # will execute in parallel and not be blocked by this one. - script.execute: on_boot_main # 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 # default is 20, 17 is recommended. output_power: 17 # using fast_connect as default since it is required for hidden networks. fast_connect: true # https://esphome.io/components/logger.html logger: # https://esphome.io/components/api.html api: id: kauf_api # https://esphome.io/components/ota.html ota: on_error: then: - button.press: restart_button # https://esphome.io/components/web_server.html web_server: local: true # 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: GPIO13 mode: input: true pullup: true inverted: true on_press: then: - 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_5s_timer on_release: then: - 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_5s_timer # 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: |- auto call = id(blue_led).make_call(); // if blue led follows power status if ( (id(select_bled).state == "Power Status") || (id(select_bled).state == "Error and Power") ) { call.set_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") ) { call.set_state(!id(relay).state); } // if blue led is set to not change on power status (error only or disabled), performing an empty call does nothing. call.perform(); // same as blue but for red led call = id(red_led).make_call(); if ( (id(select_rled).state == "Power Status") || (id(select_rled).state == "Error and Power") ) { call.set_state(id(relay).state); } else if ( (id(select_rled).state == "Invert Power Status") || (id(select_rled).state == "Error and Invert Power") ) { call.set_state(!id(relay).state); } call.perform(); - 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") ) { auto call = id(blue_led).turn_on(); call.set_save(false); call.perform(); } // 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") ) { auto call = id(red_led).turn_on(); call.set_save(false); call.perform(); } - 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") ) { auto call = id(blue_led).turn_off(); call.set_save(false); call.perform(); } // 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") ) { auto call = id(red_led).turn_off(); call.set_save(false); call.perform(); } - 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_new_debounce mode: restart # only reboot plug once number is static for 10s. changing value again restarts timer. then: # only do it if wifi is connected, keeps initial loading of value on boot from causing restart. - if: condition: - wifi.connected then: - lambda: ESP_LOGD("kauf-plug.yaml","New debounce value detected, rebooting in 10 seconds to effect change."); - delay: 10s - button.press: restart_button - id: script_5s_timer then: - delay: !lambda return (5000-id(number_debounce).state); - script.execute: $sub_on_hold_5s - id: script_do_nothing then: - lambda: return; - id: on_boot_main then: # this on_boot script has to execute after select and number entities are set up, so # priority < 800. Adding a check here to make sure all needed entities are set up just # in case another yaml file makes on_boot priority higher than 800 the following code # will still work right. If on_boot priority becomes <= 600, the code for setting up # power monitoring mode won't work because HLW will already be set up before values are # changed in this script. - wait_until: lambda: return (id(select_monitor_mode).has_state() ); - lambda: |- ////////////////////////////////////////////////////////////////////// // implement power monitoring mode per select entity if ( id(select_monitor_mode).state == "10s P / 40s V,I") { id(hlw_main).set_update_interval(10000); id(hlw_main).set_change_mode_every(1); } else if ( id(select_monitor_mode).state == "10s P,I Only" ) { id(hlw_main).set_update_interval(10000); id(hlw_main).set_change_mode_every(4294967295); id(hlw_main).set_initial_mode(hlw8012::HLW8012_INITIAL_MODE_CURRENT); } else if ( id(select_monitor_mode).state == "10s P,V Only" ) { id(hlw_main).set_update_interval(10000); id(hlw_main).set_change_mode_every(4294967295); id(hlw_main).set_initial_mode(hlw8012::HLW8012_INITIAL_MODE_VOLTAGE); } else if ( id(select_monitor_mode).state == " 2s P,I Only" ) { id(hlw_main).set_update_interval(2000); id(hlw_main).set_change_mode_every(4294967295); id(hlw_main).set_initial_mode(hlw8012::HLW8012_INITIAL_MODE_CURRENT); } else if ( id(select_monitor_mode).state == " 2s P,V Only" ) { id(hlw_main).set_update_interval(2000); id(hlw_main).set_change_mode_every(4294967295); id(hlw_main).set_initial_mode(hlw8012::HLW8012_INITIAL_MODE_VOLTAGE); } ////////////////////////////////////////////////////////////////////// // add a delayed_on filter to the button binary_sensor with time argument from number_debounce. // this is placed after settings recovery so that it will take into account recovered setting // on first boot. In any case, needs to be after priority 800 so that number_debounce is set up. binary_sensor::DelayedOnFilter *button_delayedonfilter; button_delayedonfilter = new binary_sensor::DelayedOnFilter(id(number_debounce).state); button_delayedonfilter->set_component_source("binary_sensor"); App.register_component(button_delayedonfilter); id(button_in).add_filters({button_delayedonfilter}); # 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: 1000 Hz pin: GPIO2 inverted: true - platform: esp8266_pwm id: red_led_pwm frequency: 1000 Hz pin: GPIO0 inverted: true # light entities for blue and red LEDs # https://esphome.io/components/light/index.html # https://esphome.io/components/light/monochromatic.html light: - platform: monochromatic name: $friendly_name Blue LED id: blue_led output: blue_led_pwm entity_category: config default_transition_length: 0s effects: - flicker: name: Flicker alpha: 90% intensity: 10% - platform: monochromatic name: $friendly_name Red LED id: red_led output: red_led_pwm entity_category: config default_transition_length: 0s effects: - flicker: name: Flicker alpha: 90% intensity: 10% # 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: GPIO4 entity_category: '' restore_mode: $sub_restore_mode on_turn_on: - script.execute: script_set_power_leds on_turn_off: - script.execute: script_set_power_leds # https://esphome.io/components/switch/template.html - platform: template id: switch_no_hass name: $friendly_name No HASS optimistic: true restore_state: true entity_category: config disabled_by_default: $disable_entities icon: mdi:toggle-switch-off-outline 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 on_turn_off: - lambda: id(kauf_api).set_reboot_timeout(900000); # 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: hlw8012 id: hlw_main sel_pin: number: GPIO12 inverted: True cf_pin: GPIO5 cf1_pin: GPIO14 current_resistor: $current_resistor_val voltage_divider: $voltage_divider_val change_mode_every: $sub_change_mode_every update_interval: $sub_update_interval initial_mode: $sub_initial_mode 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: return x * id(scale_power).state/100.0f; 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 min_save_interval: 5min # 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 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} Scale Power min_value: 50 max_value: 200 step: .1 initial_value: 100 id: scale_power entity_category: config optimistic: true restore_value: true unit_of_measurement: "%" mode: box disabled_by_default: $disable_entities 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: 100 id: scale_current entity_category: config optimistic: true restore_value: true unit_of_measurement: "%" mode: box disabled_by_default: $disable_entities 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: 100 id: scale_voltage entity_category: config optimistic: true restore_value: true unit_of_measurement: "%" mode: box disabled_by_default: $disable_entities 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: 50 max_value: 2000 step: 1 initial_value: 75 id: number_debounce entity_category: config optimistic: true restore_value: true unit_of_measurement: "ms" mode: box disabled_by_default: $disable_entities on_value: # currently, have to reboot plug to apply new delayed_on time - script.execute: script_new_debounce # 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 - 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 on_value: then: - if: condition: - lambda: return ( x == "Error Status" ); then: - light.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 on_value: then: - if: condition: - lambda: return ( x == "Error Status" ); then: - light.turn_off: red_led - script.execute: script_set_power_leds # change mode of power monitoring - platform: template name: $friendly_name Monitoring Mode id: select_monitor_mode optimistic: true options: - "10s P / 40s V,I" - "10s P,I Only" - "10s P,V Only" - " 2s P,I Only" - " 2s P,V Only" - "YAML Configured" initial_option: "10s P / 40s V,I" restore_value: true icon: mdi:wrench-clock entity_category: config disabled_by_default: $disable_entities set_action: - delay: 1s - lambda: global_preferences->sync(); - delay: 2s - button.press: restart_button # 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: - if: condition: - lambda: return ( ( (App.get_app_state() & STATUS_LED_ERROR) != 0u) || ((App.get_app_state() & STATUS_LED_WARNING) != 0u) ); then: - if: condition: not: - script.is_running: blink_status_led then: - script.execute: blink_status_led