1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
| import appdaemon.plugins.hass.hassapi as hass
import const
import math
import time
import datetime
from dateutil import parser
from datetime import timedelta
from datetime import timezone
class VaatwasserApp(hass.Hass):
def initialize(self):
# MQTT
self.mqtt = self.get_plugin_api("MQTT")
# Config
self.devname = "vaatwasser"
self.powerthreshold = 1.0
self.delay = 300
# Add time restrictions to devices
self.allowedStartHour = None
self.allowedEndHour = None # Including, so 22 means 22:59 is also allowed!
self.timestep = 900
# Profiles in Watts
# Vaatwasser = 1.3kWh
self.profile = [200, 1704, 1139, 66, 44, 1592, 462, 4, 5]
# State
self.running = False
self.waiting = False
self.smartstarttimer = None
self.energy = 0
self.costs = 0
self.co2 = 0
self.delaytimer = None
self.finishtimer = None
# Listeners
self.listen_state(self.changed_power, "sensor.stopcontact_"+self.devname+"_power")
self.listen_state(self.changed_energy, "sensor.stopcontact_"+self.devname+"_energy")
self.listen_state(self.changed_powerswitch, "switch.stopcontact_"+self.devname, old="off", new="on")
self.listen_state(self.changed_datetime, "input_datetime."+self.devname+"_starttijd")
self.listen_state(self.click_button_price, "input_button."+self.devname+"_goedkoop")
self.listen_state(self.click_button_co2, "input_button."+self.devname+"_co2")
self.listen_state(self.click_button_clever, "input_button."+self.devname+"_clever")
# Initial state
self.set_state("sensor."+self.devname+"_plan_status", state="Inactief")
if float(self.get_state("sensor.stopcontact_"+self.devname+"_power")) >= self.powerthreshold:
self.running = True
self.set_state("sensor."+self.devname+"_plan_status", state="Actief")
# Optimal scheduling
self.run_in(self.calculate, 10)
# Restore
if float(self.get_state("sensor.stopcontact_"+self.devname+"_power")) >= self.powerthreshold:
self.running = True
self.set_state("sensor."+self.devname+"_plan_status", state="Actief")
def changed_power(self, entity, attribute, old, new, kwargs):
# First check the smart start and disable the device if needed
smartstart = self.get_state("input_boolean."+self.devname+"_smartstart") == "on"
smarttime = int(self.get_state("input_datetime."+self.devname+"_starttijd", attribute="timestamp"))
thistime = int(time.time())
# Check if we should delay the start
if smartstart and not thistime >= smarttime-10 and not self.waiting and self.get_state(const.auto) == "on":
# We should turn off the device if it is consuming
if float(new) >= self.powerthreshold:
# This is the only way to turn off with smart start due to the initial power peak when setting a mode
# This will be triggered after 15 seconds. Hence, we have 15 seconds to set to turn on the dishwasher, set the mode, and press start
self.run_in(self.smart_turn_off, 15)
# Else, we should be running and this code should do the accounting + notification trigger
else:
# Device is running
if float(new) >= self.powerthreshold:
# Make sure we don't get false notifications
if self.finishtimer is not None:
try:
self.cancel_timer(self.finishtimer)
self.finishtimer = None
except:
pass
# Reset accounting triggers
if not self.running:
# Reset the defaults
self.running = True
self.waiting = False
self.energy = 0
self.costs = 0
self.co2 = 0
# Calculate the end time:
et = datetime.datetime.fromtimestamp(int(time.time())+len(self.profile)*self.timestep)
s = "Actief - Klaar rond "+et.strftime('%H:%M')
self.set_state("sensor."+self.devname+"_plan_status", state=s)
# Device seems to be idle
# Activate the delay if needed
else:
if self.running and self.finishtimer is None:
self.finishtimer = self.run_in(self.timer_finish, self.delay)
# This is the only way to turn off with smart start due to the initial power peak when setting a mode
# This will be triggered after 30 seconds. Hence, we have 30 seconds to set to turn on the dishwasher, set the mode, and press start
def smart_turn_off(self, kwargs):
# First check the smart start and disable the device if needed
smartstart = self.get_state("input_boolean."+self.devname+"_smartstart") == "on"
smarttime = int(self.get_state("input_datetime."+self.devname+"_starttijd", attribute="timestamp"))
thistime = int(time.time())
# Check if we should delay the start
if smartstart and not thistime >= smarttime and not self.waiting and self.get_state(const.auto) == "on":
# Set the waiting state
self.waiting = True
self.running = False
self.turn_off("switch.stopcontact_"+self.devname)
# Set a message in the interface:
s = "Uitgesteld met Smart Start"
self.set_state("sensor."+self.devname+"_plan_status", state=s)
# Set the timer for the smart start
st = self.get_state("input_datetime."+self.devname+"_starttijd")
self.smartstarttimer = self.run_at(self.delayed_start, st)
# Activate after delayed start
def delayed_start(self, kwargs):
smartstart = self.get_state("input_boolean."+self.devname+"_smartstart") == "on"
smarttime = int(self.get_state("input_datetime."+self.devname+"_starttijd", attribute="timestamp"))
thistime = int(time.time())
# Check if we should start
if smartstart and self.get_state(const.auto) == "on":
self.waiting = False
self.smartstarttimer = None
# Start the device
self.turn_on("switch.stopcontact_"+self.devname)
# And push a notification
self.call_service("notify/mobile_app_smartphone", message="De "+self.devname+" start nu door middel van Smart Start", title="Witgoed")
# Cleanup if we decide to flick the switch now
def changed_powerswitch(self, entity, attribute, old, new, kwargs):
try:
self.waiting = False
if self.smartstarttimer is not None:
self.cancel_timer(self.smartstarttimer)
self.smartstarttimer = None
except:
pass
# Monitor changes in datetime when the device is waiting
def changed_datetime(self, entity, attribute, old, new, kwargs):
smartstart = self.get_state("input_boolean."+self.devname+"_smartstart") == "on"
smarttime = int(self.get_state("input_datetime."+self.devname+"_starttijd", attribute="timestamp"))
thistime = int(time.time())
# Sanity check, if the datetime is in history we do no nothing
if smartstart and self.waiting and self.smartstarttimer is not None and self.get_state(const.auto) == "on":
if thistime < smarttime:
try:
if self.smartstarttimer is not None:
self.cancel_timer(self.smartstarttimer)
self.smartstarttimer = None
except:
pass
# Change the timer
st = self.get_state("input_datetime."+self.devname+"_starttijd")
self.smartstarttimer = self.run_at(self.delayed_start, st)
# Monitor the energy usage of this run
def changed_energy(self, entity, attribute, old, new, kwargs):
if self.running:
price = float(self.get_state("sensor.zonneplan_current_electricity_tariff"))
co2 = float(self.get_state("sensor.zonneplan_current_co2"))
diff = float(new)-float(old)
self.energy += diff
self.costs += diff*price
self.co2 += diff*co2
# Finish notification
def timer_finish(self, kwargs):
if self.running and float(self.get_state("sensor.stopcontact_"+self.devname+"_power")) < self.powerthreshold:
self.running = False
# Set the stats
s = "{:.2f} kWh".format(self.energy)
s = s.replace(".", ",")
a = "€ {:.2f} en {:d} gCO2".format(self.costs, int(round(self.co2)))
a = a.replace(".", ",")
self.mqtt.mqtt_publish("homeassistant/sensor/"+self.devname+"/last_run", s, retain=True)
self.mqtt.mqtt_publish("homeassistant/sensor/"+self.devname+"/last_run/info", a, retain=True)
# Create the message
msg = "De "+self.devname+" is klaar! Statistieken: {:.2f} kWh verbruikt voor in totaal € {:.2f} met {:d} gCO2 uitstoot.".format(self.energy, self.costs, int(round(self.co2)))
# Send out the message
rl = int(float(self.get_state("input_number.runlevel")))
self.call_service("notify/mobile_app_smartphone", message=msg, title="Witgoed")
if rl >= const.rlHome:
self.call_service("tts/google_translate_say", entity_id = "media_player.gc_huiskamer", message="De "+self.devname+" is klaar!")
self.call_service("tts/google_translate_say", entity_id = "media_player.gc_keuken", message="De "+self.devname+" is klaar!")
self.call_service("tts/google_translate_say", entity_id = "media_player.gc_studeerkamer", message="De "+self.devname+" is klaar!")
# Set the state
self.set_state("sensor."+self.devname+"_plan_status", state="Klaar")
self.finishtimer = self.run_in(self.timer_idle, 7200)
# Reset
self.energy = 0
self.costs = 0
self.co2 = 0
# Timer to reset the device description to idle again
def timer_idle(self, kwargs):
if not self.running:
self.set_state("sensor."+self.devname+"_plan_status", state="Inactief")
# Helpers to set the time
def click_button_price(self, entity, attribute, old, new, kwargs):
dt = self.get_state("sensor."+self.devname+"_plan_price_timestamp")
self.call_service("input_datetime/set_datetime", entity_id="input_datetime."+self.devname+"_starttijd", timestamp=dt)
def click_button_co2(self, entity, attribute, old, new, kwargs):
dt = self.get_state("sensor."+self.devname+"_plan_co2_timestamp")
self.call_service("input_datetime/set_datetime", entity_id="input_datetime."+self.devname+"_starttijd", timestamp=dt)
def click_button_clever(self, entity, attribute, old, new, kwargs):
dt = self.get_state("sensor."+self.devname+"_plan_clever_timestamp")
self.call_service("input_datetime/set_datetime", entity_id="input_datetime."+self.devname+"_starttijd", timestamp=dt) |