aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--config-factory/kauf-plug-factory.yaml438
-rw-r--r--config-factory/webserver-v1.min.css2
-rw-r--r--config-factory/webserver-v1.min.js1
-rw-r--r--config-update/kauf-plug-update.yaml367
-rw-r--r--config-update/webserver-v1.min.css2
-rw-r--r--config-update/webserver-v1.min.js1
-rw-r--r--kauf-plug.yaml68
7 files changed, 853 insertions, 26 deletions
diff --git a/config-factory/kauf-plug-factory.yaml b/config-factory/kauf-plug-factory.yaml
new file mode 100644
index 0000000..53ddccb
--- /dev/null
+++ b/config-factory/kauf-plug-factory.yaml
@@ -0,0 +1,438 @@
+substitutions:
+ friendly_name: Kauf Plug
+
+esp8266: # https://esphome.io/components/esp8266.html
+ board: esp01_1m
+ restore_from_flash: true
+
+external_components:
+ - source:
+ type: git
+ url: https://github.com/KaufHA/kauf_plf10_components
+ refresh: 0s
+ # - source:
+ # type: local
+ # path: kauf_plf10_components
+
+preferences:
+ flash_write_interval: 3s
+
+esphome:
+
+ name: kauf-plug
+ name_add_mac_suffix: true
+
+ project:
+ name: "kauf.plf10"
+ version: "1.8"
+
+ on_boot:
+ then:
+
+
+ # little sequence so factory can confirm firwmare is working.
+ - if:
+ condition:
+ lambda: 'return id(first_boot);'
+ then:
+
+ # get AP going ASAP
+ - lambda: 'wifi_wificomponent->set_ap_timeout(1);'
+
+ # disable button toggling relay
+ - select.set:
+ id: select_button
+ option: "Disabled"
+
+ # turn on relay to draw current
+ - switch.turn_on: relay
+ - delay: 1s
+ - switch.turn_off: blue_led
+
+ # wait until button is pressed, then turn on led
+ - while:
+ condition:
+ binary_sensor.is_off: button
+ then:
+ - switch.turn_off: blue_led
+ - delay: 50ms
+ - while:
+ condition:
+ binary_sensor.is_on: button
+ then:
+ - switch.turn_on: blue_led
+ - delay: 50ms
+
+ # give it some time to see if a crash occurs with both AP and relay on
+ - delay: 8s
+
+ # test passed, clear first_boot variable so we don't run this again
+ - globals.set:
+ id: first_boot
+ value: 'false'
+
+ # re-enable button toggling relay
+ - select.set:
+ id: select_button
+ option: "Enabled"
+
+ # delay time to save above changes
+ - delay: 3s
+
+ # blink blue led to indicate pass test until button is pressed then stay on
+ - while:
+ condition:
+ binary_sensor.is_off: button
+ then:
+ - switch.toggle: blue_led
+ - delay: 100ms
+ - while:
+ condition:
+ binary_sensor.is_on: button
+ then:
+ - switch.turn_on: blue_led
+ - delay: 50ms
+
+
+ # normal bootup
+
+ # wait until ESPHome tries loading wifi credentials
+ - wait_until:
+ lambda: 'return ( wifi_wificomponent->tried_loading_creds );'
+
+
+ - if:
+ condition: # don't do anything if credentials were loaded unless force_ap is on as override
+ lambda: 'return ( wifi_wificomponent->loaded_creds && !id(force_ap) );'
+ then:
+ - logger.log: "------------------->>>>>>>>>>>>>>>>> wifi already configured, leaving ap timeout cranked"
+
+ - globals.set: # set global so if wifi never connects we'll have AP next time.
+ id: force_ap
+ value: 'true'
+
+ - wait_until:
+ wifi.connected
+ - globals.set: # clear global so we won't have AP next time
+ id: force_ap
+ value: 'false'
+
+ # if we do need AP
+ else: # if credentials not loaded or force_ap is set, lower ap timeout to allow saving new credentials
+ - logger.log: "------------------->>>>>>>>>>>>>>>>> wifi not configured, enabling ap and waiting"
+ # set ap timeout to 15 seconds in milliseconds.
+ - lambda: 'wifi_wificomponent->set_ap_timeout(15000);'
+ - wait_until:
+ wifi.connected
+ - globals.set: # clear global so ap doesn't come back up next time
+ id: force_ap
+ value: 'false'
+
+ # set ap timeout to max 32 bit value in milliseconds. About 3.5 weeks.
+ - logger.log: "------------------->>>>>>>>>>>>>>>>> wifi connected, cranking ap timeout back up"
+ - lambda: 'wifi_wificomponent->set_ap_timeout(2147483647);'
+
+globals:
+ - id: force_ap
+ type: bool
+ restore_value: yes
+ initial_value: "true"
+ - id: first_boot # used to run routine at factory to confirm
+ type: bool # correct firwmare is running.
+ restore_value: yes
+ initial_value: 'true'
+
+
+wifi:
+
+ # default credentials as backup. Also needed to show found networks in captive portal.
+ ssid: initial_ap2
+ password: asdfasdfasdfasdf
+
+ # use_address allows wireless programming through dashboard. remove after programming.
+ # use_address: 192.168.86.202
+
+ # default is 20, 17 is recommended.
+ output_power: 17
+
+ ap:
+ ssid: ${friendly_name} Hotspot
+ ap_timeout: 2147483647ms # default to max 32-bit value in milliseconds. About 3.5 weeks.
+
+
+captive_portal: # for fallback wifi hotspot
+
+logger: # Enable logging
+ # baud_rate: 0 # Disable UART logging since TX pad not easily available
+
+api: # Enable Home Assistant API
+ # password: !secret api_password # optional password field for Home Assistant API.
+
+ota:
+ # password: !secret ota_password # optional password for OTA updates.
+
+debug: # outputs additional debug info when logs start
+
+
+web_server: # web server allows access to device with a web browser
+ # auth: # optional login details for web interface
+ # username: admin
+ # password: !secret web_server_password
+
+ # host css and js file directly on device
+ css_include: "webserver-v1.min.css"
+ css_url: ""
+ js_include: "webserver-v1.min.js"
+ js_url: ""
+
+
+# red led, blink when not connected to WiFi or Home Assistant
+status_led:
+ pin:
+ number: GPIO0
+ inverted: true
+
+
+binary_sensor:
+
+ # button input toggles relay and thereby blue led
+ - platform: gpio
+ id: button
+ name: $friendly_name Button
+ pin:
+ number: GPIO13
+ mode: INPUT_PULLUP
+ inverted: true
+ entity_category: ''
+
+
+ on_click:
+
+ - min_length: 30ms
+ max_length: 2s
+ then:
+ - if:
+ condition: # only toggle relay if button is enabled
+ lambda: 'return (id(select_button).state == "Enabled");'
+ then:
+ switch.toggle: relay
+
+
+
+ # restart with AP enabled if button is held for 5 seconds (up to 30 seconds because ESPHome requires a max).
+ # Not disabled by button disable option because you may not be able to re-enable button if wifi is not connecting.
+ # If your kid accidentally triggers this, the plug will just reboot and reconnect to wifi. Shouldn't harm anything.
+
+ - min_length: 5s
+ max_length: 30s
+ then:
+ - logger.log: "------------------->>>>>>>>>>>>>>>>> HELD BUTTON 5 SECONDS, FORCING AP"
+
+ # blink LED for 10s then restart to get captive portal to turn on.
+ - globals.set:
+ id: force_ap
+ value: 'true'
+ - script.execute: blink_led
+ - delay: 10s
+
+ # we have to restart because ESPHome's AP won't show the captive portal once wifi has been connected.
+ - switch.turn_on: restart_switch
+
+
+ # indicates whether plugged-in device is running based on configurable threshold.
+ - platform: template
+ id: in_use
+ name: ${friendly_name} Device In Use
+
+
+script:
+ # 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
+ # clear wifi credentials and reboot. causes AP to be enabled.
+ - id: clear_wifi_script
+ then:
+ - lambda: 'wifi_wificomponent->clear_stored_creds();'
+ - delay: 2s
+ - switch.turn_on: restart_switch
+
+switch:
+
+ # blue LED follows relay power state
+ - platform: gpio
+ id: blue_led
+ pin:
+ number: GPIO2
+ inverted: true
+
+ # relay output
+ - platform: gpio
+ id: relay
+ name: $friendly_name
+ pin: GPIO4
+ entity_category: ''
+
+ # automatically make blue led equal relay state
+ on_turn_on:
+ - if:
+ condition: # only if blue LED enabled
+ lambda: 'return (id(select_led).state == "Enabled");'
+ then:
+ switch.turn_on: blue_led
+
+ on_turn_off:
+ - switch.turn_off: blue_led
+
+ - platform: restart
+ id: restart_switch
+ name: $friendly_name Restart Firmware
+ entity_category: diagnostic
+ disabled_by_default: true
+
+ - platform: template
+ id: clear_wifi_switch
+ name: $friendly_name Clear WiFi Credentials
+ optimistic: true
+ entity_category: diagnostic
+ disabled_by_default: true
+# restore_state: no # no is default already per https://esphome.io/components/switch/template.html
+ on_turn_on:
+ then:
+ script.execute: clear_wifi_script
+
+
+# clock input from Home Assistant used to calculate total daily energy
+time:
+ - platform: homeassistant
+ id: homeassistant_time
+
+
+sensor: # Power monitoring sensors output to Home Assistant
+ - platform: hlw8012
+ sel_pin:
+ number: GPIO12
+ inverted: True
+ cf_pin: GPIO5
+ cf1_pin: GPIO14
+ current_resistor: 0.001 # The value of the shunt resistor for current measurement.
+ voltage_divider: 2401 # The value of the voltage divider on the board as (R_upstream + R_downstream) / R_downstream.
+ power:
+ name: ${friendly_name} Power
+ unit_of_measurement: W
+ id: wattage
+ filters:
+ - calibrate_linear:
+ - 0.0 -> 0.0
+ - 333.8 -> 60 # value with 60W bulb.
+ on_value: # set or clear in_use template binary sensor depending on whether power usage is over threshold
+ - if:
+ condition:
+ lambda: 'return (x >= id(threshold).state);'
+ then:
+ - binary_sensor.template.publish:
+ id: in_use
+ state: ON
+ else:
+ - binary_sensor.template.publish:
+ id: in_use
+ state: OFF
+ current:
+ name: ${friendly_name} Current
+ unit_of_measurement: A
+ filters:
+ - calibrate_linear:
+ - 0.0 -> 0.0
+ - 0.6 -> 0.515 # value with 60W bulb.
+ voltage:
+ name: ${friendly_name} Voltage
+ unit_of_measurement: V
+ filters:
+ - calibrate_linear:
+ - 0.0 -> 0.0
+ - 302.1 -> 117.1 # Tested using a meter
+ change_mode_every: 1
+ update_interval: 10s # 20 second effective update rate for Power, 40 second for Current and Voltage.
+
+# Reports the total Power so-far each day, resets at midnight
+# See 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
+
+
+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 # required for changing value from home assistant
+ restore_value: true
+ on_value:
+ - if: # set or clear in_use template binary sensor depending on whether power usage is above threshold
+ condition:
+ lambda: 'return (id(wattage).state >= x);'
+ then:
+ - binary_sensor.template.publish:
+ id: in_use
+ state: ON
+ else:
+ - binary_sensor.template.publish:
+ id: in_use
+ state: OFF
+
+
+select:
+
+ # option to disable button
+ - platform: template
+ name: $friendly_name Button
+ id: select_button
+ optimistic: true
+ options:
+ - Enabled
+ - Disabled
+ initial_option: Enabled
+ restore_value: true
+ icon: mdi:circle-double
+ entity_category: config
+
+
+ # option to disable blue LED
+ - platform: template
+ name: $friendly_name LED
+ id: select_led
+ optimistic: true
+ entity_category: config
+ options:
+ - Enabled
+ - Disabled
+ initial_option: Enabled
+ restore_value: true
+ icon: mdi:led-on
+ on_value:
+ then:
+ - if:
+ condition:
+ lambda: 'return ( (id(select_led).state == "Enabled") && id(relay).state );'
+ then:
+ switch.turn_on: blue_led
+ else:
+ switch.turn_off: blue_led
+
+
+# Send IP Address to HA
+text_sensor:
+ - platform: wifi_info
+ ip_address:
+ name: $friendly_name IP Address
diff --git a/config-factory/webserver-v1.min.css b/config-factory/webserver-v1.min.css
new file mode 100644
index 0000000..3b4c2c5
--- /dev/null
+++ b/config-factory/webserver-v1.min.css
@@ -0,0 +1,2 @@
+/* Based off of https://github.com/sindresorhus/github-markdown-css */
+.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.5;color:#24292e;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body a{background-color:transparent}.markdown-body a:active,.markdown-body a:hover{outline-width:0}.markdown-body strong{font-weight:bolder}.markdown-body h1{font-size:2em;margin:0.67em 0}.markdown-body img{border-style:none}.markdown-body pre{font-family:monospace,monospace;font-size:1em}.markdown-body hr{box-sizing:content-box;height:0;overflow:visible}.markdown-body input{font:inherit;margin:0}.markdown-body input{overflow:visible}.markdown-body [type="checkbox"]{box-sizing:border-box;padding:0}.markdown-body *{box-sizing:border-box}.markdown-body input{font-family:inherit;font-size:inherit;line-height:inherit}.markdown-body a{color:#0366d6;text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body strong{font-weight:600}.markdown-body hr{height:0;margin:15px 0;overflow:hidden;background:transparent;border:0;border-bottom:1px solid #dfe2e5}.markdown-body hr::before{display:table;content:""}.markdown-body hr::after{display:table;clear:both;content:""}.markdown-body table{border-spacing:0;border-collapse:collapse}.markdown-body td,.markdown-body th{padding:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:0;margin-bottom:0}.markdown-body h1{font-size:32px;font-weight:600}.markdown-body h2{font-size:24px;font-weight:600}.markdown-body h3{font-size:20px;font-weight:600}.markdown-body h4{font-size:16px;font-weight:600}.markdown-body h5{font-size:14px;font-weight:600}.markdown-body h6{font-size:12px;font-weight:600}.markdown-body p{margin-top:0;margin-bottom:10px}.markdown-body blockquote{margin:0}.markdown-body ul,.markdown-body ol{padding-left:0;margin-top:0;margin-bottom:0}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ul ul ol,.markdown-body ul ol ol,.markdown-body ol ul ol,.markdown-body ol ol ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body pre{margin-top:0;margin-bottom:0;font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px}.markdown-body::before{display:table;content:""}.markdown-body::after{display:table;clear:both;content:""}.markdown-body>*:first-child{margin-top:0!important}.markdown-body>*:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre{margin-top:0;margin-bottom:16px}.markdown-body hr{height:0.25em;padding:0;margin:24px 0;background-color:#e1e4e8;border:0}.markdown-body blockquote{padding:0 1em;color:#6a737d;border-left:0.25em solid #dfe2e5}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1{padding-bottom:0.3em;font-size:2em;border-bottom:1px solid #eaecef}.markdown-body h2{padding-bottom:0.3em;font-size:1.5em;border-bottom:1px solid #eaecef}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:0.875em}.markdown-body h6{font-size:0.85em;color:#6a737d}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li{word-wrap:break-all}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:0.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}.markdown-body table{display:block;width:100%;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{padding:6px 13px;border:1px solid #dfe2e5}.markdown-body table tr{background-color:#fff;border-top:1px solid #c6cbd1}.markdown-body table tr:nth-child(2n){background-color:#f6f8fa}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:#fff}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f6f8fa;border-radius:3px;word-wrap:normal}.markdown-body:checked+.radio-label{position:relative;z-index:1;border-color:#0366d6}.markdown-body hr{border-bottom-color:#eee}#log .v{color:#888}#log .d{color:#0DD}#log .c{color:magenta}#log .i{color:limegreen}#log .w{color:yellow}#log .e{color:red;font-weight:bold}#log{background-color:#1c1c1c} \ No newline at end of file
diff --git a/config-factory/webserver-v1.min.js b/config-factory/webserver-v1.min.js
new file mode 100644
index 0000000..e2cbb4b
--- /dev/null
+++ b/config-factory/webserver-v1.min.js
@@ -0,0 +1 @@
+const source=new EventSource("/events");source.addEventListener("log",function(a){const b=document.getElementById("log");let c="";a.data.startsWith("\x1B[1;31m")?c="e":a.data.startsWith("\x1B[0;33m")?c="w":a.data.startsWith("\x1B[0;32m")?c="i":a.data.startsWith("\x1B[0;35m")?c="c":a.data.startsWith("\x1B[0;36m")?c="d":a.data.startsWith("\x1B[0;37m")?c="v":b.innerHTML+=a.data+"\n",b.innerHTML+="<span class=\""+c+"\">"+a.data.substr(7,a.data.length-10)+"</span>\n"}),source.addEventListener("state",function(a){const b=JSON.parse(a.data);document.getElementById(b.id).children[1].innerText=b.state});const states=document.getElementById("states");for(let a,b=0;a=states.rows[b];b++)a.classList.contains("switch")&&function(b){a.children[2].children[0].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/switch/"+b.substr(7)+"/toggle",!0),a.send()})}(a.id),a.classList.contains("fan")&&function(b){a.children[2].children[0].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/fan/"+b.substr(4)+"/toggle",!0),a.send()})}(a.id),a.classList.contains("light")&&function(b){a.children[2].children[0].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/light/"+b.substr(6)+"/toggle",!0),a.send()})}(a.id),a.classList.contains("cover")&&function(b){a.children[2].children[0].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/cover/"+b.substr(6)+"/open",!0),a.send()}),a.children[2].children[1].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/cover/"+b.substr(6)+"/close",!0),a.send()})}(a.id); \ No newline at end of file
diff --git a/config-update/kauf-plug-update.yaml b/config-update/kauf-plug-update.yaml
new file mode 100644
index 0000000..cffccd6
--- /dev/null
+++ b/config-update/kauf-plug-update.yaml
@@ -0,0 +1,367 @@
+substitutions:
+ friendly_name: Kauf Plug
+
+esp8266: # https://esphome.io/components/esp8266.html
+ board: esp01_1m
+ restore_from_flash: true
+
+external_components:
+ - source:
+ type: git
+ url: https://github.com/KaufHA/kauf_plf10_components
+ refresh: 0s
+ # - source:
+ # type: local
+ # path: kauf_plf10_components
+
+preferences:
+ flash_write_interval: 3s
+
+esphome:
+
+ name: kauf-plug
+ name_add_mac_suffix: true
+
+ project:
+ name: "kauf.plf10"
+ version: "1.8"
+
+ on_boot:
+ then:
+
+ # wait until ESPHome tries loading wifi credentials
+ - wait_until:
+ lambda: 'return ( wifi_wificomponent->tried_loading_creds );'
+
+
+ - if:
+ condition: # don't do anything if credentials were loaded unless force_ap is on as override
+ lambda: 'return ( wifi_wificomponent->loaded_creds && !id(force_ap) );'
+ then:
+ - logger.log: "------------------->>>>>>>>>>>>>>>>> wifi already configured, leaving ap timeout cranked"
+
+ - globals.set: # set global so if wifi never connects we'll have AP next time.
+ id: force_ap
+ value: 'true'
+
+ - wait_until:
+ wifi.connected
+ - globals.set: # clear global so we won't have AP next time
+ id: force_ap
+ value: 'false'
+
+ # if we do need AP
+ else: # if credentials not loaded or force_ap is set, lower ap timeout to allow saving new credentials
+ - logger.log: "------------------->>>>>>>>>>>>>>>>> wifi not configured, enabling ap and waiting"
+ # set ap timeout to 15 seconds in milliseconds.
+ - lambda: 'wifi_wificomponent->set_ap_timeout(15000);'
+ - wait_until:
+ wifi.connected
+ - globals.set: # clear global so ap doesn't come back up next time
+ id: force_ap
+ value: 'false'
+
+ # set ap timeout to max 32 bit value in milliseconds. About 3.5 weeks.
+ - logger.log: "------------------->>>>>>>>>>>>>>>>> wifi connected, cranking ap timeout back up"
+ - lambda: 'wifi_wificomponent->set_ap_timeout(2147483647);'
+
+globals:
+ - id: force_ap
+ type: bool
+ restore_value: yes
+ initial_value: "true"
+
+
+wifi:
+
+ # default credentials as backup. Also needed to show found networks in captive portal.
+ ssid: initial_ap2
+ password: asdfasdfasdfasdf
+
+ # use_address allows wireless programming through dashboard. remove after programming.
+ # use_address: 192.168.86.202
+
+ # default is 20, 17 is recommended.
+ output_power: 17
+
+ ap:
+ ssid: ${friendly_name} Hotspot
+ ap_timeout: 2147483647ms # default to max 32-bit value in milliseconds. About 3.5 weeks.
+
+
+captive_portal: # for fallback wifi hotspot
+
+logger: # Enable logging
+ # baud_rate: 0 # Disable UART logging since TX pad not easily available
+
+api: # Enable Home Assistant API
+ # password: !secret api_password # optional password field for Home Assistant API.
+
+ota:
+ # password: !secret ota_password # optional password for OTA updates.
+
+debug: # outputs additional debug info when logs start
+
+
+web_server: # web server allows access to device with a web browser
+ # auth: # optional login details for web interface
+ # username: admin
+ # password: !secret web_server_password
+
+ # host css and js file directly on device
+ css_include: "webserver-v1.min.css"
+ css_url: ""
+ js_include: "webserver-v1.min.js"
+ js_url: ""
+
+
+# red led, blink when not connected to WiFi or Home Assistant
+status_led:
+ pin:
+ number: GPIO0
+ inverted: true
+
+
+binary_sensor:
+
+ # button input toggles relay and thereby blue led
+ - platform: gpio
+ id: button
+ name: $friendly_name Button
+ pin:
+ number: GPIO13
+ mode: INPUT_PULLUP
+ inverted: true
+ entity_category: ''
+
+
+ on_click:
+
+ - min_length: 30ms
+ max_length: 2s
+ then:
+ - if:
+ condition: # only toggle relay if button is enabled
+ lambda: 'return (id(select_button).state == "Enabled");'
+ then:
+ switch.toggle: relay
+
+
+
+ # restart with AP enabled if button is held for 5 seconds (up to 30 seconds because ESPHome requires a max).
+ # Not disabled by button disable option because you may not be able to re-enable button if wifi is not connecting.
+ # If your kid accidentally triggers this, the plug will just reboot and reconnect to wifi. Shouldn't harm anything.
+
+ - min_length: 5s
+ max_length: 30s
+ then:
+ - logger.log: "------------------->>>>>>>>>>>>>>>>> HELD BUTTON 5 SECONDS, FORCING AP"
+
+ # blink LED for 10s then restart to get captive portal to turn on.
+ - globals.set:
+ id: force_ap
+ value: 'true'
+ - script.execute: blink_led
+ - delay: 10s
+
+ # we have to restart because ESPHome's AP won't show the captive portal once wifi has been connected.
+ - switch.turn_on: restart_switch
+
+
+ # indicates whether plugged-in device is running based on configurable threshold.
+ - platform: template
+ id: in_use
+ name: ${friendly_name} Device In Use
+
+
+script:
+ # 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
+ # clear wifi credentials and reboot. causes AP to be enabled.
+ - id: clear_wifi_script
+ then:
+ - lambda: 'wifi_wificomponent->clear_stored_creds();'
+ - delay: 2s
+ - switch.turn_on: restart_switch
+
+switch:
+
+ # blue LED follows relay power state
+ - platform: gpio
+ id: blue_led
+ pin:
+ number: GPIO2
+ inverted: true
+
+ # relay output
+ - platform: gpio
+ id: relay
+ name: $friendly_name
+ pin: GPIO4
+ entity_category: ''
+
+ # automatically make blue led equal relay state
+ on_turn_on:
+ - if:
+ condition: # only if blue LED enabled
+ lambda: 'return (id(select_led).state == "Enabled");'
+ then:
+ switch.turn_on: blue_led
+
+ on_turn_off:
+ - switch.turn_off: blue_led
+
+ - platform: restart
+ id: restart_switch
+ name: $friendly_name Restart Firmware
+ entity_category: diagnostic
+ disabled_by_default: true
+
+ - platform: template
+ id: clear_wifi_switch
+ name: $friendly_name Clear WiFi Credentials
+ optimistic: true
+ entity_category: diagnostic
+ disabled_by_default: true
+# restore_state: no # no is default already per https://esphome.io/components/switch/template.html
+ on_turn_on:
+ then:
+ script.execute: clear_wifi_script
+
+
+# clock input from Home Assistant used to calculate total daily energy
+time:
+ - platform: homeassistant
+ id: homeassistant_time
+
+
+sensor: # Power monitoring sensors output to Home Assistant
+ - platform: hlw8012
+ sel_pin:
+ number: GPIO12
+ inverted: True
+ cf_pin: GPIO5
+ cf1_pin: GPIO14
+ current_resistor: 0.001 # The value of the shunt resistor for current measurement.
+ voltage_divider: 2401 # The value of the voltage divider on the board as (R_upstream + R_downstream) / R_downstream.
+ power:
+ name: ${friendly_name} Power
+ unit_of_measurement: W
+ id: wattage
+ filters:
+ - calibrate_linear:
+ - 0.0 -> 0.0
+ - 333.8 -> 60 # value with 60W bulb.
+ on_value: # set or clear in_use template binary sensor depending on whether power usage is over threshold
+ - if:
+ condition:
+ lambda: 'return (x >= id(threshold).state);'
+ then:
+ - binary_sensor.template.publish:
+ id: in_use
+ state: ON
+ else:
+ - binary_sensor.template.publish:
+ id: in_use
+ state: OFF
+ current:
+ name: ${friendly_name} Current
+ unit_of_measurement: A
+ filters:
+ - calibrate_linear:
+ - 0.0 -> 0.0
+ - 0.6 -> 0.515 # value with 60W bulb.
+ voltage:
+ name: ${friendly_name} Voltage
+ unit_of_measurement: V
+ filters:
+ - calibrate_linear:
+ - 0.0 -> 0.0
+ - 302.1 -> 117.1 # Tested using a meter
+ change_mode_every: 1
+ update_interval: 10s # 20 second effective update rate for Power, 40 second for Current and Voltage.
+
+# Reports the total Power so-far each day, resets at midnight
+# See 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
+
+
+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 # required for changing value from home assistant
+ restore_value: true
+ on_value:
+ - if: # set or clear in_use template binary sensor depending on whether power usage is above threshold
+ condition:
+ lambda: 'return (id(wattage).state >= x);'
+ then:
+ - binary_sensor.template.publish:
+ id: in_use
+ state: ON
+ else:
+ - binary_sensor.template.publish:
+ id: in_use
+ state: OFF
+
+
+select:
+
+ # option to disable button
+ - platform: template
+ name: $friendly_name Button
+ id: select_button
+ optimistic: true
+ options:
+ - Enabled
+ - Disabled
+ initial_option: Enabled
+ restore_value: true
+ icon: mdi:circle-double
+ entity_category: config
+
+
+ # option to disable blue LED
+ - platform: template
+ name: $friendly_name LED
+ id: select_led
+ optimistic: true
+ entity_category: config
+ options:
+ - Enabled
+ - Disabled
+ initial_option: Enabled
+ restore_value: true
+ icon: mdi:led-on
+ on_value:
+ then:
+ - if:
+ condition:
+ lambda: 'return ( (id(select_led).state == "Enabled") && id(relay).state );'
+ then:
+ switch.turn_on: blue_led
+ else:
+ switch.turn_off: blue_led
+
+
+# Send IP Address to HA
+text_sensor:
+ - platform: wifi_info
+ ip_address:
+ name: $friendly_name IP Address
diff --git a/config-update/webserver-v1.min.css b/config-update/webserver-v1.min.css
new file mode 100644
index 0000000..3b4c2c5
--- /dev/null
+++ b/config-update/webserver-v1.min.css
@@ -0,0 +1,2 @@
+/* Based off of https://github.com/sindresorhus/github-markdown-css */
+.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.5;color:#24292e;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body a{background-color:transparent}.markdown-body a:active,.markdown-body a:hover{outline-width:0}.markdown-body strong{font-weight:bolder}.markdown-body h1{font-size:2em;margin:0.67em 0}.markdown-body img{border-style:none}.markdown-body pre{font-family:monospace,monospace;font-size:1em}.markdown-body hr{box-sizing:content-box;height:0;overflow:visible}.markdown-body input{font:inherit;margin:0}.markdown-body input{overflow:visible}.markdown-body [type="checkbox"]{box-sizing:border-box;padding:0}.markdown-body *{box-sizing:border-box}.markdown-body input{font-family:inherit;font-size:inherit;line-height:inherit}.markdown-body a{color:#0366d6;text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body strong{font-weight:600}.markdown-body hr{height:0;margin:15px 0;overflow:hidden;background:transparent;border:0;border-bottom:1px solid #dfe2e5}.markdown-body hr::before{display:table;content:""}.markdown-body hr::after{display:table;clear:both;content:""}.markdown-body table{border-spacing:0;border-collapse:collapse}.markdown-body td,.markdown-body th{padding:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:0;margin-bottom:0}.markdown-body h1{font-size:32px;font-weight:600}.markdown-body h2{font-size:24px;font-weight:600}.markdown-body h3{font-size:20px;font-weight:600}.markdown-body h4{font-size:16px;font-weight:600}.markdown-body h5{font-size:14px;font-weight:600}.markdown-body h6{font-size:12px;font-weight:600}.markdown-body p{margin-top:0;margin-bottom:10px}.markdown-body blockquote{margin:0}.markdown-body ul,.markdown-body ol{padding-left:0;margin-top:0;margin-bottom:0}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ul ul ol,.markdown-body ul ol ol,.markdown-body ol ul ol,.markdown-body ol ol ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body pre{margin-top:0;margin-bottom:0;font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px}.markdown-body::before{display:table;content:""}.markdown-body::after{display:table;clear:both;content:""}.markdown-body>*:first-child{margin-top:0!important}.markdown-body>*:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre{margin-top:0;margin-bottom:16px}.markdown-body hr{height:0.25em;padding:0;margin:24px 0;background-color:#e1e4e8;border:0}.markdown-body blockquote{padding:0 1em;color:#6a737d;border-left:0.25em solid #dfe2e5}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1{padding-bottom:0.3em;font-size:2em;border-bottom:1px solid #eaecef}.markdown-body h2{padding-bottom:0.3em;font-size:1.5em;border-bottom:1px solid #eaecef}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:0.875em}.markdown-body h6{font-size:0.85em;color:#6a737d}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li{word-wrap:break-all}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:0.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}.markdown-body table{display:block;width:100%;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{padding:6px 13px;border:1px solid #dfe2e5}.markdown-body table tr{background-color:#fff;border-top:1px solid #c6cbd1}.markdown-body table tr:nth-child(2n){background-color:#f6f8fa}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:#fff}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f6f8fa;border-radius:3px;word-wrap:normal}.markdown-body:checked+.radio-label{position:relative;z-index:1;border-color:#0366d6}.markdown-body hr{border-bottom-color:#eee}#log .v{color:#888}#log .d{color:#0DD}#log .c{color:magenta}#log .i{color:limegreen}#log .w{color:yellow}#log .e{color:red;font-weight:bold}#log{background-color:#1c1c1c} \ No newline at end of file
diff --git a/config-update/webserver-v1.min.js b/config-update/webserver-v1.min.js
new file mode 100644
index 0000000..e2cbb4b
--- /dev/null
+++ b/config-update/webserver-v1.min.js
@@ -0,0 +1 @@
+const source=new EventSource("/events");source.addEventListener("log",function(a){const b=document.getElementById("log");let c="";a.data.startsWith("\x1B[1;31m")?c="e":a.data.startsWith("\x1B[0;33m")?c="w":a.data.startsWith("\x1B[0;32m")?c="i":a.data.startsWith("\x1B[0;35m")?c="c":a.data.startsWith("\x1B[0;36m")?c="d":a.data.startsWith("\x1B[0;37m")?c="v":b.innerHTML+=a.data+"\n",b.innerHTML+="<span class=\""+c+"\">"+a.data.substr(7,a.data.length-10)+"</span>\n"}),source.addEventListener("state",function(a){const b=JSON.parse(a.data);document.getElementById(b.id).children[1].innerText=b.state});const states=document.getElementById("states");for(let a,b=0;a=states.rows[b];b++)a.classList.contains("switch")&&function(b){a.children[2].children[0].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/switch/"+b.substr(7)+"/toggle",!0),a.send()})}(a.id),a.classList.contains("fan")&&function(b){a.children[2].children[0].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/fan/"+b.substr(4)+"/toggle",!0),a.send()})}(a.id),a.classList.contains("light")&&function(b){a.children[2].children[0].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/light/"+b.substr(6)+"/toggle",!0),a.send()})}(a.id),a.classList.contains("cover")&&function(b){a.children[2].children[0].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/cover/"+b.substr(6)+"/open",!0),a.send()}),a.children[2].children[1].addEventListener("click",function(){const a=new XMLHttpRequest;a.open("POST","/cover/"+b.substr(6)+"/close",!0),a.send()})}(a.id); \ No newline at end of file
diff --git a/kauf-plug.yaml b/kauf-plug.yaml
index 77979b3..0d82744 100644
--- a/kauf-plug.yaml
+++ b/kauf-plug.yaml
@@ -3,48 +3,49 @@ substitutions:
# **** CHANGE FRIENDLY NAME TO SOMETHING UNIQUE PER DEVICE ****
friendly_name: Kauf Plug
+esp8266: # https://esphome.io/components/esp8266.html
+ board: esp01_1m
+ restore_from_flash: true
-esphome:
-
- # **** CHANGE DEVICE NAME TO SOMETHING UNIQUE PER DEVICE ****
- name: kauf-plug # **** RENAME YAML FILE TO SAME NAME ****
- platform: ESP8266
- board: esp01_1m
+esphome:
- esp8266_restore_from_flash: true
+ 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. ****
project:
name: "kauf.plf10"
- version: "1.7"
+ version: "1.8"
wifi:
# **** ENTER WI-FI CREDENTIALS HERE, USING SECRETS.YAML RECOMMENDED ****
- ssid: initial_ap2 # !secret my_wifi_ssid
- password: asdfasdfasdfasdf # !secret my_wifi_pass
-
+ ssid: initial_ap2 # !secret wifi_ssid
+ password: asdfasdfasdfasdf # !secret wifi_pass
+
# Delete the following line to acknowledge that you know that you must enter your home network's
# wi-fi credentials in the preceding two fields. If for whatever reason the credentials are typed
# in wrong you will have to set up a wifi network with the entered credentials to recover your plug.
you_must_enter_wifi_credentials_above: true
+
# Uncomment below to set a static IP
# manual_ip:
- # static_ip: !secret kauf_bulb_ip_address
+ # static_ip: !secret kauf_bulb_rgb_ip_address
# gateway: !secret wifi_gateway
# subnet: !secret wifi_subnet
# dns1: !secret wifi_dns1
- # use_address allows wireless programming through the ESPHome dashboard.
- # Set to the plug's IP Address. Remove after programming.
- # use_address: 192.168.1.3
+ # use_address allows wireless programming through dashboard.
+ # Set to the bulb's IP Address. Remove after programming.
+ # use_address: 192.168.86.244
# default is 20, 17 is recommended.
output_power: 17
+
logger: # Enable logging
# baud_rate: 0 # Disable UART logging since TX pad not easily available
@@ -56,14 +57,15 @@ ota:
debug: # outputs additional debug info when logs start
-
web_server: # web server allows access to device with a web browser
# auth: # optional login details for web interface
# username: admin
# password: !secret web_server_password
-# red led, blink when not connected to wifi or Home Assistant
+
+
+# red led, blink when not connected to WiFi or Home Assistant
status_led:
pin:
number: GPIO0
@@ -75,26 +77,30 @@ binary_sensor:
# button input toggles relay and thereby blue led
- platform: gpio
id: button
- name: Kauf Plug Button
+ name: $friendly_name Button
pin:
number: GPIO13
mode: INPUT_PULLUP
inverted: true
+ entity_category: ''
+
on_press:
- - if:
- condition: # only toggle relay if button is enabled
- lambda: 'return (id(select_button).state == "Enabled");'
then:
- switch.toggle: relay
+ - if:
+ condition: # only toggle relay if button is enabled
+ lambda: 'return (id(select_button).state == "Enabled");'
+ then:
+ switch.toggle: relay
- # indicates whether plugged-in devices is running based on configurable threshold.
+ # indicates whether plugged-in device is running based on configurable threshold.
- platform: template
id: in_use
name: ${friendly_name} Device In Use
+
switch:
# blue LED follows relay power state
@@ -109,7 +115,8 @@ switch:
id: relay
name: $friendly_name
pin: GPIO4
-
+ entity_category: ''
+
# automatically make blue led equal relay state
on_turn_on:
- if:
@@ -121,6 +128,12 @@ switch:
on_turn_off:
- switch.turn_off: blue_led
+ - platform: restart
+ id: restart_switch
+ name: $friendly_name Restart Firmware
+ entity_category: diagnostic
+ disabled_by_default: true
+
# clock input from Home Assistant used to calculate total daily energy
time:
@@ -192,6 +205,7 @@ number: # used as a threshold for whether the plugged-in devices is running
step: 1
initial_value: 3
id: threshold
+ entity_category: config
optimistic: true # required for changing value from home assistant
restore_value: true
on_value:
@@ -212,7 +226,7 @@ select:
# option to disable button
- platform: template
- name: "Kauf Plug Button"
+ name: $friendly_name Button
id: select_button
optimistic: true
options:
@@ -221,13 +235,15 @@ select:
initial_option: Enabled
restore_value: true
icon: mdi:circle-double
+ entity_category: config
# option to disable blue LED
- platform: template
- name: "Kauf Plug LED"
+ name: $friendly_name LED
id: select_led
optimistic: true
+ entity_category: config
options:
- Enabled
- Disabled