juham-automation 0.2.7__tar.gz → 0.2.20__tar.gz

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.
Files changed (91) hide show
  1. {juham_automation-0.2.7/juham_automation.egg-info → juham_automation-0.2.20}/PKG-INFO +2 -3
  2. {juham_automation-0.2.7/docs/source → juham_automation-0.2.20}/README.rst +0 -1
  3. {juham_automation-0.2.7 → juham_automation-0.2.20}/docs/source/CHANGELOG.rst +14 -1
  4. {juham_automation-0.2.7 → juham_automation-0.2.20/docs/source}/README.rst +0 -1
  5. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/__init__.py +0 -2
  6. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/automation/__init__.py +0 -2
  7. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/automation/heatingoptimizer.py +18 -32
  8. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/automation/leakdetector.py +0 -2
  9. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/japp.py +0 -2
  10. {juham_automation-0.2.7 → juham_automation-0.2.20/juham_automation.egg-info}/PKG-INFO +2 -3
  11. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation.egg-info/SOURCES.txt +0 -3
  12. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation.egg-info/entry_points.txt +0 -1
  13. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation.egg-info/requires.txt +1 -1
  14. {juham_automation-0.2.7 → juham_automation-0.2.20}/pyproject.toml +2 -3
  15. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_heatingoptimizer.cpython-312.pyc +0 -0
  16. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/test_heatingoptimizer.py +2 -8
  17. juham_automation-0.2.7/examples/myapp.log +0 -52
  18. juham_automation-0.2.7/juham_automation/automation/spothintafi.py +0 -140
  19. juham_automation-0.2.7/tests/automation/test_spothintafi.py +0 -67
  20. {juham_automation-0.2.7 → juham_automation-0.2.20}/LICENSE.rst +0 -0
  21. {juham_automation-0.2.7 → juham_automation-0.2.20}/MANIFEST.in +0 -0
  22. {juham_automation-0.2.7 → juham_automation-0.2.20}/docs/source/CONTRIBUTING.rst +0 -0
  23. {juham_automation-0.2.7 → juham_automation-0.2.20}/docs/source/LICENSE.rst +0 -0
  24. {juham_automation-0.2.7 → juham_automation-0.2.20}/examples/myapp.py +0 -0
  25. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/automation/energybalancer.py +0 -0
  26. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/automation/energycostcalculator.py +0 -0
  27. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/automation/watercirculator.py +0 -0
  28. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/py.typed +0 -0
  29. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/__init__.py +0 -0
  30. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/electricityprice_ts.py +0 -0
  31. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/energybalancer_ts.py +0 -0
  32. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/energycostcalculator_ts.py +0 -0
  33. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/forecast_ts.py +0 -0
  34. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/log_ts.py +0 -0
  35. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/power_ts.py +0 -0
  36. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/powermeter_ts.py +0 -0
  37. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation/ts/powerplan_ts.py +0 -0
  38. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation.egg-info/dependency_links.txt +0 -0
  39. {juham_automation-0.2.7 → juham_automation-0.2.20}/juham_automation.egg-info/top_level.txt +0 -0
  40. {juham_automation-0.2.7 → juham_automation-0.2.20}/setup.cfg +0 -0
  41. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/__init__.py +0 -0
  42. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  43. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/__pycache__/test_japp.cpython-312-pytest-9.0.1.pyc +0 -0
  44. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/__pycache__/test_japp.cpython-312.pyc +0 -0
  45. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__init__.py +0 -0
  46. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/__init__.cpython-312.pyc +0 -0
  47. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_energybalancer.cpython-312-pytest-9.0.1.pyc +0 -0
  48. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_energybalancer.cpython-312.pyc +0 -0
  49. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_energycostcalculator.cpython-312-pytest-9.0.1.pyc +0 -0
  50. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_energycostcalculator.cpython-312.pyc +0 -0
  51. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_heatingoptimizer.cpython-312-pytest-9.0.1.pyc +0 -0
  52. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_juham.cpython-312-pytest-9.0.1.pyc +0 -0
  53. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_juham.cpython-312.pyc +0 -0
  54. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_leakdetector.cpython-312-pytest-9.0.1.pyc +0 -0
  55. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_leakdetector.cpython-312.pyc +0 -0
  56. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_spothintafi.cpython-312-pytest-9.0.1.pyc +0 -0
  57. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_spothintafi.cpython-312.pyc +0 -0
  58. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_watercirculator.cpython-312-pytest-9.0.1.pyc +0 -0
  59. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/__pycache__/test_watercirculator.cpython-312.pyc +0 -0
  60. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/test_energybalancer.py +0 -0
  61. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/test_energycostcalculator.py +0 -0
  62. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/test_juham.py +0 -0
  63. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/test_leakdetector.py +0 -0
  64. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/automation/test_watercirculator.py +0 -0
  65. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/test_japp.py +0 -0
  66. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__init__.py +0 -0
  67. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/__init__.cpython-312.pyc +0 -0
  68. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_electricityprice_ts.cpython-312-pytest-9.0.1.pyc +0 -0
  69. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_electricityprice_ts.cpython-312.pyc +0 -0
  70. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_energybalancer_ts.cpython-312-pytest-9.0.1.pyc +0 -0
  71. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_energybalancer_ts.cpython-312.pyc +0 -0
  72. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_energycostcalculator_ts.cpython-312-pytest-9.0.1.pyc +0 -0
  73. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_energycostcalculator_ts.cpython-312.pyc +0 -0
  74. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_forecast_ts.cpython-312-pytest-9.0.1.pyc +0 -0
  75. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_forecast_ts.cpython-312.pyc +0 -0
  76. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_log_ts.cpython-312-pytest-9.0.1.pyc +0 -0
  77. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_log_ts.cpython-312.pyc +0 -0
  78. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_power_ts.cpython-312-pytest-9.0.1.pyc +0 -0
  79. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_power_ts.cpython-312.pyc +0 -0
  80. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_powermeter_ts.cpython-312-pytest-9.0.1.pyc +0 -0
  81. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_powermeter_ts.cpython-312.pyc +0 -0
  82. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_powerplan_ts.cpython-312-pytest-9.0.1.pyc +0 -0
  83. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/__pycache__/test_powerplan_ts.cpython-312.pyc +0 -0
  84. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/test_electricityprice_ts.py +0 -0
  85. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/test_energybalancer_ts.py +0 -0
  86. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/test_energycostcalculator_ts.py +0 -0
  87. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/test_forecast_ts.py +0 -0
  88. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/test_log_ts.py +0 -0
  89. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/test_power_ts.py +0 -0
  90. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/test_powermeter_ts.py +0 -0
  91. {juham_automation-0.2.7 → juham_automation-0.2.20}/tests/ts/test_powerplan_ts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: juham-automation
