juham-automation 0.0.19__py3-none-any.whl → 0.0.27__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.
- juham_automation/__init__.py +42 -38
- juham_automation/automation/__init__.py +23 -21
- juham_automation/automation/energybalancer.py +158 -0
- juham_automation/automation/energycostcalculator.py +267 -266
- juham_automation/automation/{hotwateroptimizer.py → heatingoptimizer.py} +539 -620
- juham_automation/automation/powermeter_simulator.py +139 -139
- juham_automation/automation/spothintafi.py +140 -140
- juham_automation/automation/watercirculator.py +159 -159
- juham_automation/japp.py +53 -49
- juham_automation/ts/__init__.py +27 -25
- juham_automation/ts/electricityprice_ts.py +51 -51
- juham_automation/ts/energybalancer_ts.py +47 -0
- juham_automation/ts/energycostcalculator_ts.py +43 -43
- juham_automation/ts/forecast_ts.py +97 -97
- juham_automation/ts/log_ts.py +57 -57
- juham_automation/ts/power_ts.py +49 -49
- juham_automation/ts/powermeter_ts.py +67 -70
- juham_automation/ts/powerplan_ts.py +45 -45
- juham_automation-0.0.27.dist-info/METADATA +152 -0
- juham_automation-0.0.27.dist-info/RECORD +25 -0
- {juham_automation-0.0.19.dist-info → juham_automation-0.0.27.dist-info}/entry_points.txt +3 -1
- {juham_automation-0.0.19.dist-info → juham_automation-0.0.27.dist-info}/licenses/LICENSE.rst +25 -25
- juham_automation-0.0.19.dist-info/METADATA +0 -106
- juham_automation-0.0.19.dist-info/RECORD +0 -23
- {juham_automation-0.0.19.dist-info → juham_automation-0.0.27.dist-info}/WHEEL +0 -0
- {juham_automation-0.0.19.dist-info → juham_automation-0.0.27.dist-info}/top_level.txt +0 -0
@@ -1,139 +1,139 @@
|
|
1
|
-
import json
|
2
|
-
from typing import Any, Dict, cast
|
3
|
-
from typing_extensions import override
|
4
|
-
|
5
|
-
from masterpiece.mqtt import MqttMsg
|
6
|
-
from juham_core import Juham
|
7
|
-
from juham_core.timeutils import timestamp
|
8
|
-
from juham_core import MasterPieceThread, JuhamThread
|
9
|
-
|
10
|
-
|
11
|
-
class PowerMeterSimulatorThread(MasterPieceThread):
|
12
|
-
"""Thread simulating Energy Meter."""
|
13
|
-
|
14
|
-
_power: float = 1000.0 # W
|
15
|
-
_power_topic: str = "power"
|
16
|
-
_interval: float = 10 # 10 seconds
|
17
|
-
|
18
|
-
def __init__(self) -> None:
|
19
|
-
"""Construct a thread for publishing power data.
|
20
|
-
|
21
|
-
Args:
|
22
|
-
topic (str, optional): MQTT topic to post the sensor readings. Defaults to None.
|
23
|
-
interval (float, optional): Interval specifying how often the sensor is read. Defaults to 60 seconds.
|
24
|
-
"""
|
25
|
-
super().__init__(None)
|
26
|
-
self.current_ts: float = timestamp()
|
27
|
-
|
28
|
-
@classmethod
|
29
|
-
def initialize(cls, power_topic: str, power: float, interval: float) -> None:
|
30
|
-
"""Initialize thread class attributes.
|
31
|
-
|
32
|
-
Args:
|
33
|
-
power_topic (str): topic to publish the energy meter readings
|
34
|
-
power (float): power to be simulated, the default is 1kW
|
35
|
-
interval (float): update interval, the default is 10s
|
36
|
-
"""
|
37
|
-
cls._power = power
|
38
|
-
cls._interval = interval
|
39
|
-
cls._power_topic = power_topic
|
40
|
-
|
41
|
-
@override
|
42
|
-
def update_interval(self) -> float:
|
43
|
-
return self._interval
|
44
|
-
|
45
|
-
def publish_active_power(self, ts: float) -> None:
|
46
|
-
"""Publish the active power, also known as real power. This is that
|
47
|
-
part of the power that can be converted to useful work.
|
48
|
-
|
49
|
-
Args:
|
50
|
-
ts (str): time stamp of the event
|
51
|
-
|
52
|
-
"""
|
53
|
-
dt = ts - self.current_ts
|
54
|
-
self.current_ts = ts
|
55
|
-
|
56
|
-
msg = {
|
57
|
-
"timestamp": ts,
|
58
|
-
"real_a": self._power * dt,
|
59
|
-
"real_b": self._power * dt,
|
60
|
-
"real_c": self._power * dt,
|
61
|
-
"real_total": 3 * self._power * dt,
|
62
|
-
}
|
63
|
-
self.publish(self._power_topic, json.dumps(msg), 1, True)
|
64
|
-
|
65
|
-
@override
|
66
|
-
def update(self) -> bool:
|
67
|
-
super().update()
|
68
|
-
self.publish_active_power(timestamp())
|
69
|
-
return True
|
70
|
-
|
71
|
-
|
72
|
-
class PowerMeterSimulator(JuhamThread):
|
73
|
-
"""Simulator energy meter sensor. Spawns a thread
|
74
|
-
to simulate Shelly PM mqtt messages"""
|
75
|
-
|
76
|
-
workerThreadId = PowerMeterSimulatorThread.get_class_id()
|
77
|
-
update_interval: float = 10
|
78
|
-
power: float = 1000.0
|
79
|
-
|
80
|
-
_POWERMETERSIMULATOR: str = "_powermetersimulator"
|
81
|
-
|
82
|
-
def __init__(
|
83
|
-
self,
|
84
|
-
name: str = "em",
|
85
|
-
interval: float = 0,
|
86
|
-
) -> None:
|
87
|
-
"""Create energy meter simulator.
|
88
|
-
|
89
|
-
Args:
|
90
|
-
name (str, optional): Name of the object. Defaults to 'em'.
|
91
|
-
topic (str, optional): MQTT topic to publish the energy meter reports. Defaults to None.
|
92
|
-
interval (float, optional): interval between events, in seconds. Defaults to None.
|
93
|
-
"""
|
94
|
-
super().__init__(name)
|
95
|
-
self.update_ts: float = 0.0
|
96
|
-
if interval > 0.0:
|
97
|
-
self.update_interval = interval
|
98
|
-
self.power_topic = self.make_topic_name("powerconsumption") # target topic
|
99
|
-
|
100
|
-
@override
|
101
|
-
def on_message(self, client: object, userdata: Any, msg: MqttMsg) -> None:
|
102
|
-
if msg.topic == self.power_topic:
|
103
|
-
em = json.loads(msg.payload.decode())
|
104
|
-
self.on_sensor(em)
|
105
|
-
else:
|
106
|
-
super().on_message(client, userdata, msg)
|
107
|
-
|
108
|
-
def on_sensor(self, em: dict[Any, Any]) -> None:
|
109
|
-
"""Handle data coming from the energy meter.
|
110
|
-
|
111
|
-
Simply log the event to indicate the presense of simulated device.
|
112
|
-
Args:
|
113
|
-
em (dict): data from the sensor
|
114
|
-
"""
|
115
|
-
self.debug(f"Simulated power meter sensor {em}")
|
116
|
-
|
117
|
-
@override
|
118
|
-
def run(self) -> None:
|
119
|
-
PowerMeterSimulatorThread.initialize(
|
120
|
-
self.power_topic, self.power, self.update_interval
|
121
|
-
)
|
122
|
-
self.worker = cast(
|
123
|
-
PowerMeterSimulatorThread,
|
124
|
-
Juham.instantiate(PowerMeterSimulatorThread.get_class_id()),
|
125
|
-
)
|
126
|
-
super().run()
|
127
|
-
|
128
|
-
@override
|
129
|
-
def to_dict(self) -> Dict[str, Any]:
|
130
|
-
data: Dict[str, Any] = super().to_dict()
|
131
|
-
data[self._POWERMETERSIMULATOR] = {"power_topic": self.power_topic}
|
132
|
-
return data
|
133
|
-
|
134
|
-
@override
|
135
|
-
def from_dict(self, data: Dict[str, Any]) -> None:
|
136
|
-
super().from_dict(data)
|
137
|
-
if self._POWERMETERSIMULATOR in data:
|
138
|
-
for key, value in data[self._POWERMETERSIMULATOR].items():
|
139
|
-
setattr(self, key, value)
|
1
|
+
import json
|
2
|
+
from typing import Any, Dict, cast
|
3
|
+
from typing_extensions import override
|
4
|
+
|
5
|
+
from masterpiece.mqtt import MqttMsg
|
6
|
+
from juham_core import Juham
|
7
|
+
from juham_core.timeutils import timestamp
|
8
|
+
from juham_core import MasterPieceThread, JuhamThread
|
9
|
+
|
10
|
+
|
11
|
+
class PowerMeterSimulatorThread(MasterPieceThread):
|
12
|
+
"""Thread simulating Energy Meter."""
|
13
|
+
|
14
|
+
_power: float = 1000.0 # W
|
15
|
+
_power_topic: str = "power"
|
16
|
+
_interval: float = 10 # 10 seconds
|
17
|
+
|
18
|
+
def __init__(self) -> None:
|
19
|
+
"""Construct a thread for publishing power data.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
topic (str, optional): MQTT topic to post the sensor readings. Defaults to None.
|
23
|
+
interval (float, optional): Interval specifying how often the sensor is read. Defaults to 60 seconds.
|
24
|
+
"""
|
25
|
+
super().__init__(None)
|
26
|
+
self.current_ts: float = timestamp()
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def initialize(cls, power_topic: str, power: float, interval: float) -> None:
|
30
|
+
"""Initialize thread class attributes.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
power_topic (str): topic to publish the energy meter readings
|
34
|
+
power (float): power to be simulated, the default is 1kW
|
35
|
+
interval (float): update interval, the default is 10s
|
36
|
+
"""
|
37
|
+
cls._power = power
|
38
|
+
cls._interval = interval
|
39
|
+
cls._power_topic = power_topic
|
40
|
+
|
41
|
+
@override
|
42
|
+
def update_interval(self) -> float:
|
43
|
+
return self._interval
|
44
|
+
|
45
|
+
def publish_active_power(self, ts: float) -> None:
|
46
|
+
"""Publish the active power, also known as real power. This is that
|
47
|
+
part of the power that can be converted to useful work.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
ts (str): time stamp of the event
|
51
|
+
|
52
|
+
"""
|
53
|
+
dt = ts - self.current_ts
|
54
|
+
self.current_ts = ts
|
55
|
+
|
56
|
+
msg = {
|
57
|
+
"timestamp": ts,
|
58
|
+
"real_a": self._power * dt,
|
59
|
+
"real_b": self._power * dt,
|
60
|
+
"real_c": self._power * dt,
|
61
|
+
"real_total": 3 * self._power * dt,
|
62
|
+
}
|
63
|
+
self.publish(self._power_topic, json.dumps(msg), 1, True)
|
64
|
+
|
65
|
+
@override
|
66
|
+
def update(self) -> bool:
|
67
|
+
super().update()
|
68
|
+
self.publish_active_power(timestamp())
|
69
|
+
return True
|
70
|
+
|
71
|
+
|
72
|
+
class PowerMeterSimulator(JuhamThread):
|
73
|
+
"""Simulator energy meter sensor. Spawns a thread
|
74
|
+
to simulate Shelly PM mqtt messages"""
|
75
|
+
|
76
|
+
workerThreadId = PowerMeterSimulatorThread.get_class_id()
|
77
|
+
update_interval: float = 10
|
78
|
+
power: float = 1000.0
|
79
|
+
|
80
|
+
_POWERMETERSIMULATOR: str = "_powermetersimulator"
|
81
|
+
|
82
|
+
def __init__(
|
83
|
+
self,
|
84
|
+
name: str = "em",
|
85
|
+
interval: float = 0,
|
86
|
+
) -> None:
|
87
|
+
"""Create energy meter simulator.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
name (str, optional): Name of the object. Defaults to 'em'.
|
91
|
+
topic (str, optional): MQTT topic to publish the energy meter reports. Defaults to None.
|
92
|
+
interval (float, optional): interval between events, in seconds. Defaults to None.
|
93
|
+
"""
|
94
|
+
super().__init__(name)
|
95
|
+
self.update_ts: float = 0.0
|
96
|
+
if interval > 0.0:
|
97
|
+
self.update_interval = interval
|
98
|
+
self.power_topic = self.make_topic_name("powerconsumption") # target topic
|
99
|
+
|
100
|
+
@override
|
101
|
+
def on_message(self, client: object, userdata: Any, msg: MqttMsg) -> None:
|
102
|
+
if msg.topic == self.power_topic:
|
103
|
+
em = json.loads(msg.payload.decode())
|
104
|
+
self.on_sensor(em)
|
105
|
+
else:
|
106
|
+
super().on_message(client, userdata, msg)
|
107
|
+
|
108
|
+
def on_sensor(self, em: dict[Any, Any]) -> None:
|
109
|
+
"""Handle data coming from the energy meter.
|
110
|
+
|
111
|
+
Simply log the event to indicate the presense of simulated device.
|
112
|
+
Args:
|
113
|
+
em (dict): data from the sensor
|
114
|
+
"""
|
115
|
+
self.debug(f"Simulated power meter sensor {em}")
|
116
|
+
|
117
|
+
@override
|
118
|
+
def run(self) -> None:
|
119
|
+
PowerMeterSimulatorThread.initialize(
|
120
|
+
self.power_topic, self.power, self.update_interval
|
121
|
+
)
|
122
|
+
self.worker = cast(
|
123
|
+
PowerMeterSimulatorThread,
|
124
|
+
Juham.instantiate(PowerMeterSimulatorThread.get_class_id()),
|
125
|
+
)
|
126
|
+
super().run()
|
127
|
+
|
128
|
+
@override
|
129
|
+
def to_dict(self) -> Dict[str, Any]:
|
130
|
+
data: Dict[str, Any] = super().to_dict()
|
131
|
+
data[self._POWERMETERSIMULATOR] = {"power_topic": self.power_topic}
|
132
|
+
return data
|
133
|
+
|
134
|
+
@override
|
135
|
+
def from_dict(self, data: Dict[str, Any]) -> None:
|
136
|
+
super().from_dict(data)
|
137
|
+
if self._POWERMETERSIMULATOR in data:
|
138
|
+
for key, value in data[self._POWERMETERSIMULATOR].items():
|
139
|
+
setattr(self, key, value)
|
@@ -1,140 +1,140 @@
|
|
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
|
+
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)
|