juham-automation 0.0.31__py3-none-any.whl → 0.0.34__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/automation/energybalancer.py +25 -8
- juham_automation/automation/heatingoptimizer.py +121 -46
- {juham_automation-0.0.31.dist-info → juham_automation-0.0.34.dist-info}/METADATA +1 -1
- {juham_automation-0.0.31.dist-info → juham_automation-0.0.34.dist-info}/RECORD +8 -8
- {juham_automation-0.0.31.dist-info → juham_automation-0.0.34.dist-info}/WHEEL +1 -1
- {juham_automation-0.0.31.dist-info → juham_automation-0.0.34.dist-info}/entry_points.txt +0 -0
- {juham_automation-0.0.31.dist-info → juham_automation-0.0.34.dist-info}/licenses/LICENSE.rst +0 -0
- {juham_automation-0.0.31.dist-info → juham_automation-0.0.34.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,7 @@ class Consumer:
|
|
14
14
|
|
15
15
|
"""
|
16
16
|
|
17
|
-
def __init__(self, name: str, power) -> None:
|
17
|
+
def __init__(self, name: str, power: float, weight: float) -> None:
|
18
18
|
"""Initialize the consumer
|
19
19
|
|
20
20
|
Args:
|
@@ -25,6 +25,7 @@ class Consumer:
|
|
25
25
|
self.power: float = power
|
26
26
|
self.start: float = 0.0
|
27
27
|
self.stop: float = 0.0
|
28
|
+
self.weight: float = weight
|
28
29
|
|
29
30
|
|
30
31
|
class EnergyBalancer(Juham):
|
@@ -102,8 +103,10 @@ class EnergyBalancer(Juham):
|
|
102
103
|
m (dict[str, Any]): power consumer message
|
103
104
|
ts (float): current time
|
104
105
|
"""
|
105
|
-
self.consumers[m["Unit"]] = Consumer(m["Unit"], m["Power"])
|
106
|
-
self.info(
|
106
|
+
self.consumers[m["Unit"]] = Consumer(m["Unit"], m["Power"], m["Weight"])
|
107
|
+
self.info(
|
108
|
+
f"Consumer {m['Unit']} added, power: {m['Power']}, weight: {m['Weight']}"
|
109
|
+
)
|
107
110
|
|
108
111
|
def update_energy_balance(self, power: float, ts: float) -> None:
|
109
112
|
"""Update the current net net energy balance. The change in the balance is calculate the
|
@@ -164,8 +167,16 @@ class EnergyBalancer(Juham):
|
|
164
167
|
remaining_ts_consumer: float = (
|
165
168
|
self.energy_balancing_interval - interval_ts
|
166
169
|
) / num_consumers
|
170
|
+
total_weight: float = 0.0
|
167
171
|
for consumer in self.consumers.values():
|
168
|
-
|
172
|
+
total_weight += consumer.weight
|
173
|
+
equal_weight: float = total_weight / num_consumers
|
174
|
+
for consumer in self.consumers.values():
|
175
|
+
required_power += (
|
176
|
+
(consumer.weight / equal_weight)
|
177
|
+
* consumer.power
|
178
|
+
* remaining_ts_consumer
|
179
|
+
)
|
169
180
|
return required_power
|
170
181
|
|
171
182
|
def initialize_consumer_timelines(self, ts: float) -> None:
|
@@ -185,9 +196,16 @@ class EnergyBalancer(Juham):
|
|
185
196
|
self.energy_balancing_interval - interval_ts
|
186
197
|
) / num_consumers
|
187
198
|
|
199
|
+
total_weight: float = 0.0
|
200
|
+
for consumer in self.consumers.values():
|
201
|
+
total_weight += consumer.weight
|
202
|
+
equal_weight: float = total_weight / num_consumers
|
203
|
+
|
188
204
|
for consumer in self.consumers.values():
|
189
205
|
consumer.start = interval_ts
|
190
|
-
consumer.stop =
|
206
|
+
consumer.stop = (
|
207
|
+
interval_ts + secs_per_consumer * consumer.weight / equal_weight
|
208
|
+
)
|
191
209
|
interval_ts += secs_per_consumer
|
192
210
|
|
193
211
|
def publish_energybalance(self, ts: float) -> None:
|
@@ -209,18 +227,18 @@ class EnergyBalancer(Juham):
|
|
209
227
|
}
|
210
228
|
self.publish(self.topic_out_diagnostics, json.dumps(m))
|
211
229
|
|
212
|
-
|
213
230
|
# publish consumer statuses to control consumers
|
214
231
|
num_consumers: int = len(self.consumers)
|
215
232
|
if num_consumers == 0:
|
216
233
|
return # If there are no consumers, we simply do nothing
|
217
234
|
interval_ts = ts % self.energy_balancing_interval
|
218
235
|
for consumer in self.consumers.values():
|
219
|
-
m
|
236
|
+
m = {
|
220
237
|
"EnergyBalancer": self.name,
|
221
238
|
"Unit": consumer.name,
|
222
239
|
"Power": consumer.power,
|
223
240
|
"Mode": consumer.start <= interval_ts < consumer.stop,
|
241
|
+
"Weight": consumer.weight,
|
224
242
|
"Timestamp": ts,
|
225
243
|
}
|
226
244
|
self.publish(self.topic_out_status, json.dumps(m))
|
@@ -263,4 +281,3 @@ class EnergyBalancer(Juham):
|
|
263
281
|
self.net_energy_balancing_mode = False
|
264
282
|
self.info("Balance used, or the end of the interval reached, disable")
|
265
283
|
self.net_energy_balance = 0.0 # Reset the energy balance at the interval's end
|
266
|
-
|
@@ -1,3 +1,4 @@
|
|
1
|
+
from datetime import datetime
|
1
2
|
import json
|
2
3
|
from typing import Any
|
3
4
|
from typing_extensions import override
|
@@ -16,31 +17,66 @@ from juham_core.timeutils import (
|
|
16
17
|
|
17
18
|
class HeatingOptimizer(Juham):
|
18
19
|
"""Automation class for optimized control of temperature driven home energy consumers e.g hot
|
19
|
-
water radiators. Reads spot prices, electricity forecast,
|
20
|
+
water radiators. Reads spot prices, solar electricity forecast, power meter and
|
21
|
+
temperature to minimize electricity bill.
|
20
22
|
|
21
23
|
Represents a heating system that knows the power rating of its radiator (e.g., 3kW).
|
24
|
+
Any number of heating devices can be controlled, each with its own temperature, schedule and electricity price ratings
|
25
|
+
|
22
26
|
The system subscribes to the 'power' topic to track the current power balance. If the solar panels
|
23
27
|
generate more energy than is being consumed, the optimizer activates a relay to ensure that all excess energy
|
24
|
-
produced within that
|
28
|
+
produced within that balancing interval is used for heating. The goal is to achieve a net zero energy balance for each hour,
|
25
29
|
ensuring that any surplus energy from the solar panels is fully utilized.
|
26
30
|
|
27
31
|
Computes also UOI - optimization utilization index for each hour, based on the spot price and the solar power forecast.
|
28
32
|
For negative energy balance this determines when energy is consumed. Value of 0 means the hour is expensive, value of 1 means
|
29
33
|
the hour is free. The UOI threshold determines the slots that are allowed to be consumed.
|
30
|
-
|
31
34
|
"""
|
32
35
|
|
33
|
-
maximum_boiler_temperature: float = 70
|
34
|
-
minimum_boiler_temperature: float = 40
|
35
36
|
energy_balancing_interval: float = 3600
|
36
|
-
|
37
|
-
|
37
|
+
"""Energy balancing interval, as regulated by the industry/converment. In seconds"""
|
38
|
+
|
39
|
+
radiator_power: float = 6000 # W
|
40
|
+
"""Radiator power in Watts. This is the maximum power that the radiator can consume."""
|
41
|
+
|
38
42
|
heating_hours_per_day: float = 4
|
43
|
+
""" Number of hours per day the radiator is allowed to heat."""
|
44
|
+
|
39
45
|
schedule_start_hour: float = 0
|
46
|
+
"""Start hour of the heating schedule."""
|
47
|
+
|
40
48
|
schedule_stop_hour: float = 0
|
49
|
+
"""Stop hour of the heating schedule. Heating is allowed only between these two hours."""
|
50
|
+
|
41
51
|
timezone: str = "Europe/Helsinki"
|
52
|
+
""" Timezone of the heating system. This is used to convert UTC timestamps to local time."""
|
53
|
+
|
42
54
|
expected_average_price: float = 0.2
|
55
|
+
"""Expected average price of electricity, beyond which the heating is avoided."""
|
56
|
+
|
43
57
|
uoi_threshold: float = 0.8
|
58
|
+
"""Utilization Optimization Index threshold. This is the minimum UOI value that is allowed for the heating to be activated."""
|
59
|
+
|
60
|
+
balancing_weight: float = 1.0
|
61
|
+
"""Weight determining how large a share of the time slot a consumer receives compared to others ."""
|
62
|
+
|
63
|
+
temperature_limits: dict[int, tuple[float, float]] = {
|
64
|
+
1: (20.0, 60.0), # January
|
65
|
+
2: (20.0, 60.0), # February
|
66
|
+
3: (20.0, 60.0), # March
|
67
|
+
4: (20.0, 50.0), # April
|
68
|
+
5: (20.0, 40.0), # May
|
69
|
+
6: (20.0, 22.0), # June
|
70
|
+
7: (20.0, 22.0), # July
|
71
|
+
8: (20.0, 22.0), # August
|
72
|
+
9: (20.0, 50.0), # September
|
73
|
+
10: (20.0, 60.0), # October
|
74
|
+
11: (20.0, 60.0), # November
|
75
|
+
12: (20.0, 60.0), # December
|
76
|
+
}
|
77
|
+
"""Temperature limits for each month. The minimum temperature is maintained regardless of the cost.
|
78
|
+
The limits are defined as a dictionary where the keys are month numbers (1-12)
|
79
|
+
and the values are tuples of (min_temp, max_temp). The min_temp and max_temp values are in degrees Celsius."""
|
44
80
|
|
45
81
|
def __init__(
|
46
82
|
self,
|
@@ -84,9 +120,9 @@ class HeatingOptimizer(Juham):
|
|
84
120
|
self.start_hour = start_hour
|
85
121
|
self.spot_limit = spot_limit
|
86
122
|
|
87
|
-
self.
|
88
|
-
self.
|
89
|
-
self.
|
123
|
+
self.topic_in_spot = self.make_topic_name("spot")
|
124
|
+
self.topic_in_forecast = self.make_topic_name("forecast")
|
125
|
+
self.topic_in_temperature = self.make_topic_name(temperature_sensor)
|
90
126
|
self.topic_powerplan = self.make_topic_name("powerplan")
|
91
127
|
self.topic_in_energybalance = self.make_topic_name("energybalance/status")
|
92
128
|
self.topic_out_energybalance = self.make_topic_name("energybalance/consumers")
|
@@ -107,9 +143,9 @@ class HeatingOptimizer(Juham):
|
|
107
143
|
def on_connect(self, client: object, userdata: Any, flags: int, rc: int) -> None:
|
108
144
|
super().on_connect(client, userdata, flags, rc)
|
109
145
|
if rc == 0:
|
110
|
-
self.subscribe(self.
|
111
|
-
self.subscribe(self.
|
112
|
-
self.subscribe(self.
|
146
|
+
self.subscribe(self.topic_in_spot)
|
147
|
+
self.subscribe(self.topic_in_forecast)
|
148
|
+
self.subscribe(self.topic_in_temperature)
|
113
149
|
self.subscribe(self.topic_in_energybalance)
|
114
150
|
self.register_as_consumer()
|
115
151
|
|
@@ -120,9 +156,25 @@ class HeatingOptimizer(Juham):
|
|
120
156
|
consumer: dict[str, Any] = {
|
121
157
|
"Unit": self.name,
|
122
158
|
"Power": self.radiator_power,
|
159
|
+
"Weight": self.balancing_weight,
|
123
160
|
}
|
124
161
|
self.publish(self.topic_out_energybalance, json.dumps(consumer), 1, False)
|
125
|
-
self.info(
|
162
|
+
self.info(
|
163
|
+
f"Registered {self.name} as consumer with {self.radiator_power}W power",
|
164
|
+
"",
|
165
|
+
)
|
166
|
+
|
167
|
+
# Function to get the temperature limits based on the current month
|
168
|
+
def get_temperature_limits_for_current_month(self) -> tuple[float, float]:
|
169
|
+
"""Get the temperature limits for the current month.
|
170
|
+
The limits are defined in a dictionary where the keys are month numbers (1-12)
|
171
|
+
and the values are tuples of (min_temp, max_temp).
|
172
|
+
Returns: tuple: (min_temp, max_temp)
|
173
|
+
"""
|
174
|
+
current_month: int = datetime.now().month
|
175
|
+
# Get the min and max temperatures for the current month
|
176
|
+
min_temp, max_temp = self.temperature_limits[current_month]
|
177
|
+
return min_temp, max_temp
|
126
178
|
|
127
179
|
def sort_by_rank(
|
128
180
|
self, hours: list[dict[str, Any]], ts_utc_now: float
|
@@ -170,7 +222,7 @@ class HeatingOptimizer(Juham):
|
|
170
222
|
reverse=True,
|
171
223
|
)
|
172
224
|
self.debug(
|
173
|
-
f"
|
225
|
+
f"{self.name} sorted {len(sh)} days of forecast starting at {timestampstr(ts_utc)}"
|
174
226
|
)
|
175
227
|
ranked_hours: list[dict[str, Any]] = []
|
176
228
|
|
@@ -178,7 +230,9 @@ class HeatingOptimizer(Juham):
|
|
178
230
|
utc_ts: float = float(h["ts"])
|
179
231
|
if utc_ts >= ts_utc:
|
180
232
|
ranked_hours.append(h)
|
181
|
-
self.debug(
|
233
|
+
self.debug(
|
234
|
+
f"{self.name} forecast sorted for the next {str(len(ranked_hours))} hours"
|
235
|
+
)
|
182
236
|
return ranked_hours
|
183
237
|
|
184
238
|
def on_spot(self, m: list[dict[str, Any]], ts_quantized: float) -> None:
|
@@ -206,7 +260,7 @@ class HeatingOptimizer(Juham):
|
|
206
260
|
|
207
261
|
self.ranked_solarpower = self.sort_by_power(forecast, ts_utc_quantized)
|
208
262
|
self.debug(
|
209
|
-
f"
|
263
|
+
f"{self.name} solar energy forecast received and ranked for {len(self.ranked_solarpower)} hours"
|
210
264
|
)
|
211
265
|
self.power_plan = [] # reset power plan, it depends on forecast
|
212
266
|
|
@@ -215,13 +269,13 @@ class HeatingOptimizer(Juham):
|
|
215
269
|
m = None
|
216
270
|
ts: float = timestamp()
|
217
271
|
ts_utc_quantized: float = quantize(3600, ts - 3600)
|
218
|
-
if msg.topic == self.
|
272
|
+
if msg.topic == self.topic_in_spot:
|
219
273
|
self.on_spot(json.loads(msg.payload.decode()), ts_utc_quantized)
|
220
274
|
return
|
221
|
-
elif msg.topic == self.
|
275
|
+
elif msg.topic == self.topic_in_forecast:
|
222
276
|
self.on_forecast(json.loads(msg.payload.decode()), ts_utc_quantized)
|
223
277
|
return
|
224
|
-
elif msg.topic == self.
|
278
|
+
elif msg.topic == self.topic_in_temperature:
|
225
279
|
m = json.loads(msg.payload.decode())
|
226
280
|
self.current_temperature = m["temperature"]
|
227
281
|
elif msg.topic == self.topic_in_energybalance:
|
@@ -250,24 +304,24 @@ class HeatingOptimizer(Juham):
|
|
250
304
|
self.relay_started_ts = ts_utc_now
|
251
305
|
|
252
306
|
if not self.ranked_spot_prices:
|
253
|
-
self.debug("
|
307
|
+
self.debug(f"{self.name} waiting spot prices...", "")
|
254
308
|
return
|
255
309
|
|
256
310
|
if not self.power_plan:
|
257
311
|
self.power_plan = self.create_power_plan()
|
258
312
|
self.heating_plan = []
|
259
313
|
self.info(
|
260
|
-
f"
|
314
|
+
f"{self.name} power plan of length {len(self.power_plan)} created",
|
261
315
|
str(self.power_plan),
|
262
316
|
)
|
263
317
|
|
264
318
|
if not self.power_plan:
|
265
|
-
self.error("
|
319
|
+
self.error(f"{self.name} failed to create a power plan", "")
|
266
320
|
return
|
267
321
|
|
268
322
|
if len(self.power_plan) < 3:
|
269
323
|
self.warning(
|
270
|
-
f"
|
324
|
+
f"{self.name} has suspiciously short {len(self.power_plan)} power plan, waiting for more data ..",
|
271
325
|
"",
|
272
326
|
)
|
273
327
|
self.heating_plan = []
|
@@ -276,21 +330,25 @@ class HeatingOptimizer(Juham):
|
|
276
330
|
|
277
331
|
if not self.ranked_solarpower or len(self.ranked_solarpower) < 4:
|
278
332
|
self.warning(
|
279
|
-
f"
|
333
|
+
f"{self.name} short of forecast {len(self.ranked_solarpower)}, optimization compromised..",
|
280
334
|
"",
|
281
335
|
)
|
282
336
|
|
283
337
|
if not self.heating_plan:
|
284
338
|
self.heating_plan = self.create_heating_plan()
|
285
339
|
if not self.heating_plan:
|
286
|
-
self.error("
|
340
|
+
self.error(f"{self.name} failed to create heating plan")
|
287
341
|
return
|
288
342
|
else:
|
289
343
|
self.info(
|
290
|
-
f"
|
344
|
+
f"{self.name} heating plan of length {len(self.heating_plan)} created",
|
345
|
+
"",
|
291
346
|
)
|
292
347
|
if len(self.heating_plan) < 3:
|
293
|
-
self.
|
348
|
+
self.warning(
|
349
|
+
f"{self.name} has too short heating plan {len(self.heating_plan)}, no can do",
|
350
|
+
"",
|
351
|
+
)
|
294
352
|
self.heating_plan = []
|
295
353
|
self.power_plan = []
|
296
354
|
return
|
@@ -304,7 +362,7 @@ class HeatingOptimizer(Juham):
|
|
304
362
|
}
|
305
363
|
self.publish(self.topic_out_power, json.dumps(heat), 1, False)
|
306
364
|
self.info(
|
307
|
-
f"
|
365
|
+
f"{self.name} relay changed to {relay} at {timestampstr(ts_utc_now)}",
|
308
366
|
"",
|
309
367
|
)
|
310
368
|
self.current_relay_state = relay
|
@@ -322,7 +380,6 @@ class HeatingOptimizer(Juham):
|
|
322
380
|
if m["Unit"] == self.name:
|
323
381
|
self.net_energy_balance_mode = m["Mode"]
|
324
382
|
|
325
|
-
|
326
383
|
def consider_heating(self, ts: float) -> int:
|
327
384
|
"""Consider whether the target boiler needs heating. Check first if the solar
|
328
385
|
energy is enough to heat the water the remaining time in the current slot.
|
@@ -337,28 +394,45 @@ class HeatingOptimizer(Juham):
|
|
337
394
|
|
338
395
|
# check if we have excess energy to spent within the current slot
|
339
396
|
if self.net_energy_balance_mode:
|
340
|
-
self.
|
397
|
+
self.debug(
|
398
|
+
f"{self.name} with positive net energy balance, spend it for heating"
|
399
|
+
)
|
341
400
|
return 1
|
342
401
|
|
343
|
-
# no free energy available, don't spend if the current temperature is already high enough
|
344
|
-
if self.current_temperature > self.maximum_boiler_temperature:
|
345
|
-
self.info(
|
346
|
-
f"Current temperature {self.current_temperature}C already beyond max {self.maximum_boiler_temperature}C"
|
347
|
-
)
|
348
|
-
return 0
|
349
402
|
hour = timestamp_hour(ts)
|
403
|
+
state: int = -1
|
350
404
|
|
351
405
|
# check if we are within the heating plan and see what the plan says
|
352
406
|
for pp in self.heating_plan:
|
353
407
|
ppts: float = pp["Timestamp"]
|
354
408
|
h: float = timestamp_hour(ppts)
|
355
409
|
if h == hour:
|
356
|
-
|
410
|
+
state = pp["State"]
|
411
|
+
break
|
357
412
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
413
|
+
if state == -1:
|
414
|
+
self.error(f"{self.name} cannot find heating plan for hour {hour}")
|
415
|
+
return 0
|
416
|
+
|
417
|
+
min_temp, max_temp = self.get_temperature_limits_for_current_month()
|
418
|
+
self.debug(
|
419
|
+
f"{self.name} month's temperature limits: Min = {min_temp}°C, Max = {max_temp}°C"
|
420
|
+
)
|
421
|
+
|
422
|
+
# don't heat if the current temperature is already high enough
|
423
|
+
if self.current_temperature > max_temp:
|
424
|
+
self.debug(
|
425
|
+
f"{self.name} plan {state}, temp {self.current_temperature}°C already beyond max {max_temp}°C"
|
426
|
+
)
|
427
|
+
return 0
|
428
|
+
# heat if the current temperature is below the required minimum
|
429
|
+
if self.current_temperature < min_temp:
|
430
|
+
self.debug(
|
431
|
+
f"{self.name} plan {state}, temp {self.current_temperature}°C below min {min_temp}°C"
|
432
|
+
)
|
433
|
+
return 1
|
434
|
+
|
435
|
+
return state # 1 = heating, 0 = not heating
|
362
436
|
|
363
437
|
# compute utilization optimization index
|
364
438
|
def compute_uoi(
|
@@ -425,7 +499,7 @@ class HeatingOptimizer(Juham):
|
|
425
499
|
ts_utc_quantized = quantize(3600, timestamp() - 3600)
|
426
500
|
starts: str = timestampstr(ts_utc_quantized)
|
427
501
|
self.info(
|
428
|
-
f"
|
502
|
+
f"{self.name} created power plan starting at {starts} with {len(self.ranked_spot_prices)} hours of spot prices",
|
429
503
|
"",
|
430
504
|
)
|
431
505
|
|
@@ -438,10 +512,11 @@ class HeatingOptimizer(Juham):
|
|
438
512
|
)
|
439
513
|
|
440
514
|
if len(spots) == 0:
|
441
|
-
self.
|
515
|
+
self.info(
|
442
516
|
f"No spot prices initialized yet, can't proceed",
|
443
517
|
"",
|
444
518
|
)
|
519
|
+
return []
|
445
520
|
self.info(
|
446
521
|
f"Have spot prices for the next {len(spots)} hours",
|
447
522
|
"",
|
@@ -497,7 +572,7 @@ class HeatingOptimizer(Juham):
|
|
497
572
|
|
498
573
|
shplan = sorted(hplan, key=lambda x: x["FOM"], reverse=True)
|
499
574
|
|
500
|
-
self.debug(f"
|
575
|
+
self.debug(f"{self.name} powerplan starts {starts} up to {len(shplan)} hours")
|
501
576
|
return shplan
|
502
577
|
|
503
578
|
def enable_relay(
|
@@ -547,5 +622,5 @@ class HeatingOptimizer(Juham):
|
|
547
622
|
heating_plan.append(heat)
|
548
623
|
hour = hour + 1
|
549
624
|
|
550
|
-
self.info(f"
|
625
|
+
self.info(f"{self.name} heating plan of {len(heating_plan)} hours created", "")
|
551
626
|
return heating_plan
|
@@ -2,9 +2,9 @@ juham_automation/__init__.py,sha256=32BL36bhT7OaSw22H7st-7-3IXcFM2Pf5js80hNA8W0,
|
|
2
2
|
juham_automation/japp.py,sha256=L2u1mfKvun2fiXhB3AEJD9zMDcdFZ3_doXZYJJzu9tg,1646
|
3
3
|
juham_automation/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
4
4
|
juham_automation/automation/__init__.py,sha256=uxkIrcRSp1cFikn-oBRtQ8XiT9cSf7xjm3CS1RN7lAQ,522
|
5
|
-
juham_automation/automation/energybalancer.py,sha256=
|
5
|
+
juham_automation/automation/energybalancer.py,sha256=VcoDYtQbZC54Fv6u0EfM6DcPYupd9nlDT0Znn3-6ml4,11533
|
6
6
|
juham_automation/automation/energycostcalculator.py,sha256=v30wxRpuY2gGBSMJifrFRTjsRU9t-iCiq33Vds7s3O8,10877
|
7
|
-
juham_automation/automation/heatingoptimizer.py,sha256=
|
7
|
+
juham_automation/automation/heatingoptimizer.py,sha256=X7BY40NoCes3aRz5tKRuEHMRi3TVtcxkgIYffp3_k14,24601
|
8
8
|
juham_automation/automation/powermeter_simulator.py,sha256=3WZcjByRTdqnC77l7LjP-TEjmZ8XBEO4hClYsrjxmBE,4549
|
9
9
|
juham_automation/automation/spothintafi.py,sha256=cZbi7w2fVweHX_fh1r5MTjGdesX9wDQta2mfVjtiwvw,4331
|
10
10
|
juham_automation/automation/watercirculator.py,sha256=a8meMNaONbHcIH3y0vP0UulJc1-gZiLZpw7H8kAOreY,6410
|
@@ -17,9 +17,9 @@ juham_automation/ts/log_ts.py,sha256=XsNaazuPmRUZLUqxU0DZae_frtT6kAFcXJTc598CtOA
|
|
17
17
|
juham_automation/ts/power_ts.py,sha256=e7bSeZjitY4C_gLup9L0NjvU_WnQsl3ayDhVShj32KY,1399
|
18
18
|
juham_automation/ts/powermeter_ts.py,sha256=gXzfK2S4SzrQ9GqM0tsLaV6z_vYmTkBatTcaivASSXs,2188
|
19
19
|
juham_automation/ts/powerplan_ts.py,sha256=LZeE7TnzPCDaugggKlaV-K48lDwwnC1ZNum50JYAWaY,1482
|
20
|
-
juham_automation-0.0.
|
21
|
-
juham_automation-0.0.
|
22
|
-
juham_automation-0.0.
|
23
|
-
juham_automation-0.0.
|
24
|
-
juham_automation-0.0.
|
25
|
-
juham_automation-0.0.
|
20
|
+
juham_automation-0.0.34.dist-info/licenses/LICENSE.rst,sha256=QVHD5V5_HSys2PdPdig_xKggDj8cGX33ALKqRsYyjtI,1089
|
21
|
+
juham_automation-0.0.34.dist-info/METADATA,sha256=LTcnFtw37Rf299ItV-STdD0g4hCaykQW0Gf7YUMjGg0,6837
|
22
|
+
juham_automation-0.0.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
+
juham_automation-0.0.34.dist-info/entry_points.txt,sha256=h-KzuKjmGPd4_iX_oiGvxx4IEc97dVbGGlhdh5ctbpI,605
|
24
|
+
juham_automation-0.0.34.dist-info/top_level.txt,sha256=jfohvtocvX_gfT21AhJk7Iay5ZiQsS3HzrDjF7S4Qp0,17
|
25
|
+
juham_automation-0.0.34.dist-info/RECORD,,
|
File without changes
|
{juham_automation-0.0.31.dist-info → juham_automation-0.0.34.dist-info}/licenses/LICENSE.rst
RENAMED
File without changes
|
File without changes
|