3
- Version: 0.2.7
3
+ Version: 0.2.20
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>
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.8
18
18
  Requires-Python: >=3.8
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.rst
21
- Requires-Dist: juham_core>=0.2.4
21
+ Requires-Dist: juham_core>=0.2.6
22
22
  Provides-Extra: dev
23
23
  Requires-Dist: check-manifest; extra == "dev"
24
24
  Requires-Dist: coverage>=7.0; extra == "dev"
@@ -38,7 +38,6 @@ It consists of two main sub-modules:
38
38
 
39
39
  This folder contains automation classes that listen to Juham™ MQTT topics and control various home automation tasks.
40
40
 
41
- - **spothintafi**: Acquires electricity prices in Finland.
42
41
  - **watercirculator**: Automates a water circulator pump based on hot water temperature and motion detection.
43
42
  - **heatingoptimizer**: Controls hot water radiators based on temperature sensors and electricity price data.
44
43
  - **energycostcalculator**: Monitors power consumption and electricity prices, and computes the energy balance in euros.
@@ -12,7 +12,6 @@ It consists of two main sub-modules:
12
12
 
13
13
  This folder contains automation classes that listen to Juham™ MQTT topics and control various home automation tasks.
14
14
 
15
- - **spothintafi**: Acquires electricity prices in Finland.
16
15
  - **watercirculator**: Automates a water circulator pump based on hot water temperature and motion detection.
17
16
  - **heatingoptimizer**: Controls hot water radiators based on temperature sensors and electricity price data.
18
17
  - **energycostcalculator**: Monitors power consumption and electricity prices, and computes the energy balance in euros.
@@ -1,10 +1,23 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
- [0.2.7] - December 1 2025
4
+
5
+ [0.2.10] - December 3 2025
6
+ --------------------------
7
+
8
+ - Error logging improved for timeseries database writes.
9
+ - Dependencies to the recent masterpiece releases updated
10
+
11
+
12
+ [0.2.9] - December 1 2025
5
13
  -------------------------
14
+ - 'spothintafi.py' removed and implemented as separate 'juham-spothintafi' plugin
15
+
6
16
 
17
+ [0.2.8] - December 1 2025
18
+ -------------------------
7
19
  - LeakDetector migrated from juham-watermeter
20
+ - Obsolete debug logging removed from the LeakDetector
8
21
 
9
22
 
10
23
  [0.2.6] - November 30 2025
@@ -12,7 +12,6 @@ It consists of two main sub-modules:
12
12
 
13
13
  This folder contains automation classes that listen to Juham™ MQTT topics and control various home automation tasks.
14
14
 
15
- - **spothintafi**: Acquires electricity prices in Finland.
16
15
  - **watercirculator**: Automates a water circulator pump based on hot water temperature and motion detection.
17
16
  - **heatingoptimizer**: Controls hot water radiators based on temperature sensors and electricity price data.
18
17
  - **energycostcalculator**: Monitors power consumption and electricity prices, and computes the energy balance in euros.
@@ -7,7 +7,6 @@ Juham - Juha's Ultimate Home Automation Masterpiece
7
7
  """
8
8
 
9
9
  from .automation import EnergyCostCalculator
10
- from .automation import SpotHintaFi
11
10
  from .automation import WaterCirculator
12
11
  from .automation import HeatingOptimizer
13
12
  from .automation import EnergyBalancer
@@ -34,7 +33,6 @@ __all__ = [
34
33
  "PowerTs",
35
34
  "PowerPlanTs",
36
35
  "PowerMeterTs",
37
- "SpotHintaFi",
38
36
  "WaterCirculator",
39
37
  "JApp",
40
38
  "ElectricityPriceTs",
@@ -7,7 +7,6 @@ Juham - Juha's Ultimate Home Automation classes
7
7
  """
