diff options
-rw-r--r-- | config-factory/kauf-plug-factory.yaml | 438 | ||||
-rw-r--r-- | config-factory/webserver-v1.min.css | 2 | ||||
-rw-r--r-- | config-factory/webserver-v1.min.js | 1 | ||||
-rw-r--r-- | config-update/kauf-plug-update.yaml | 367 | ||||
-rw-r--r-- | config-update/webserver-v1.min.css | 2 | ||||
-rw-r--r-- | config-update/webserver-v1.min.js | 1 | ||||
-rw-r--r-- | kauf-plug.yaml | 68 |
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 |