Skip to content

Instantly share code, notes, and snippets.

@buglloc
Last active October 3, 2023 11:14
Show Gist options
  • Save buglloc/3ea4cf04a89e318fb58cc6d86886bbe0 to your computer and use it in GitHub Desktop.
Save buglloc/3ea4cf04a89e318fb58cc6d86886bbe0 to your computer and use it in GitHub Desktop.
WasherNotifier
## Perform actions when an appliance has finished
##
## This blueprint is my improved version of an original blueprint by @sbyx:
##
## Original blueprint:
## https://gist.github.com/sbyx/6d8344d3575c9865657ac51915684696
##
## Community thread:
## https://community.home-assistant.io/t/notify-or-do-something-when-an-appliance-like-a-dishwasher-or-washing-machine-finishes/254841
##
## Changelog
## ~~~~~~~~~
## - v1.0.1 (2022-09-18)
## - Add instructions how to skip (pre) actions.
## - v1.0 (2022-09-02)
## - Initial release
## - Hysteresis presented as a duration selector.
## (https://www.home-assistant.io/docs/blueprint/selectors/#duration-selector)
## - Entities limited to power sensors by device class
## (https://www.home-assistant.io/docs/blueprint/selectors/#device_class)
##
blueprint:
name: Appliance has finished
description: Perform one or more actions when an appliance
(like a washing machine or dishwasher)
has started and finished as detected by a power sensor.
source_url: https://github.com/metbril/home-assistant-blueprints/blob/main/automation/appliance_has_finished.yaml
domain: automation
input:
power_sensor:
name: Power Sensor
description: Power sensor entity (e.g. from a smart plug device).
selector:
entity:
domain: sensor
device_class: power
multiple: false
starting_threshold:
name: Starting power threshold
description: Power threshold above which we assume the appliance has started.
default: 5
selector:
number:
min: 1.0
max: 100.0
step: 1.0
unit_of_measurement: W
mode: slider
starting_hysteresis:
name: Starting hysteresis
description: Time duration the power measurement has to stay above the starting
power threshold.
default:
minutes: 5
selector:
duration:
finishing_threshold:
name: Finishing power threshold
description: Power threshold below which we assume the appliance has finished.
default: 5
selector:
number:
min: 1.0
max: 100.0
unit_of_measurement: W
mode: slider
step: 1.0
finishing_hysteresis:
name: Finishing hysteresis
description: Time duration the power measurement has to stay below the finishing
power threshold.
default:
minutes: 5
selector:
duration:
actions:
name: Actions
description: Actions (e.g. pushing a notification, TTS announcement, ...)
To skip, enter a 'Wait for delay' action of `0` seconds.
selector:
action: {}
pre_actions:
name: Actions
description: Actions when starting threshhold is crossed.
To skip, enter a 'Wait for delay' action of `0` seconds.
selector:
action: {}
trigger:
- platform: numeric_state
entity_id: !input power_sensor
for:
!input starting_hysteresis
above: !input starting_threshold
condition: []
action:
- choose: []
default: !input pre_actions
- wait_for_trigger:
- platform: numeric_state
entity_id: !input power_sensor
below: !input finishing_threshold
for:
!input finishing_hysteresis
- choose: []
default: !input actions
mode: single
max_exceeded: silent
esphome:
name: washeresp
friendly_name: WasherESP
esp32:
board: esp32-c3-devkitm-1
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "DEADBEEF"
ota:
password: "deadbeef"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
manual_ip:
static_ip: 10.11.4.3
gateway: 10.11.0.1
subnet: 255.255.0.0
dns1: 10.11.1.2
# I²C Bus необходим, т.к. к нему подключается vl53l0x
i2c:
sda: GPIO9
scl: GPIO10
id: bus
scan: true
# Не люблю много думать о времени, потому всегда использую Home Assistant Time Source
# doc: https://esphome.io/components/time/homeassistant.html
time:
- platform: homeassistant
id: homeassistant_time
# Status LED Light для того чтобы понимать текущее состояние
# doc: https://esphome.io/components/light/status_led
status_led:
pin:
number: GPIO8
inverted: true
switch:
# Restart Switch, чтобы мочь рестартить MCU из HA
# doc: https://esphome.io/components/switch/restart.html
- platform: restart
name: Restart
binary_sensor:
# Status Binary Sensor, дабы отрисовывать аптайм
# doc: https://esphome.io/components/binary_sensor/status.html
- platform: status
name: Status
# С помощью template перенесем логику обнаружения непосредственно на ESP
# doc: https://esphome.io/components/binary_sensor/template.html
- platform: template
id: washer_window_opened
publish_initial_state: true
name: "Washer Window"
device_class: window
sensor:
# WiFi Signal Sensor, дабы лучше понимать что с WiFi
# doc: https://esphome.io/components/sensor/wifi_signal.html
- platform: wifi_signal
name: "WiFi Signal Strength"
update_interval: 60s
# VL53L0X Time Of Flight Distance Sensor, собсно ради него весь и сыр-бор
# doc: https://esphome.io/components/sensor/vl53l0x
- platform: vl53l0x
name: "Washer Door Distance"
id: washer_door_distance
address: 0x29
update_interval: 5s
long_range: false
unit_of_measurement: "m"
internal: true
filters:
- lambda: !lambda |-
// считаем дверцу открытой, если растояние до нее менее 20 см
bool isOpened = x <= 0.20;
// дальше меняем состояние бинарного сенсора "washer_window_opened"
bool curState = id(washer_window_opened).state;
if (isOpened != curState) {
id(washer_window_opened).publish_state(isOpened);
}
return {};
esphome:
name: dev01
friendly_name: Dev01
esp32:
board: esp32dev
framework:
type: arduino
api:
encryption:
key: "DEADBEEF"
ota:
password: "deadbeef"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
manual_ip:
static_ip: 10.11.9.1
gateway: 10.11.0.1
subnet: 255.255.0.0
dns1: 10.11.1.2
# Больше логов богу логов
logger:
# Не люблю много думать о времени, потому всегда использую Home Assistant Time Source
# doc: https://esphome.io/components/time/homeassistant.html
time:
- platform: homeassistant
id: homeassistant_time
sensor:
# WiFi Signal Sensor, дабы лучше понимать что с WiFi
# doc: https://esphome.io/components/sensor/wifi_signal.html
- platform: wifi_signal
name: "WiFi Signal Strength"
update_interval: 60s
# Pulse Counter Sensor с медианой, т.к. у меня стиральная машина стоит на балконе, а там бывает и кот и птицы и люди, хотелось не реагировать на случайные срабатывания
# doc: https://esphome.io/components/sensor/pulse_counter.html
# doc: https://esphome.io/components/sensor/#median
- platform: pulse_counter
name: "Washer vibration pulse rate"
internal: false
id: vibration_pulse_rate
icon: mdi:vibrate
update_interval: 250ms
pin:
number: GPIO34
mode: input
filters:
- median:
window_size: 120 # скользяще окно около 30 секунд
send_every: 4 # отправляем раз в секунду
send_first_at: 3
binary_sensor:
# Status Binary Sensor, дабы отрисовывать аптайм
# doc: https://esphome.io/components/binary_sensor/status.html
- platform: status
name: Status
# Template Binary Sensor, который будет зажигать лампочку на основе ответа лямбды. Так же устанавливаем ему "delayed_on_off" для минимизации фолзов
# doc: https://esphome.io/components/binary_sensor/template.html
# doc: https://esphome.io/components/binary_sensor/index.html#delayed-on-off
- platform: template
name: "Washer Running"
device_class: vibration
filters:
- delayed_on_off:
time_on: 20ms
time_off: 5min # сглаживаем отпутствие вибрации
lambda: |-
return id(vibration_pulse_rate).state >= 240;
# Restart Switch, чтобы мочь рестартить MCU из HA
# doc: https://esphome.io/components/switch/restart.html
switch:
- platform: restart
name: Restart
[
{
"id": "054ec8d5ab79c60a",
"type": "tab",
"label": "Washer notifier",
"disabled": false,
"info": "",
"env": []
},
{
"id": "a46f08dc0b249efa",
"type": "api-call-service",
"z": "054ec8d5ab79c60a",
"name": "Phone notify",
"server": "34a16949.b02c86",
"version": 5,
"debugenabled": false,
"domain": "notify",
"service": "mobile_app_xiphone",
"areaId": [],
"deviceId": [],
"entityId": [],
"data": "{\"title\":\"Твоя стиралочка\",\"message\":\"Д-О-С-Т-И-Р-А-ЛА\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"x": 890,
"y": 120,
"wires": [
[]
]
},
{
"id": "ee7b86592f593a4d",
"type": "server-events",
"z": "054ec8d5ab79c60a",
"name": "Wait washer state",
"server": "34a16949.b02c86",
"version": 2,
"eventType": "washer_state",
"exposeToHomeAssistant": false,
"eventData": "",
"haConfig": [
{
"property": "name",
"value": ""
},
{
"property": "icon",
"value": ""
}
],
"waitForRunning": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
}
],
"event_type": "",
"x": 250,
"y": 120,
"wires": [
[
"62ab59d23bdf9789"
]
]
},
{
"id": "9cc43855da8199d9",
"type": "switch",
"z": "054ec8d5ab79c60a",
"name": "Should ignore?",
"property": "payload.ignore",
"propertyType": "msg",
"rules": [
{
"t": "false"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 640,
"y": 100,
"wires": [
[
"d8c6bdc4ed04ef3f",
"526b285468c6f9e4",
"8e61c053f50d9ce4",
"a46f08dc0b249efa"
],
[
"3c3c2d95fa9b230d"
]
]
},
{
"id": "15308a2045f375f7",
"type": "inject",
"z": "054ec8d5ab79c60a",
"name": "Trigger washer done",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "1",
"topic": "",
"payload": "{\"event\":{\"state\":\"done\"}}",
"payloadType": "json",
"x": 250,
"y": 80,
"wires": [
[
"62ab59d23bdf9789"
]
]
},
{
"id": "f99d79efc64c3f7f",
"type": "inject",
"z": "054ec8d5ab79c60a",
"name": "Trigger window open",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "1",
"topic": "",
"payload": "{}",
"payloadType": "json",
"x": 240,
"y": 220,
"wires": [
[
"146ba045f2884829"
]
]
},
{
"id": "21a347b40c580863",
"type": "trigger-state",
"z": "054ec8d5ab79c60a",
"name": "Washer window",
"server": "34a16949.b02c86",
"version": 2,
"exposeToHomeAssistant": false,
"haConfig": [
{
"property": "name",
"value": ""
},
{
"property": "icon",
"value": ""
}
],
"entityid": "binary_sensor.washeresp_washer_window",
"entityidfiltertype": "exact",
"debugenabled": false,
"constraints": [
{
"targetType": "this_entity",
"targetValue": "",
"propertyType": "previous_state",
"propertyValue": "old_state.state",
"comparatorType": "is",
"comparatorValueDatatype": "bool",
"comparatorValue": "false"
},
{
"targetType": "this_entity",
"targetValue": "",
"propertyType": "current_state",
"propertyValue": "new_state.state",
"comparatorType": "is",
"comparatorValueDatatype": "bool",
"comparatorValue": "true"
}
],
"inputs": 0,
"outputs": 2,
"customoutputs": [],
"outputinitially": false,
"state_type": "habool",
"enableInput": false,
"x": 260,
"y": 260,
"wires": [
[
"146ba045f2884829"
],
[]
]
},
{
"id": "146ba045f2884829",
"type": "function",
"z": "054ec8d5ab79c60a",
"name": "Cache && format",
"func": "flow.set(\"door_opened_at\", Math.round(Date.now() / 1000));\n\nmsg.payload = {};\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 550,
"y": 240,
"wires": [
[
"526b285468c6f9e4",
"d60d356fc395856e"
]
]
},
{
"id": "8e61c053f50d9ce4",
"type": "mqtt out",
"z": "054ec8d5ab79c60a",
"name": "Clock notify",
"topic": "awtrix/notify",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "346df2a95aac5785",
"x": 890,
"y": 180,
"wires": []
},
{
"id": "526b285468c6f9e4",
"type": "debug",
"z": "054ec8d5ab79c60a",
"name": "Clock dbg",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 890,
"y": 240,
"wires": []
},
{
"id": "d8c6bdc4ed04ef3f",
"type": "debug",
"z": "054ec8d5ab79c60a",
"name": "Phone dbg",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 890,
"y": 80,
"wires": []
},
{
"id": "62ab59d23bdf9789",
"type": "function",
"z": "054ec8d5ab79c60a",
"name": "Check && format",
"func": "const windowOpenedAt = flow.get(\"door_opened_at\") || 0;\nconst secondsSinceLastOpen = Math.round(Date.now() / 1000) - windowOpenedAt;\nif (secondsSinceLastOpen <= 10) {\n msg.payload = {\n ignore: true,\n windowOpenedAt: windowOpenedAt,\n secondsSinceLastOpen: secondsSinceLastOpen,\n };\n return msg;\n}\n\nmsg.payload = {\n ignore: false,\n text: \"done!!!\",\n gradient: [[255,255,0],[255,0,0]],\n textCase: 1,\n icon: 17544,\n stack: false,\n hold: true,\n};\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 450,
"y": 100,
"wires": [
[
"9cc43855da8199d9"
]
]
},
{
"id": "3c3c2d95fa9b230d",
"type": "debug",
"z": "054ec8d5ab79c60a",
"name": "Sportloto",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 640,
"y": 140,
"wires": []
},
{
"id": "d60d356fc395856e",
"type": "mqtt out",
"z": "054ec8d5ab79c60a",
"name": "Clock dismiss",
"topic": "awtrix/notify/dismiss",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "346df2a95aac5785",
"x": 900,
"y": 280,
"wires": []
},
{
"id": "34a16949.b02c86",
"type": "server",
"name": "Home Assistant",
"addon": true
},
{
"id": "346df2a95aac5785",
"type": "mqtt-broker",
"name": "haas",
"broker": "10.11.0.1",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment