juham-automation 0.0.17__py3-none-any.whl → 0.0.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -176,41 +176,64 @@ class HotWaterOptimizer(Juham):
176
176
  self.debug(f"Forecast sorted for the next {str(len(ranked_hours))} hours")
177
177
  return ranked_hours
178
178
 
179
+ def on_spot(self, m: list[dict[str, Any]], ts_quantized: float) -> None:
180
+ """Handle the spot prices.
181
+
182
+ Args:
183
+ list[dict[str, Any]]: list of spot prices
184
+ ts_quantized (float): current time
185
+ """
186
+ self.ranked_spot_prices = self.sort_by_rank(m, ts_quantized)
187
+
188
+ def on_forecast(
189
+ self, forecast: list[dict[str, Any]], ts_utc_quantized: float
190
+ ) -> None:
191
+ """Handle the solar forecast.
192
+
193
+ Args:
194
+ m (list[dict[str, Any]]): list of forecast prices
195
+ ts_quantized (float): current time
196
+ """
197
+ # reject forecasts that don't have solarenergy key
198
+ for f in forecast:
199
+ if not "solarenergy" in f:
200
+ return
201
+
202
+ self.ranked_solarpower = self.sort_by_power(forecast, ts_utc_quantized)
203
+ self.debug(
204
+ f"Solar energy forecast received and ranked for {len(self.ranked_solarpower)} hours"
205
+ )
206
+ self.power_plan = [] # reset power plan, it depends on forecast
207
+
208
+ def on_power(self, m: dict[str, Any], ts: float) -> None:
209
+ """Handle the power consumption. Read the current power balance and accumulate
210
+ to the net energy balance to reflect the energy produced (or consumed) within the
211
+ current time slot.
212
+ Args:
213
+ m (dict[str, Any]): power consumption message
214
+ ts (float): current time
215
+ """
216
+ self.net_energy_power = m["power"]
217
+ balance: float = (ts - self.net_energy_balance_ts) * self.net_energy_power
218
+ self.net_energy_balance = self.net_energy_balance + balance
219
+ self.net_energy_balance_ts = ts
220
+
179
221
  @override
180
222
  def on_message(self, client: object, userdata: Any, msg: MqttMsg) -> None:
181
223
  m = None
182
224
  ts: float = timestamp()
183
225
  ts_utc_quantized: float = quantize(3600, ts - 3600)
184
226
  if msg.topic == self.topic_spot:
185
- self.ranked_spot_prices = self.sort_by_rank(
186
- json.loads(msg.payload.decode()), ts_utc_quantized
187
- )
188
- self.debug(
189
- f"Spot prices received and ranked for {len(self.ranked_spot_prices)} hours"
190
- )
191
- self.power_plan = [] # reset power plan, it depends on spot prices
227
+ self.on_spot(json.loads(msg.payload.decode()), ts_utc_quantized)
192
228
  return
193
229
  elif msg.topic == self.topic_forecast:
194
- forecast = json.loads(msg.payload.decode())
195
- # reject messages that don't have solarenergy forecast
196
-
197
- for f in forecast:
198
- if not "solarenergy" in f:
199
- return
200
-
201
- self.ranked_solarpower = self.sort_by_power(forecast, ts_utc_quantized)
202
- self.debug(
203
- f"Solar energy forecast received and ranked for {len(self.ranked_solarpower)} hours"
204
- )
205
- self.power_plan = [] # reset power plan, it depends on forecast
230
+ self.on_forecast(json.loads(msg.payload.decode()), ts_utc_quantized)
206
231
  return
207
232
  elif msg.topic == self.topic_temperature:
208
233
  m = json.loads(msg.payload.decode())
209
234
  self.current_temperature = m["temperature"]
210
235
  elif msg.topic == self.topic_in_net_energy_balance:
211
- m = json.loads(msg.payload.decode())
212
- self.net_energy_balance = m["energy"]
213
- self.net_energy_power = m["power"]
236
+ self.on_power(json.loads(msg.payload.decode()), ts)
214
237
  elif msg.topic == self.topic_in_powerconsumption:
215
238
  m = json.loads(msg.payload.decode())
216
239
  self.current_power = m["real_total"]
@@ -220,7 +243,10 @@ class HotWaterOptimizer(Juham):
220
243
  self.on_powerplan(ts)
221
244
 
222
245
  def on_powerplan(self, ts_utc_now: float) -> None:
223
- """Apply power plan.
246
+ """Apply the power plan. Check if the relay needs to be switched on or off.
247
+ The relay is switched on if the current temperature is below the maximum
248
+ temperature and the current time is within the heating plan. The relay is switched off
249
+ if the current temperature is above the maximum temperature or the current time is outside.
224
250
 
225
251
  Args:
226
252
  ts_utc_now (float): utc time
@@ -313,22 +339,33 @@ class HotWaterOptimizer(Juham):
313
339
 
314
340
  # don't bother to switch the relay on for small intervals, to avoid
315
341
  # wearing contactors out
316
- if remaining_ts < self.operation_threshold:
342
+ if (
343
+ not self.net_energy_balancing_mode
344
+ and remaining_ts < self.operation_threshold
345
+ ):
317
346
  print(
318
347
  f"Skipping balance, remaining time {remaining_ts}s < {self.operation_threshold}s"
319
348
  )
320
349
  return False
350
+ elif remaining_ts <= 0:
351
+ self.net_energy_balancing_rc = False # heating off
352
+ self.info(
353
+ f"End of the balancing interval reached, disabled with {self.net_energy_balance/3600}kWh left"
354
+ )
355
+ self.net_energy_balance = 0.0
356
+ self.net_energy_balance_ts = ts
357
+ return False
321
358
 
322
- # check if the balance is sufficient for heating the next half of the energy balancing interval
323
- # if yes then switch heating on for the next half an hour
324
- needed_energy = 0.5 * self.radiator_power * remaining_ts
359
+ # check if the balance is sufficient for heating the remainin interval
360
+ # if yes then switch heating on
361
+ needed_energy = self.radiator_power * remaining_ts
325
362
  elapsed_interval = ts - self.net_energy_balance_ts
326
363
  print(
327
- f"Needed energy {int(needed_energy)}Wh, current balance {int(self.net_energy_balance)}Wh"
364
+ f"Needed energy {int(needed_energy)/3600}kWh, current balance {int(self.net_energy_balance/3600)}kWh"
328
365
  )
329
366
 
330
367
  if (
331
- self.net_energy_balance > needed_energy
368
+ self.net_energy_balance >= needed_energy
332
369
  ) and not self.net_energy_balancing_rc:
333
370
  self.net_energy_balance_ts = ts
334
371
  self.net_energy_balancing_rc = True # heat
@@ -338,7 +375,7 @@ class HotWaterOptimizer(Juham):
338
375
  # check if we have reach the end of the interval, or consumed all the energy
339
376
  # of the current slot. If so switch the energy balancer mode off
340
377
  if (
341
- elapsed_interval > self.energy_balancing_interval / 2.0
378
+ elapsed_interval > self.energy_balancing_interval
342
379
  or self.net_energy_balance < 0
343
380
  ):
344
381
  self.net_energy_balancing_rc = False # heating off
@@ -346,7 +383,9 @@ class HotWaterOptimizer(Juham):
346
383
  return self.net_energy_balancing_rc
347
384
 
348
385
  def consider_heating(self, ts: float) -> int:
349
- """Consider whether the target boiler needs heating.
386
+ """Consider whether the target boiler needs heating. Check first if the solar
387
+ energy is enough to heat the water the remaining time in the current slot.
388
+ If not, follow the predefined heating plan computed earlier based on the cheapest spot prices.
350
389
 
351
390
  Args:
352
391
  ts (float): current UTC time
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: juham-automation
3
- Version: 0.0.17
3
+ Version: 0.0.19
4
4
  Summary: Juha's Ultimate Home Automation Masterpiece
5
5
  Author-email: J Meskanen <juham.api@gmail.com>