8
8
 
9
9
  from .energycostcalculator import EnergyCostCalculator
10
- from .spothintafi import SpotHintaFi
11
10
  from .watercirculator import WaterCirculator
12
11
  from .heatingoptimizer import HeatingOptimizer
13
12
  from .energybalancer import EnergyBalancer
@@ -16,7 +15,6 @@ from .leakdetector import LeakDetector
16
15
  __all__ = [
17
16
  "EnergyCostCalculator",
18
17
  "HeatingOptimizer",
19
- "SpotHintaFi",
20
18
  "WaterCirculator",
21
19
  "EnergyBalancer",
22
20
  "LeakDetector",
@@ -44,15 +44,6 @@ class HeatingOptimizer(Juham):
44
44
  radiator_power: float = 6000 # W
45
45
  """Radiator power in Watts. This is the maximum power that the radiator can consume."""
46
46
 
47
- heating_slots_per_day: float = 4
48
- """ Number of slots per day the radiator is allowed to heat."""
49
-
50
- schedule_start_slot: float = 0
51
- """Start slots of the heating schedule."""
52
-
53
- schedule_stop_slot: float = 0
54
- """Stop slot of the heating schedule. Heating is allowed only between start-stop slots."""
55
-
56
47
  timezone: str = "Europe/Helsinki"
57
48
  """ Timezone of the heating system. This is used to convert UTC timestamps to local time."""
58
49
 
@@ -147,7 +138,7 @@ class HeatingOptimizer(Juham):
147
138
  super().__init__(name)
148
139
 
149
140
  self.heating_slots_per_day = num_hours * ( 3600 / self.energy_balancing_interval)
150
- self.start_slot = start_hour * (3600 / self.energy_balancing_interval)
141
+ self.schedule_start_slot = start_hour * (3600 / self.energy_balancing_interval)
151
142
  self.spot_limit = spot_limit
152
143
 
153
144
  self.topic_in_spot = self.make_topic_name("spot")
@@ -464,6 +455,8 @@ class HeatingOptimizer(Juham):
464
455
  """
465
456
  self.spot_prices = m
466
457
  self.ranked_spot_prices = self.sort_by_rank(m, ts_quantized)
458
+ # reset the current state of the relay
459
+ self.current_relay_state = -1
467
460
 
468
461
  def on_forecast(
469
462
  self, forecast: list[dict[str, Any]], ts_utc_quantized: float
@@ -572,11 +565,7 @@ class HeatingOptimizer(Juham):
572
565
  if not self.heating_plan:
573
566
  self.error(f"{self.name} failed to create heating plan")
574
567
  return
575
- else:
576
- self.info(
577
- f"{self.name} heating plan of length {len(self.heating_plan)} created",
578
- "",
579
- )
568
+ self.info(f"{self.name} heating plan of length {len(self.heating_plan)} created", str(self.heating_plan))
580
569
 
581
570
  self.publish_heating_plan(self.heating_plan)
582
571
 
@@ -630,6 +619,7 @@ class HeatingOptimizer(Juham):
630
619
 
631
620
  # check if we have excess energy to spent within the current slot
632
621
  if self.net_energy_balance_mode:
622
+ self.debug("Energy balancing active, bypass plan")
633
623
  return 1
634
624
 
635
625
  slot : int = self.timestamp_slot(ts)
@@ -644,7 +634,7 @@ class HeatingOptimizer(Juham):
644
634
  break
645
635
 
646
636
  if state == -1:
647
- self.error(f"{self.name} cannot find heating plan for slot {slot}")
637
+ self.error(f"Cannot find heating plan for slot {slot}, no heating")
648
638
  return 0
649
639
 
650
640
  # don't heat if the current temperature is already high enough
@@ -673,13 +663,13 @@ class HeatingOptimizer(Juham):
673
663
  """
674
664
 
675
665
  if not self.is_slot_within_schedule(
676
- slot, self.schedule_start_slot, self.schedule_stop_slot
666
+ slot, self.schedule_start_slot, self.schedule_start_slot + self.heating_slots_per_day
677
667
  ):
678
668
  return 0.0
679
669
 
680
- if price < 0.0001:
670
+ if price < 0.01:
681
671
  return 1.0 # use
682
- elif price > self.expected_average_price:
672
+ elif price > 3*self.expected_average_price:
683
673
  return 0.0 # try not to use
684
674
  else:
685
675
  fom = self.expected_average_price / price
@@ -880,8 +870,6 @@ class HeatingOptimizer(Juham):
880
870
  # factor = 0 -> uses monthly_temp_max (full monthly default capacity)
881
871
  max_temp_today = (factor * T_max_target) + ((1 - factor) * monthly_temp_max)
882
872
 
883
-
884
-
885
873
  # adjust the temperature limits based on the electricity prices
886
874
  ts : float = timestamp()
887
875
  num_hours : float = self.heating_slots_per_day * self.energy_balancing_interval / 3600.0
@@ -901,10 +889,7 @@ class HeatingOptimizer(Juham):
901
889
  self, slot: int, spot: float, fom: float, end_slot: int
902
890
  ) -> bool:
