juham-automation 0.0.17__py3-none-any.whl → 0.0.26__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.
Files changed (26) hide show
  1. juham_automation/__init__.py +42 -38
  2. juham_automation/automation/__init__.py +23 -21
  3. juham_automation/automation/energybalancer.py +158 -0
  4. juham_automation/automation/energycostcalculator.py +267 -266
  5. juham_automation/automation/{hotwateroptimizer.py → heatingoptimizer.py} +539 -581
  6. juham_automation/automation/powermeter_simulator.py +139 -139
  7. juham_automation/automation/spothintafi.py +140 -140
  8. juham_automation/automation/watercirculator.py +159 -159
  9. juham_automation/japp.py +53 -49
  10. juham_automation/ts/__init__.py +27 -25
  11. juham_automation/ts/electricityprice_ts.py +51 -51
  12. juham_automation/ts/energybalancer_ts.py +47 -0
  13. juham_automation/ts/energycostcalculator_ts.py +43 -43
  14. juham_automation/ts/forecast_ts.py +97 -97
  15. juham_automation/ts/log_ts.py +57 -57
  16. juham_automation/ts/power_ts.py +49 -49
  17. juham_automation/ts/powermeter_ts.py +67 -70
  18. juham_automation/ts/powerplan_ts.py +45 -45
  19. juham_automation-0.0.26.dist-info/METADATA +152 -0
  20. juham_automation-0.0.26.dist-info/RECORD +25 -0
  21. {juham_automation-0.0.17.dist-info → juham_automation-0.0.26.dist-info}/entry_points.txt +3 -1
  22. {juham_automation-0.0.17.dist-info → juham_automation-0.0.26.dist-info}/licenses/LICENSE.rst +25 -25
  23. juham_automation-0.0.17.dist-info/METADATA +0 -106
  24. juham_automation-0.0.17.dist-info/RECORD +0 -23
  25. {juham_automation-0.0.17.dist-info → juham_automation-0.0.26.dist-info}/WHEEL +0 -0
  26. {juham_automation-0.0.17.dist-info → juham_automation-0.0.26.dist-info}/top_level.txt +0 -0
