juham-automation 0.0.16__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.
@@ -16,13 +16,17 @@ from juham_core.timeutils import (
16
16
 
17
17
  class HotWaterOptimizer(Juham):
18
18
  """Automation class for optimized control of temperature driven home energy consumers e.g hot
19
- water radiators. Reads spot prices, electricity forecast and temperatures to minimize electricity bill.
20
- Additional control over heating is provided by the following attributes
19
+ water radiators. Reads spot prices, electricity forecast, power meter and temperatures to minimize electricity bill.
21
20
 
22
- Computes UOI - optimization utilization index for each hour, based on the spot price and the solar power forecast.
23
- Value of 0 means the hour is expensive, value of 1 means the hour is free. The UOI threshold determines the slots
24
- that are allowed to be consumed.
21
+ Represents a heating system that knows the power rating of its radiator (e.g., 3kW).
22
+ The system subscribes to the 'power' topic to track the current power balance. If the solar panels
23
+ generate more energy than is being consumed, the optimizer activates a relay to ensure that all excess energy
24
+ produced within that hour is used for heating. The goal is to achieve a net zero energy balance for each hour,
25
+ ensuring that any surplus energy from the solar panels is fully utilized.
25
26
 
27
+ Computes also UOI - optimization utilization index for each hour, based on the spot price and the solar power forecast.
28
+ For negative energy balance this determines when energy is consumed. Value of 0 means the hour is expensive, value of 1 means
29
+ the hour is free. The UOI threshold determines the slots that are allowed to be consumed.
26
30
 
27
31
  """
28
32
 
@@ -172,41 +176,64 @@ class HotWaterOptimizer(Juham):
172
176
  self.debug(f"Forecast sorted for the next {str(len(ranked_hours))} hours")
173
177
  return ranked_hours
174
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
+
175
221
  @override
176
222
  def on_message(self, client: object, userdata: Any, msg: MqttMsg) -> None:
177
223
  m = None
178
224
  ts: float = timestamp()
179
225
  ts_utc_quantized: float = quantize(3600, ts - 3600)
180
226
  if msg.topic == self.topic_spot:
181
- self.ranked_spot_prices = self.sort_by_rank(
182
- json.loads(msg.payload.decode()), ts_utc_quantized
183
- )
184
- self.debug(
185
- f"Spot prices received and ranked for {len(self.ranked_spot_prices)} hours"
186
- )
187
- self.power_plan = [] # reset power plan, it depends on spot prices
227
+ self.on_spot(json.loads(msg.payload.decode()), ts_utc_quantized)
188
228
  return
189
229
  elif msg.topic == self.topic_forecast:
190
- forecast = json.loads(msg.payload.decode())
191
- # reject messages that don't have solarenergy forecast
192
-
193
- for f in forecast:
194
- if not "solarenergy" in f:
195
- return
196
-
197
- self.ranked_solarpower = self.sort_by_power(forecast, ts_utc_quantized)
198
- self.debug(
199
- f"Solar energy forecast received and ranked for {len(self.ranked_solarpower)} hours"
200
- )
201
- self.power_plan = [] # reset power plan, it depends on forecast
230
+ self.on_forecast(json.loads(msg.payload.decode()), ts_utc_quantized)
202
231
  return
203
232
  elif msg.topic == self.topic_temperature:
204
233
  m = json.loads(msg.payload.decode())
205
234
  self.current_temperature = m["temperature"]
206
235
  elif msg.topic == self.topic_in_net_energy_balance:
207
- m = json.loads(msg.payload.decode())
208
- self.net_energy_balance = m["energy"]
209
- self.net_energy_power = m["power"]
236
+ self.on_power(json.loads(msg.payload.decode()), ts)
210
237
  elif msg.topic == self.topic_in_powerconsumption:
211
238
  m = json.loads(msg.payload.decode())
212
239
  self.current_power = m["real_total"]
@@ -216,7 +243,10 @@ class HotWaterOptimizer(Juham):
216
243
  self.on_powerplan(ts)
217
244
 
218
245
  def on_powerplan(self, ts_utc_now: float) -> None:
219
- """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.
220
250
 
221
251
  Args:
222
252
  ts_utc_now (float): utc time
@@ -299,28 +329,43 @@ class HotWaterOptimizer(Juham):
299
329
  bool: true if production exceeds the consumption
300
330
  """
301
331
 
332
+ # if net energy balance is negative, then we are not in balancing mode
333
+ if self.net_energy_balance < 0:
334
+ return False
335
+
302
336
  # elapsed and remaining time within the current balancing slot
303
337
  elapsed_ts = ts - quantize(self.energy_balancing_interval, ts)
304
338
  remaining_ts = self.energy_balancing_interval - elapsed_ts
305
339
 
306
340
  # don't bother to switch the relay on for small intervals, to avoid
307
341
  # wearing contactors out
308
- if remaining_ts < self.operation_threshold:
342
+ if (
343
+ not self.net_energy_balancing_mode
344
+ and remaining_ts < self.operation_threshold
345
+ ):
309
346
  print(
310
347
  f"Skipping balance, remaining time {remaining_ts}s < {self.operation_threshold}s"
311
348
  )
312
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
313
358
 
314
- # check if the balance is sufficient for heating the next half of the energy balancing interval
315
- # if yes then switch heating on for the next half an hour
316
- 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
317
362
  elapsed_interval = ts - self.net_energy_balance_ts
318
363
  print(
319
- f"Needed energy {needed_energy}Wh, current balance {self.net_energy_balance}Wh"
364
+ f"Needed energy {int(needed_energy)/3600}kWh, current balance {int(self.net_energy_balance/3600)}kWh"
320
365
  )
321
366
 
322
367
  if (
323
- self.net_energy_balance > needed_energy
368
+ self.net_energy_balance >= needed_energy
324
369
  ) and not self.net_energy_balancing_rc:
325
370
  self.net_energy_balance_ts = ts
326
371
  self.net_energy_balancing_rc = True # heat
@@ -330,7 +375,7 @@ class HotWaterOptimizer(Juham):
330
375
  # check if we have reach the end of the interval, or consumed all the energy
331
376
  # of the current slot. If so switch the energy balancer mode off
332
377
  if (
333
- elapsed_interval > self.energy_balancing_interval / 2.0
378
+ elapsed_interval > self.energy_balancing_interval
334
379
  or self.net_energy_balance < 0
335
380
  ):
336
381
  self.net_energy_balancing_rc = False # heating off
@@ -338,7 +383,9 @@ class HotWaterOptimizer(Juham):
338
383
  return self.net_energy_balancing_rc
339
384
 
340
385
  def consider_heating(self, ts: float) -> int:
341
- """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.
342
389
 
343
390
  Args:
344
391
  ts (float): current UTC time
@@ -349,7 +396,7 @@ class HotWaterOptimizer(Juham):
349
396
 
350
397
  # check if we have energy to consume, if so return 1
351
398
  if self.consider_net_energy_balance(ts):
352
- self.warning("Net energy balance positive")
399
+ self.info("Net energy balance positive")
353
400
  return 1
354
401
  elif self.net_energy_balancing_mode:
355
402
  balancing_slot_start_ts = quantize(self.energy_balancing_interval, ts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: juham-automation
3
- Version: 0.0.16
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=rCOOWP3t0kuRqtoxYcS5R2rla2ud707s53ldkna1s18,22524
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.16.dist-info/licenses/LICENSE.rst,sha256=D3SSbUrv10lpAZ91lTMCQAke-MXMvrjFDsDyM3vEKJI,1114
19
- juham_automation-0.0.16.dist-info/METADATA,sha256=L0RMt4ryBUN-tXO5bFB6nceAlcIfiXaE9dgRtGt9NWA,4733
20
- juham_automation-0.0.16.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
21
- juham_automation-0.0.16.dist-info/entry_points.txt,sha256=di8tXChhP8B_98bQ44u-1zkOha2kZCoJpCAXxTgoSw8,491
22
- juham_automation-0.0.16.dist-info/top_level.txt,sha256=jfohvtocvX_gfT21AhJk7Iay5ZiQsS3HzrDjF7S4Qp0,17
23
- juham_automation-0.0.16.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,,