903
891
  return (
904
- slot >= self.start_slot
905
- and slot < end_slot
906
- and float(spot) < self.spot_limit
907
- and fom > self.uoi_threshold
892
+ slot >= self.schedule_start_slot and slot < end_slot
908
893
  )
909
894
 
910
895
  def create_heating_plan(self) -> list[dict[str, Any]]:
@@ -916,17 +901,17 @@ class HeatingOptimizer(Juham):
916
901
 
917
902
  state = 0
918
903
  heating_plan: list[dict[str, Any]] = []
919
- slot: int = 0
904
+
920
905
  for hp in self.power_plan:
921
906
  ts: float = hp["Timestamp"]
922
907
  fom = hp["FOM"]
923
908
  spot = hp["Spot"]
924
- end_slot: float = self.start_slot + self.heating_slots_per_day
925
- slot: float = self.timestamp_slot(ts)
909
+ end_slot: int = self.schedule_start_slot + self.heating_slots_per_day
910
+ slot: int = self.timestamp_slot(ts)
926
911
  schedule_on: bool = self.is_slot_within_schedule(
927
- slot, self.schedule_start_slot, self.schedule_stop_slot
912
+ slot, self.schedule_start_slot, self.schedule_start_slot + self.heating_slots_per_day
928
913
  )
929
-
914
+
930
915
  if self.enable_relay(slot, spot, fom, end_slot) and schedule_on:
931
916
  state = 1
932
917
  else:
@@ -939,11 +924,12 @@ class HeatingOptimizer(Juham):
939
924
  "UOI": fom,
940
925
  "Spot": spot,
941
926
  }
927
+ print(f"Slot {slot}, start_slot={self.schedule_start_slot}, end_slot={end_slot}, schedule on {schedule_on} and enable_relay {self.enable_relay(slot, spot, fom, end_slot)}")
942
928
 
943
929
  heating_plan.append(heat)
944
- slot = slot + 1
945
930
 
946
- self.info(f"{self.name} heating plan of {len(heating_plan)} slots created", "")
931
+
932
+ self.info(f"{self.name} heating plan of {len(heating_plan)} slots created", str(heating_plan))
947
933
  return heating_plan
948
934
 
949
935
 
@@ -144,8 +144,6 @@ class LeakDetector(Juham):
144
144
  data (dict): Motion sensor data containing timestamp.