@@ -1,38 +1,42 @@
1
- """
2
- Description
3
- ===========
4
-
5
- Juham - Juha's Ultimate Home Automation Masterpiece
6
-
7
- """
8
-
9
- from .automation import EnergyCostCalculator
10
- from .automation import PowerMeterSimulator
11
- from .automation import SpotHintaFi
12
- from .automation import WaterCirculator
13
- from .automation import HotWaterOptimizer
14
- from .ts import EnergyCostCalculatorTs
15
- from .ts import ForecastTs
16
- from .ts import LogTs
17
- from .ts import PowerTs
18
- from .ts import PowerPlanTs
19
- from .ts import PowerMeterTs
20
- from .ts import ElectricityPriceTs
21
- from .japp import JApp
22
-
23
-
24
- __all__ = [
25
- "EnergyCostCalculator",
26
- "EnergyCostCalculatorTs",
27
- "ForecastTs",
28
- "HotWaterOptimizer",
29
- "LogTs",
30
- "PowerTs",
31
- "PowerPlanTs",
32
- "PowerMeterTs",
33
- "SpotHintaFi",
34
- "WaterCirculator",
35
- "JApp",
36
- "PowerMeterSimulator",
37
- "ElectricityPriceTs",
38
- ]
1
+ """
2
+ Description
3
+ ===========
4
+
5
+ Juham - Juha's Ultimate Home Automation Masterpiece
6
+
7
+ """
8
+
9
+ from .automation import EnergyCostCalculator
10
+ from .automation import PowerMeterSimulator
11
+ from .automation import SpotHintaFi
12
+ from .automation import WaterCirculator
13
+ from .automation import HeatingOptimizer
14
+ from .automation import EnergyBalancer
15
+ from .ts import EnergyCostCalculatorTs
16
+ from .ts import ForecastTs
17
+ from .ts import LogTs
18
+ from .ts import PowerTs
19
+ from .ts import PowerPlanTs
20
+ from .ts import PowerMeterTs
21
+ from .ts import ElectricityPriceTs
22
+ from .ts import EnergyBalancerTs
23
+ from .japp import JApp
24
+
25
+
26
+ __all__ = [
27
+ "EnergyCostCalculator",
28
+ "EnergyCostCalculatorTs",
29
+ "ForecastTs",
30
+ "HeatingOptimizer",
31
+ "EnergyBalancer",
32
+ "LogTs",
33
+ "PowerTs",
34
+ "PowerPlanTs",
35
+ "PowerMeterTs",
36
+ "SpotHintaFi",
37
+ "WaterCirculator",
38
+ "JApp",
39
+ "PowerMeterSimulator",
40
+ "ElectricityPriceTs",
41
+ "EnergyBalancerTs",
42
+ ]
@@ -1,21 +1,23 @@
1
- """
2
- Description
3
- ===========
4
-
5
- Juham - Juha's Ultimate Home Automation classes
6
-
7
- """
8
-
9
- from .energycostcalculator import EnergyCostCalculator
10
- from .spothintafi import SpotHintaFi
11
- from .watercirculator import WaterCirculator
12
- from .hotwateroptimizer import HotWaterOptimizer
13
- from .powermeter_simulator import PowerMeterSimulator
14
-
15
- __all__ = [
16
- "EnergyCostCalculator",
17
- "HotWaterOptimizer",
18
- "SpotHintaFi",
19
- "WaterCirculator",
20
- "PowerMeterSimulator",
21
- ]
1
+ """
2
+ Description
3
+ ===========
4
+
5
+ Juham - Juha's Ultimate Home Automation classes
6
+
7
+ """
8
+
9
+ from .energycostcalculator import EnergyCostCalculator
10
+ from .spothintafi import SpotHintaFi
11
+ from .watercirculator import WaterCirculator
12
+ from .heatingoptimizer import HeatingOptimizer
13
+ from .energybalancer import EnergyBalancer
14
+ from .powermeter_simulator import PowerMeterSimulator
15
+
16
+ __all__ = [
17
+ "EnergyCostCalculator",
18
+ "HeatingOptimizer",
19
+ "SpotHintaFi",
20
+ "WaterCirculator",
21
+ "PowerMeterSimulator",
22
+ "EnergyBalancer",
23
+ ]
@@ -0,0 +1,158 @@
1
+ import json
2
+ from typing import Any, Dict
3
+ from typing_extensions import override
4
+
5
+ from juham_core import Juham, timestamp
6
+ from juham_core.timeutils import timestampstr, quantize
7
+ from masterpiece import MqttMsg
8
+
9
+
10
+ class EnergyBalancer(Juham):
11
+ """The energy balancer monitors the balance between produced and consumed energy
12
+ within the balancing interval to determine if there is enough energy available for
13
+ a given energy-consuming device, such as heating radiators, to operate within the
14
+ remaining time of the interval.
15
+
16
+ Any number of energy-consuming devices can be connected to the energy balancer.
17
+ The energy balancer is typically used in conjunction with a power meter that reads
18
+ the total power consumption of the house. The energy balancer uses the power meter
19
+ """
20
+
21
+ #: Description of the attribute
22
+ energy_balancing_interval: int = 3600
23
+ radiator_power: float = 3000
24
+ timezone: str = "Europe/Helsinki"
25
+
26
+ def __init__(self, name: str = "energybalancer") -> None:
27
+ """Initialize the energy balancer.
28
+
29
+ Args:
30
+ name (str): name of the heating radiator
31
+ power (float): power of the consumer in watts
32
+ """
33
+ super().__init__(name)
34
+
35
+ self.topic_in_powerconsumption = self.make_topic_name("powerconsumption")
36
+ self.topic_in_net_energy_balance = self.make_topic_name("net_energy_balance")
37
+ self.topic_out_energybalance = self.make_topic_name("energybalance")
38
+ self.net_energy_balance: float = 0.0 # Energy balance in joules (watt-seconds)
39
+ self.net_energy_balance_ts: float = -1
40
+ self.needed_energy: float = self.energy_balancing_interval * self.radiator_power
41
+ self.net_energy_balancing_mode: bool = False
42
+
43
+ @override
44
+ def on_connect(self, client: object, userdata: Any, flags: int, rc: int) -> None:
45
+ super().on_connect(client, userdata, flags, rc)
46
+ if rc == 0:
47
+ self.subscribe(self.topic_in_net_energy_balance)
48
+
49
+ @override
50
+ def on_message(self, client: object, userdata: Any, msg: MqttMsg) -> None:
51
+ ts: float = timestamp()
52
+
53
+ if msg.topic == self.topic_in_net_energy_balance:
54
+ self.on_power(json.loads(msg.payload.decode()), ts)
55
+ else:
56
+ super().on_message(client, userdata, msg)
57
+
58
+ def on_power(self, m: dict[str, Any], ts: float) -> None:
59
+ """Handle the power consumption. Read the current power balance and accumulate
60
+ to the net energy balance to reflect the energy produced (or consumed) within the
61
+ current time slot.
62
+ Args:
63
+ m (dict[str, Any]): power consumption message
64
+ ts (float): current time
65
+ """
66
+ self.update_energy_balance(m["power"], ts)
67
+
68
+ def update_energy_balance(self, power: float, ts: float) -> None:
69
+ """Update the current net net energy balance. The change in the balance is calculate the
70
+ energy balance, which the time elapsed since the last update, multiplied by the
71
+ power. Positive energy balance means we have produced energy that can be consumed
72
+ at the end of the interval. The target is to use all the energy produced during the
73
+ balancing interval. This method is typically called by the powermeter reading the
74
+ total power consumption of the house
75
+
76
+ Args:
77
+ power (float): power reading from the powermeter. Positive value means
78
+ energy produced, negative value means energy consumed. The value of 0 means
79
+ the house is not consuming or producing energy.
80
+ ts (float): current time in utc seconds
81
+ """
82
+
83
+ # regardless of the mode, if we hit the end of the interval, reset the balance
84
+ quantized_ts: float = ts % self.energy_balancing_interval
85
+ if self.net_energy_balance_ts < 0:
86
+ self.net_energy_balance_ts = quantized_ts
87
+ self.needed_energy = self.energy_balancing_interval - quantized_ts
88
+ elif quantized_ts <= self.net_energy_balance_ts:
89
+ self.reset_net_energy_balance()
90
+ else:
91
+ # update the energy balance with the elapsed time and the power
92
+ elapsed_ts = quantized_ts - self.net_energy_balance_ts
93
+ balance: float = elapsed_ts * power # joules i.e. watt-seconds
94
+ self.net_energy_balance = self.net_energy_balance + balance
95
+ self.net_energy_balance_ts = quantized_ts
96
+ self.needed_energy = (
97
+ self.energy_balancing_interval - quantized_ts
98
+ ) * self.radiator_power
99
+
100
+ if self.net_energy_balancing_mode:
101
+ if self.net_energy_balance <= 0:
102
+ # if we have used all the energy, disable the balancing mode
103
+ self.reset_net_energy_balance()
104
+ else:
105
+ if self.net_energy_balance >= self.needed_energy:
106
+ self.net_energy_balancing_mode = True
107
+ self.publish_energybalance(ts)
108
+
109
+ def consider_net_energy_balance(self, ts: float) -> bool:
110
+ """Check if there is enough energy available for the consumer to heat
111
+ the water in the remaining time within the balancing interval, and switch
112
+ the balancing mode on if sufficient.
113
+
114
+ Args:
115
+ ts (float): current time
116
+
117
+ Returns:
118
+ bool: true if production exceeds the consumption
119
+ """
120
+ return self.net_energy_balancing_mode
121
+
122
+ def reset_net_energy_balance(self) -> None:
123
+ """Reset the net energy balance at the end of the interval."""
124
+ self.net_energy_balance = 0.0
125
+ self.needed_energy = self.energy_balancing_interval * self.radiator_power
126
+ self.net_energy_balance_ts = 0
127
+ self.net_energy_balancing_mode = False
128
+
129
+ def activate_balancing_mode(self, ts: float) -> None:
130
+ """Activate balancing mode when enough energy is available."""
131
+ self.net_energy_balancing_mode = True
132
+ self.info(
133
+ f"{int(self.net_energy_balance/3600)} Wh is enough to supply the radiator, enable"
134
+ )
135
+
136
+ def deactivate_balancing_mode(self) -> None:
137
+ """Deactivate balancing mode when energy is depleted or interval ends."""
138
+ self.net_energy_balancing_mode = False
139
+ self.info("Balance used, or the end of the interval reached, disable")
140
+ self.net_energy_balance = 0.0 # Reset the energy balance at the interval's end
141
+
142
+ def publish_energybalance(self, ts: float) -> None:
143
+ """Publish energy balance information.
144
+
145
+ Args:
146
+ ts (float): current time
147
+ Returns:
148
+ dict: diagnostics information
149
+ """
150
+ m: dict[str, Any] = {
151
+ "Unit": self.name,
152
+ "Mode": self.net_energy_balancing_mode,
153
+ "Rc": self.net_energy_balancing_mode,
154
+ "CurrentBalance": self.net_energy_balance,
155
+ "NeededBalance": self.needed_energy,
156
+ "Timestamp": ts,
157
+ }
158
+ self.publish(self.topic_out_energybalance, json.dumps(m))