6
6
  Maintainer-email: "J. Meskanen" <juham.api@gmail.com>
@@ -3,7 +3,7 @@ juham_automation/japp.py,sha256=zD5ulfIcaSzwbVKjHv2tdXpw79fpw97B7P-v-ncY6e4,1520
3
3
  juham_automation/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
4
4
  juham_automation/automation/__init__.py,sha256=73Mw0jkipeMCoUtpREHJPATfKe368ZyNeqfbjamBx_Q,481
5
5
  juham_automation/automation/energycostcalculator.py,sha256=7GGKLv5JpHAY3XVjbkboXuQBk-whpWkBwNuQ68Tv4Pc,11091
6
- juham_automation/automation/hotwateroptimizer.py,sha256=oOgMgfqzqLN-hNdqaxgK_eKuw6q3JCsnZNM5XG17nko,23194
6
+ juham_automation/automation/hotwateroptimizer.py,sha256=ImbbfSBQMQXGVneAo8ldN1Dcjyjow75qhLZ1kbqhQzQ,24999
7
7
  juham_automation/automation/powermeter_simulator.py,sha256=0g0gOD9WTqxUj9IbENkea_33JrJ2sZDSQVtmxVUHcD8,4688
8
8
  juham_automation/automation/spothintafi.py,sha256=XnL2zIPx_XaP_1E8ksuYEUemtHP7N6tLlSv2LEBQyXQ,4471
9
9
  juham_automation/automation/watercirculator.py,sha256=d7PQFNajtVafizS_y2R_6GWhm_GYb8uV4-QScz1Sggo,6569
@@ -15,9 +15,9 @@ juham_automation/ts/log_ts.py,sha256=DPfeJhbSMQChY37mjAxEmE73Ys3dxUvNsN78PSuBm9Y
15
15
  juham_automation/ts/power_ts.py,sha256=esNbtH1xklyUaf0YJQ2wDuxTAV3SnEfx-FtiBGPaSVA,1448
16
16
  juham_automation/ts/powermeter_ts.py,sha256=zSATxZAzz1KJeU1wFK8CP86iySWnHil89mridz7WHos,2421
17
17
  juham_automation/ts/powerplan_ts.py,sha256=-Lhc7v5Cj7USy2MfmyUEusXSox9UbEoDtYGReDEt3cw,1527
18
- juham_automation-0.0.17.dist-info/licenses/LICENSE.rst,sha256=D3SSbUrv10lpAZ91lTMCQAke-MXMvrjFDsDyM3vEKJI,1114
19
- juham_automation-0.0.17.dist-info/METADATA,sha256=os2IZ8IJd054hzwvnWwzC8OPdX5939zEMzdrSwH77nw,4733
20
- juham_automation-0.0.17.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
21
- juham_automation-0.0.17.dist-info/entry_points.txt,sha256=di8tXChhP8B_98bQ44u-1zkOha2kZCoJpCAXxTgoSw8,491
22
- juham_automation-0.0.17.dist-info/top_level.txt,sha256=jfohvtocvX_gfT21AhJk7Iay5ZiQsS3HzrDjF7S4Qp0,17
23
- juham_automation-0.0.17.dist-info/RECORD,,
18
+ juham_automation-0.0.19.dist-info/licenses/LICENSE.rst,sha256=D3SSbUrv10lpAZ91lTMCQAke-MXMvrjFDsDyM3vEKJI,1114
19
+ juham_automation-0.0.19.dist-info/METADATA,sha256=zjFtQzp9U-30OuXPZHRoyWSgewTKr5tdM9dGceg3ICY,4733
20
+ juham_automation-0.0.19.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
21
+ juham_automation-0.0.19.dist-info/entry_points.txt,sha256=di8tXChhP8B_98bQ44u-1zkOha2kZCoJpCAXxTgoSw8,491
22
+ juham_automation-0.0.19.dist-info/top_level.txt,sha256=jfohvtocvX_gfT21AhJk7Iay5ZiQsS3HzrDjF7S4Qp0,17
23
+ juham_automation-0.0.19.dist-info/RECORD,,