145
145
  """
146
146
  if "motion" in data and data["motion"]:
147
- readable :str = datetime.fromtimestamp(self.motion_last_detected_ts).strftime("%Y-%m-%d %H:%M:%S")
148
- self.warning(f"Leak suspect reset due to detected motion", f"Last detected motion {readable}")
149
147
  self.motion_last_detected_ts = data["ts"]
150
148
 
151
149
  @override
@@ -10,7 +10,6 @@ from .ts import EnergyBalancerTs
10
10
  from .ts import LogTs
11
11
  from .ts import EnergyCostCalculatorTs
12
12
  from .ts import ElectricityPriceTs
13
- from .automation import SpotHintaFi
14
13
  from .automation import EnergyCostCalculator
15
14
 
16
15
 
@@ -40,7 +39,6 @@ class JApp(Application):
40
39
  self.add(PowerPlanTs())
41
40
  self.add(PowerMeterTs())
42
41
  self.add(LogTs())
43
- self.add(SpotHintaFi())
44
42
  self.add(EnergyCostCalculator())
45
43
  self.add(EnergyCostCalculatorTs())
46
44
  self.add(ElectricityPriceTs())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: juham-automation
3
- Version: 0.2.7
3
+ Version: 0.2.20
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>
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.8
18
18
  Requires-Python: >=3.8
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.rst
21
- Requires-Dist: juham_core>=0.2.4
21
+ Requires-Dist: juham_core>=0.2.6
22
22
  Provides-Extra: dev
23
23
  Requires-Dist: check-manifest; extra == "dev"
24
24
  Requires-Dist: coverage>=7.0; extra == "dev"
@@ -38,7 +38,6 @@ It consists of two main sub-modules:
38
38
 
39
39
  This folder contains automation classes that listen to Juham™ MQTT topics and control various home automation tasks.
40
40
 
41
- - **spothintafi**: Acquires electricity prices in Finland.
42
41
  - **watercirculator**: Automates a water circulator pump based on hot water temperature and motion detection.
43
42
  - **heatingoptimizer**: Controls hot water radiators based on temperature sensors and electricity price data.
44
43
  - **energycostcalculator**: Monitors power consumption and electricity prices, and computes the energy balance in euros.
@@ -6,7 +6,6 @@ docs/source/CHANGELOG.rst
6
6
  docs/source/CONTRIBUTING.rst
7
7
  docs/source/LICENSE.rst
8
8
  docs/source/README.rst
9
- examples/myapp.log
10
9
  examples/myapp.py
11
10
  juham_automation/__init__.py
12
11
  juham_automation/japp.py
@@ -22,7 +21,6 @@ juham_automation/automation/energybalancer.py
22
21
  juham_automation/automation/energycostcalculator.py
23
22
  juham_automation/automation/heatingoptimizer.py
24
23
  juham_automation/automation/leakdetector.py
25
- juham_automation/automation/spothintafi.py
26
24
  juham_automation/automation/watercirculator.py
27
25
  juham_automation/ts/__init__.py
28
26
  juham_automation/ts/electricityprice_ts.py
@@ -44,7 +42,6 @@ tests/automation/test_energycostcalculator.py
44
42
  tests/automation/test_heatingoptimizer.py
45
43
  tests/automation/test_juham.py
46
44
  tests/automation/test_leakdetector.py
47
- tests/automation/test_spothintafi.py
48
45
  tests/automation/test_watercirculator.py
49
46
  tests/automation/__pycache__/__init__.cpython-312.pyc
50
47
  tests/automation/__pycache__/test_energybalancer.cpython-312-pytest-9.0.1.pyc
@@ -9,4 +9,3 @@ logts_plugin = juham_automation:LogTs
9
9
  powerplan_plugin = juham_automation:PowerPlanTs
10
10
  powerts_plugin = juham_automation:PowerTs
11
11
  rwatercirculator_plugin = juham_automation:WaterCirculator
12
- spothintafi_plugin = juham_automation:SpotHintaFi
@@ -1,4 +1,4 @@
1
- juham_core>=0.2.4
1
+ juham_core>=0.2.6
2
2
 
3
3
  [dev]
4
4
  check-manifest
@@ -7,7 +7,7 @@ include-package-data = true
7
7
 
8
8
  [project]
9
9
  name = "juham-automation"
10
- version = "0.2.7"
10
+ version = "0.2.20"
11
11
  description = "Juha's Ultimate Home Automation Masterpiece"
12
12
  readme = {file = "README.rst", content-type = "text/markdown"}
13
13
  requires-python = ">=3.8"
@@ -28,7 +28,7 @@ classifiers = [
28
28
  ]
29
29
 
30
30
  dependencies = [
31
- "juham_core >= 0.2.4",
31
+ "juham_core >= 0.2.6",
32
32
  ]
33
33
 
34
34
 
@@ -45,7 +45,6 @@ powerts_plugin = "juham_automation:PowerTs"
45
45
  powerplan_plugin = "juham_automation:PowerPlanTs"
46
46
  energybalancerts_plugin = "juham_automation:EnergyBalancerTs"
47
47
  logts_plugin = "juham_automation:LogTs"
48
- spothintafi_plugin = "juham_automation:SpotHintaFi"
49
48
  rwatercirculator_plugin = "juham_automation:WaterCirculator"
50
49
  energycostcalculator_plugin = "juham_automation:EnergyCostCalculator"
51
50
  energybalancer_plugin = "juham_automation:EnergyBalancer"
@@ -85,8 +85,6 @@ class HeatingOptimizerTest2(unittest.TestCase):
85
85
  self.assertGreater(uoi, 0)
86
86
  # Price above expected_average_price
87
87
  self.ho.expected_average_price = 0.2
88
- uoi2 = self.ho.compute_uoi(price=0.3, slot=7 * 3600/self.ho.energy_balancing_interval)
89
- self.assertEqual(uoi2, 0.0)
90
88
 
91
89
  def test_compute_effective_price(self):
92
90
  price = self.ho.compute_effective_price(requested_power=6000, available_solpower=3000, spot=0.2)
@@ -159,7 +157,7 @@ class TestHeatingOptimizer(unittest.TestCase):
159
157
 
160
158
  def test_initialization(self) -> None:
161
159
  self.assertEqual(self.optimizer.heating_slots_per_day, 3*3600 // self.optimizer.energy_balancing_interval)
162
- self.assertEqual(self.optimizer.start_slot, 5 * 3600 // self.optimizer.energy_balancing_interval)
160
+ self.assertEqual(self.optimizer.schedule_start_slot, 5 * 3600 // self.optimizer.energy_balancing_interval)
163
161
  self.assertEqual(self.optimizer.spot_limit, 0.25)
164
162
  self.assertEqual(self.optimizer.current_temperature, 100)
165
163
  self.assertFalse(self.optimizer.relay)
@@ -354,33 +352,29 @@ class HeatingOptimizerOnPowerPlanTest(unittest.TestCase):
354
352
  min_a, max_a = self.ho.calculate_target_temps(
355
353
  MIN_MONTHLY, MAX_MONTHLY, -30.0, TARGET_HOME_TEMP
356
354
  )
357
- self.assertEqual(40.0, min_a);
355
+
358
356
  self.assertEqual(70.0, max_a);
359
357
 
360
358
  # --- Scenario 2: 0C Forecast (Low Demand) ---
361
359
  min_b, max_b = self.ho.calculate_target_temps(
362
360
  MIN_MONTHLY, MAX_MONTHLY, -20.0, TARGET_HOME_TEMP
363
361
  )
364
- self.assertEqual(40.0, min_b);
365
362
  self.assertEqual(64.0, max_b);
366
363
 
367
364
  # --- Scenario 3: Warm Forecast (Very Low Demand) ---
368
365
  min_c, max_c = self.ho.calculate_target_temps(
369
366
  MIN_MONTHLY, MAX_MONTHLY, -10, TARGET_HOME_TEMP
370
367
  )
371
- self.assertEqual(40.0, min_c);
372
368
  self.assertEqual(58.0, max_c);
373
369
 
374
370
  # --- Scenario 4: Hot Forecast ---
375
371
  min_d, max_d = self.ho.calculate_target_temps(
376
372
  MIN_MONTHLY, MAX_MONTHLY, 0, TARGET_HOME_TEMP
377
373
  )
378
- self.assertEqual(40.0, min_d);
379
374
  self.assertEqual(52.0, max_d);
380
375
  min_e, max_e = self.ho.calculate_target_temps(
381
376
  MIN_MONTHLY, MAX_MONTHLY, 30, TARGET_HOME_TEMP
382
377
  )
383
- self.assertEqual(40.0, min_e);
384
378
  self.assertEqual(40.0, max_e);
385
379
 
386
380
 
@@ -1,52 +0,0 @@
1
- 2025-11-24 20:05:06,895 [INFO ] MyApp : Configuration files in ~/.myapp/config
2
- 2025-11-24 20:05:06,899 [INFO ] MyApp : myapp : Starting up 0 forecast_ts
3
- 2025-11-24 20:05:06,899 [WARNI] ForecastTs : forecast_ts : Suspicious configuration: no database_class_id set
4
- 2025-11-24 20:05:06,899 [WARNI] ForecastTs : forecast_ts : Suscpicious configuration: no mqtt_class_id set for forecast_ts:ForecastTs
5
- 2025-11-24 20:05:06,899 [INFO ] MyApp : myapp : Starting up 1 power_ts
6
- 2025-11-24 20:05:06,899 [WARNI] PowerTs : power_ts : Suspicious configuration: no database_class_id set
7
- 2025-11-24 20:05:06,899 [WARNI] PowerTs : power_ts : Suscpicious configuration: no mqtt_class_id set for power_ts:PowerTs
8
- 2025-11-24 20:05:06,900 [INFO ] MyApp : myapp : Starting up 2 powerplan_ts
9
- 2025-11-24 20:05:06,900 [WARNI] PowerPlanTs : powerplan_ts : Suspicious configuration: no database_class_id set
10
- 2025-11-24 20:05:06,900 [WARNI] PowerPlanTs : powerplan_ts : Suscpicious configuration: no mqtt_class_id set for powerplan_ts:PowerPlanTs
11
- 2025-11-24 20:05:06,900 [INFO ] MyApp : myapp : Starting up 3 powermeter_record
12
- 2025-11-24 20:05:06,900 [WARNI] PowerMeterTs : powermeter_record : Suspicious configuration: no database_class_id set
13
- 2025-11-24 20:05:06,900 [WARNI] PowerMeterTs : powermeter_record : Suscpicious configuration: no mqtt_class_id set for powermeter_record:PowerMeterTs
14
- 2025-11-24 20:05:06,900 [INFO ] MyApp : myapp : Starting up 4 log_ts
15
- 2025-11-24 20:05:06,900 [WARNI] LogTs : log_ts : Suspicious configuration: no database_class_id set
16
- 2025-11-24 20:05:06,900 [WARNI] LogTs : log_ts : Suscpicious configuration: no mqtt_class_id set for log_ts:LogTs
17
- 2025-11-24 20:05:06,900 [INFO ] MyApp : myapp : Starting up 5 rspothintafi
18
- 2025-11-24 20:05:06,900 [WARNI] SpotHintaFi : rspothintafi : Suspicious configuration: no database_class_id set
19
- 2025-11-24 20:05:06,900 [WARNI] SpotHintaFi : rspothintafi : Suscpicious configuration: no mqtt_class_id set for rspothintafi:SpotHintaFi
20
- 2025-11-24 20:05:06,901 [INFO ] SpotHintaFi : rspothintafi : Starting up rspothintafi - <class 'juham_automation.automation.spothintafi.SpotHintaFiThread'>
21
- 2025-11-24 20:05:06,901 [INFO ] MyApp : myapp : Starting up 6 ecc
22
- 2025-11-24 20:05:06,901 [WARNI] EnergyCostCalculator : ecc : Suscpicious configuration: no mqtt_class_id set for ecc:EnergyCostCalculator
23
- 2025-11-24 20:05:06,901 [INFO ] MyApp : myapp : Starting up 7 ecc_ts
24
- 2025-11-24 20:05:06,901 [WARNI] EnergyCostCalculatorTs : ecc_ts : Suspicious configuration: no database_class_id set
25
- 2025-11-24 20:05:06,901 [WARNI] EnergyCostCalculatorTs : ecc_ts : Suscpicious configuration: no mqtt_class_id set for ecc_ts:EnergyCostCalculatorTs
26
- 2025-11-24 20:05:06,902 [INFO ] MyApp : myapp : Starting up 8 electricityprice_ts
27
- 2025-11-24 20:05:06,902 [WARNI] ElectricityPriceTs : electricityprice_ts : Suspicious configuration: no database_class_id set
28
- 2025-11-24 20:05:06,902 [WARNI] ElectricityPriceTs : electricityprice_ts : Suscpicious configuration: no mqtt_class_id set for electricityprice_ts:ElectricityPriceTs
29
- 2025-11-24 20:05:06,902 [INFO ] MyApp : myapp : Starting up 9 energybalancer
30
- 2025-11-24 20:05:06,902 [WARNI] EnergyBalancer : energybalancer : Suscpicious configuration: no mqtt_class_id set for energybalancer:EnergyBalancer
31
- 2025-11-24 20:05:06,902 [INFO ] MyApp : myapp : Starting up 10 energybalancer_ts
32
- 2025-11-24 20:05:06,902 [WARNI] EnergyBalancerTs : energybalancer_ts : Suspicious configuration: no database_class_id set
33
- 2025-11-24 20:05:06,905 [WARNI] EnergyBalancerTs : energybalancer_ts : Suscpicious configuration: no mqtt_class_id set for energybalancer_ts:EnergyBalancerTs
34
- 2025-11-24 20:05:06,905 [INFO ] MyApp : myapp : Starting up 11 boiler
35
- 2025-11-24 20:05:06,905 [WARNI] HeatingOptimizer : boiler : Suscpicious configuration: no mqtt_class_id set for boiler:HeatingOptimizer
36
- 2025-11-24 20:05:06,905 [INFO ] MyApp : myapp : All 12 children successfully started
37
- 2025-11-24 20:05:06,905 [WARNI] Juham : myapp : Suscpicious configuration: no mqtt_class_id set for myapp:Juham
38
- 2025-11-24 20:05:06,905 [ERROR] Juham : myapp : myapp does NOT have mqtt client, cannot run_forever, giving up
39
- 2025-11-24 20:05:06,905 [INFO ] MyApp : myapp : Shutting down children
40
- 2025-11-24 20:05:06,906 [INFO ] MyApp : myapp : Shutting down thread 0 forecast_ts
41
- 2025-11-24 20:05:06,906 [INFO ] MyApp : myapp : Shutting down thread 1 power_ts
42
- 2025-11-24 20:05:06,906 [INFO ] MyApp : myapp : Shutting down thread 2 powerplan_ts
43
- 2025-11-24 20:05:06,906 [INFO ] MyApp : myapp : Shutting down thread 3 powermeter_record
44
- 2025-11-24 20:05:06,906 [INFO ] MyApp : myapp : Shutting down thread 4 log_ts
45
- 2025-11-24 20:05:06,906 [INFO ] MyApp : myapp : Shutting down thread 5 rspothintafi
46
- 2025-11-24 20:05:07,298 [INFO ] MyApp : myapp : Shutting down thread 6 ecc
47
- 2025-11-24 20:05:07,298 [INFO ] MyApp : myapp : Shutting down thread 7 ecc_ts
48
- 2025-11-24 20:05:07,298 [INFO ] MyApp : myapp : Shutting down thread 8 electricityprice_ts
49
- 2025-11-24 20:05:07,298 [INFO ] MyApp : myapp : Shutting down thread 9 energybalancer
50
- 2025-11-24 20:05:07,298 [INFO ] MyApp : myapp : Shutting down thread 10 energybalancer_ts
51
- 2025-11-24 20:05:07,298 [INFO ] MyApp : myapp : Shutting down thread 11 boiler
52
- 2025-11-24 20:05:07,298 [INFO ] MyApp : myapp : All 12 children successfully shut down
@@ -1,140 +0,0 @@
1
- from datetime import datetime
2
- import time
3
- import json
4
- from typing import Any, Dict, Optional, cast
5
- from typing_extensions import override
6
-
7
- from masterpiece.mqtt import Mqtt, MqttMsg
8
- from juham_core import JuhamCloudThread, JuhamThread
9
-
10
-
11
- class SpotHintaFiThread(JuhamCloudThread):
12
- """Thread running SpotHinta.fi.
13
-
14
- Periodically fetches the spot electricity prices and publishes them
15
- to 'spot' topic.
16
- """
17
-
18
- _spot_topic: str = ""
19
- _url: str = ""
20
- _interval: float = 12 * 3600
21
- grid_cost_day: float = 0.0314
22
- grid_cost_night: float = 0.0132
23
- grid_cost_tax: float = 0.028272
24
-
25
- def __init__(self, client: Optional[Mqtt] = None) -> None:
26
- super().__init__(client)
27
- self._interval = 60
28
-
29
- def init(self, topic: str, url: str, interval: float) -> None:
30
- self._spot_topic = topic
31
- self._url = url
32
- self._interval = interval
33
-
34
- @override
35
- def make_weburl(self) -> str:
36
- return self._url
37
-
38
- @override
39
- def update_interval(self) -> float:
40
- return self._interval
41
-
42
- @override
43
- def process_data(self, rawdata: Any) -> None:
44
- """Publish electricity price message to Juham topic.
45
-
46
- Args:
47
- rawdata (dict): electricity prices
48
- """
49
-
50
- super().process_data(rawdata)
51
- data = rawdata.json()
52
-
53
- spot = []
54
- for e in data:
55
- dt = datetime.fromisoformat(e["DateTime"]) # Correct timezone handling
56
- ts = int(dt.timestamp()) # Ensure integer timestamps like in the test
57
-
58
- hour = dt.strftime("%H") # Correctly extract hour
59
-
60
- if 6 <= int(hour) < 22:
61
- grid_cost = self.grid_cost_day
62
- else:
63
- grid_cost = self.grid_cost_night
64
-
65
- total_price = round(e["PriceWithTax"] + grid_cost + self.grid_cost_tax, 6)
66
- grid_cost_total = round(grid_cost + self.grid_cost_tax, 6)
67
-
68
- h = {
69
- "Timestamp": ts,
70
- "hour": hour,
71
- "Rank": e["Rank"],
72
- "PriceWithTax": total_price,
73
- "GridCost": grid_cost_total,
74
- }
75
- spot.append(h)
76
-
77
- self.publish(self._spot_topic, json.dumps(spot), 1, True)
78
- # self.info(f"Spot electricity prices published for the next {len(spot)} days")
79
-
80
-
81
- class SpotHintaFi(JuhamThread):
82
- """Spot electricity price for reading hourly electricity prices from
83
- https://api.spot-hinta.fi site.
84
- """
85
-
86
- _SPOTHINTAFI: str = "_spothintafi"
87
- worker_thread_id = SpotHintaFiThread.get_class_id()
88
- url = "https://api.spot-hinta.fi/TodayAndDayForward"
89
- update_interval = 12 * 3600
90
-
91
- def __init__(self, name: str = "rspothintafi") -> None:
92
- super().__init__(name)
93
- self.active_liter_lpm = -1
94
- self.update_ts = None
95
- self.spot_topic = self.make_topic_name("spot")
96
-
97
- @override
98
- def on_connect(self, client: object, userdata: Any, flags: int, rc: int) -> None:
99
- super().on_connect(client, userdata, flags, rc)
100
- if rc == 0:
101
- self.subscribe(self.spot_topic)
102
-
103
- @override
104
- def on_message(self, client: object, userdata: Any, msg: MqttMsg) -> None:
105
- if msg.topic == self.spot_topic:
106
- em = json.loads(msg.payload.decode())
107
- self.on_spot(em)
108
- else:
109
- super().on_message(client, userdata, msg)
110
-
111
- def on_spot(self, m: dict[Any, Any]) -> None:
112
- """Write hourly spot electricity prices to time series database.
113
-
114
- Args:
115
- m (dict): holding hourly spot electricity prices
116
- """
117
- pass
118
-
119
- @override
120
- def run(self) -> None:
121
- self.worker = cast(SpotHintaFiThread, self.instantiate(self.worker_thread_id))
122
- self.worker.init(self.spot_topic, self.url, self.update_interval)
123
- super().run()
124
-
125
- @override
126
- def to_dict(self) -> Dict[str, Any]:
127
- data: Dict[str, Any] = super().to_dict()
128
- data[self._SPOTHINTAFI] = {
129
- "topic": self.spot_topic,
130
- "url": self.url,
131
- "interval": self.update_interval,
132
- }
133
- return data
134
-
135
- @override
136
- def from_dict(self, data: Dict[str, Any]) -> None:
137
- super().from_dict(data)
138
- if self._SPOTHINTAFI in data:
139
- for key, value in data[self._SPOTHINTAFI].items():
140
- setattr(self, key, value)
@@ -1,67 +0,0 @@
1
- import unittest
2
- from unittest import TestCase, mock
3
- from unittest.mock import MagicMock, patch
4
- import json
5
- from typing import Dict, Any
6
- from juham_core import JuhamCloudThread
7
-
8
- from juham_automation.automation.spothintafi import (
9
- SpotHintaFiThread,
10
- SpotHintaFi,
11
- )
12
-
13
-
14
- class TestSpotHintaFiThread(TestCase):
15
-
16
- @patch("juham_automation.automation.spothintafi.Mqtt")
17
- def test_make_weburl(self, mock_mqtt) -> None:
18
-
19
- thread = SpotHintaFiThread(mock_mqtt)
20
- thread.init("test/topic", "http://test.url", 60)
21
-
22
- # Test make_weburl method
23
- self.assertEqual(thread.make_weburl(), "http://test.url")
24
-
25
-
26
- class TestSpotHintaFi(unittest.TestCase):
27
-
28
- def test_to_dict(self) -> None:
29
- spot_hinta = SpotHintaFi("spot")
30
- spot_hinta.url = "http://test.url"
31
- expected_dict = {
32
- "_class": "SpotHintaFi",
33
- "_version": 0,
34
- "_object": {"name": "spot", "payload": None},
35
- "_base": {},
36
- "_spothintafi": {
37
- "topic": "/spot",
38
- "url": "http://test.url",
39
- "interval": 43200,
40
- },
41
- }
42
-
43
- actual_dict: Dict[str, Any] = spot_hinta.to_dict()
44
- self.assertEqual(actual_dict, expected_dict)
45
-
46
- def test_from_dict(self) -> None:
47
- spot_hinta = SpotHintaFi()
48
- data = {
49
- "_class": "SpotHintaFi",
50
- "_object": {"name": "rspothintafi"},
51
- "_base": {},
52
- "_spothintafi": {
53
- "topic": "spot/topic",
54
- "url": "http://test.url",
55
- "interval": 60,
56
- },
57
- }
58
-
59
- spot_hinta.from_dict(data)
60
-
61
- self.assertEqual(spot_hinta.spot_topic, "/spot")
62
- self.assertEqual(spot_hinta.url, "http://test.url")
63
- self.assertEqual(spot_hinta.update_interval, 43200)
64
-
65
-
66
- if __name__ == "__main__":
67
- unittest.main()