commit | author | age
|
425bf7
|
1 |
import abc |
JK |
2 |
import collections |
|
3 |
import datetime |
|
4 |
from math import floor |
|
5 |
|
|
6 |
import math |
|
7 |
import pytz |
|
8 |
import solartime |
|
9 |
|
|
10 |
|
|
11 |
solartime.basestring = str |
|
12 |
|
|
13 |
|
|
14 |
class DataReader(abc.ABC): |
|
15 |
def __init__(self): |
|
16 |
self.lines = [] |
|
17 |
|
|
18 |
@abc.abstractmethod |
|
19 |
def getNextLine(self): |
|
20 |
pass |
|
21 |
|
|
22 |
def peek(self, line=0): |
|
23 |
while len(self.lines) <= line: |
|
24 |
self.lines.append(self.getNextLine()) |
|
25 |
return self.lines[line] |
|
26 |
|
|
27 |
def peekColumn(self, column, line=0): |
|
28 |
return self.peek(line)[column] |
|
29 |
|
|
30 |
def pop(self) -> dict: |
|
31 |
self.peek() |
|
32 |
return self.lines.pop(0) |
|
33 |
|
|
34 |
def __iter__(self): |
|
35 |
return self |
|
36 |
|
|
37 |
def __next__(self): |
|
38 |
return self.pop() |
|
39 |
|
|
40 |
|
|
41 |
class CsvDataReader(DataReader): |
|
42 |
def __init__(self, filename, **kwargs): |
|
43 |
super().__init__() |
|
44 |
|
|
45 |
import csv |
|
46 |
self.filehandle = open(filename, 'r') |
|
47 |
self.reader = csv.reader(self.filehandle, delimiter=' ', quoting=csv.QUOTE_NONE, **kwargs) |
|
48 |
|
|
49 |
def transformLine(self, line): |
|
50 |
return line |
|
51 |
|
|
52 |
def getNextLine(self): |
|
53 |
return self.transformLine(next(self.reader)) |
|
54 |
|
|
55 |
|
|
56 |
class HistoricalDataReader(DataReader): |
|
57 |
def __init__(self, filename, **kwargs): |
|
58 |
super().__init__() |
|
59 |
|
|
60 |
self.last_flow = 100 |
|
61 |
self.last_return = 100 |
|
62 |
self.last_mode = 0 |
|
63 |
|
|
64 |
import csv |
|
65 |
filehandle = open(filename, 'r') |
|
66 |
self.reader = csv.DictReader( |
|
67 |
filehandle, |
|
68 |
fieldnames=['time', 'temp_in', 'temp_out', 'temp_target', 'temp_flow', 'temp_return', 'mode'], |
|
69 |
delimiter=' ', quoting=csv.QUOTE_NONE, |
|
70 |
**kwargs |
|
71 |
) |
|
72 |
|
|
73 |
def transformLine(self, line): |
|
74 |
return { |
|
75 |
'time': int(line['time']), |
|
76 |
'temp_in': float(line['temp_in']), |
|
77 |
'temp_out': float(line['temp_out']), |
|
78 |
'temp_target': float(line['temp_target']), |
|
79 |
'temp_flow': float(line['temp_flow']), |
|
80 |
'temp_return': float(line['temp_return']), |
|
81 |
'mode': int(line['mode']), |
|
82 |
} |
|
83 |
|
|
84 |
def getNextLine(self): |
|
85 |
line = self.transformLine(next(self.reader)) |
|
86 |
if ( not line['mode'] and self.last_flow < line['temp_flow'] ) or (line['mode'] and not self.last_mode): |
|
87 |
line['temp_flow'] = self.last_flow |
|
88 |
self.last_flow = line['temp_flow'] |
|
89 |
if not line['mode'] and self.last_return < line['temp_return']: |
|
90 |
line['temp_return'] = self.last_return |
|
91 |
self.last_return = line['temp_return'] |
|
92 |
self.last_mode = line['mode'] |
|
93 |
return line |
|
94 |
|
|
95 |
|
|
96 |
class DataGenerator(DataReader): |
|
97 |
def __init__(self): |
|
98 |
super().__init__() |
|
99 |
|
|
100 |
self.x = -60 |
|
101 |
|
|
102 |
def getNextLine(self): |
|
103 |
self.x += 60 |
|
104 |
return { |
|
105 |
'time': self.x, |
|
106 |
'temp_in': 20.0, |
|
107 |
'temp_out': 10 * math.sin(self.x / 24 / 500), # around 20 hours |
|
108 |
'mode': 0, |
|
109 |
} |
|
110 |
|
|
111 |
|
|
112 |
class WeatherDataReader(DataReader): |
|
113 |
def __init__(self, filename, period=3, utc_offset=2): |
|
114 |
super().__init__() |
|
115 |
|
|
116 |
import json |
|
117 |
with open(filename, 'r') as filehandle: |
|
118 |
self.data = json.load(filehandle, object_pairs_hook=collections.OrderedDict) |
|
119 |
self.iterator = iter(self.data) |
|
120 |
|
|
121 |
self.period = period * 3600 |
|
122 |
self.utc_offset = utc_offset * 3600 |
|
123 |
|
|
124 |
self.start_time = int(next(iter(self.data))) |
|
125 |
self.end_time = int(next(iter(reversed(self.data)))) |
|
126 |
|
|
127 |
def getNextLine(self): |
|
128 |
i = next(self.iterator) |
|
129 |
result = {k: float(v) for k, v in self.data[i].items()} |
|
130 |
result['time'] = int(i) + self.utc_offset |
|
131 |
return result |
|
132 |
|
|
133 |
def getWeatherForTime(self, time): |
|
134 |
time = int(time) - self.utc_offset |
|
135 |
if time < self.start_time or time > self.end_time: |
|
136 |
raise Exception('Weather for time {} is unavailable'.format(time)) |
|
137 |
|
|
138 |
period_no = (time - self.start_time) / self.period |
|
139 |
first = floor(period_no) * self.period + self.start_time |
|
140 |
second = first + self.period |
|
141 |
if second > self.end_time: |
|
142 |
second = first |
|
143 |
first -= self.period |
|
144 |
|
|
145 |
result = {} |
|
146 |
for i in self.data[str(first)]: |
|
147 |
result[i] = (float(self.data[str(first)][i]) * (second - time) |
|
148 |
+ float(self.data[str(second)][i]) * (time - first)) / self.period |
|
149 |
result['time'] = time |
|
150 |
|
|
151 |
return result |
|
152 |
|
|
153 |
|
|
154 |
class WeatherDataWrapper(DataReader): |
|
155 |
def __init__(self, reader: DataReader, weather: WeatherDataReader): |
|
156 |
super().__init__() |
|
157 |
|
|
158 |
self.reader = reader |
|
159 |
self.weather = weather |
|
160 |
|
|
161 |
def getNextLine(self): |
|
162 |
data = self.reader.getNextLine() |
|
163 |
weather = self.weather.getWeatherForTime(data['time']) |
|
164 |
return {**data, **weather} |
|
165 |
|
|
166 |
|
|
167 |
class RadiationDataWrapper(DataReader): |
|
168 |
def __init__(self, weather: WeatherDataWrapper, latitude=49.88, longitude=19.49, localtz=None): |
|
169 |
super().__init__() |
|
170 |
|
|
171 |
self.weather = weather |
|
172 |
self.localtz = localtz if localtz else pytz.timezone('Europe/Warsaw') |
|
173 |
self.latitude = latitude |
|
174 |
self.longitude = longitude |
|
175 |
self.solartime = solartime.SolarTime() |
|
176 |
|
|
177 |
def getNextLine(self): |
|
178 |
data = self.weather.getNextLine() |
|
179 |
|
|
180 |
dtime = datetime.datetime.fromtimestamp(data['time']).replace(tzinfo=self.localtz).astimezone(pytz.utc) |
|
181 |
sunrise = self.solartime.sunrise_utc(dtime, self.latitude, self.longitude) + datetime.timedelta(hours=1) |
|
182 |
sunset = self.solartime.sunset_utc(dtime, self.latitude, self.longitude) - datetime.timedelta(hours=1) |
|
183 |
|
|
184 |
data['day'] = int(sunrise < dtime < sunset) |
|
185 |
data['radiation'] = (100 - data['cloudiness']) * data['day'] |
|
186 |
data['humid'] = 0 if data['humidity'] > 75 else int(75 - data['humidity']) |
|
187 |
return data |
|
188 |
|
|
189 |
|
|
190 |
class PeriodicReaderWrapper(DataReader): |
|
191 |
def __init__(self, reader, period=60, max_difference=60 * 60): |
|
192 |
super().__init__() |
|
193 |
|
|
194 |
self.reader = reader |
|
195 |
self.period = period |
|
196 |
self.max_difference = max_difference |
|
197 |
|
|
198 |
self.time = None |
|
199 |
self.last = None |
|
200 |
|
|
201 |
def getNextLine(self): |
|
202 |
if self.last is None: |
|
203 |
self.last = self.reader.pop() |
|
204 |
self.time = self.last['time'] |
|
205 |
else: |
|
206 |
self.time += self.period |
|
207 |
|
|
208 |
while self.reader.peekColumn('time') < self.time: |
|
209 |
self.last = self.reader.pop() |
|
210 |
|
|
211 |
if abs(self.last['time'] - self.time) > self.max_difference: |
|
212 |
self.last = None |
|
213 |
self.time = None |
|
214 |
raise StopIteration() |
|
215 |
|
|
216 |
return { |
|
217 |
**self.last, |
|
218 |
'time': self.time, |
|
219 |
} |
|
220 |
|
|
221 |
|
|
222 |
class AggregatorReaderWrapper(DataReader): |
|
223 |
def __init__(self, reader, period=60, aggregate=10): |
|
224 |
super().__init__() |
|
225 |
|
|
226 |
self.reader = reader |
|
227 |
self.period = period |
|
228 |
self.aggregate = aggregate |
|
229 |
|
|
230 |
self.time = None |
|
231 |
|
|
232 |
def getNextLine(self): |
|
233 |
if self.time is None: |
|
234 |
self.time = self.reader.peekColumn('time') |
|
235 |
|
|
236 |
sums = {} |
|
237 |
for i in range(self.aggregate): |
|
238 |
line = self.reader.pop() |
|
239 |
if self.time != line['time']: |
|
240 |
raise Exception('Invalid time series to aggregate, ' |
|
241 |
+ 'expected time {}, got {}'.format(self.time, line['time'])) |
|
242 |
|
|
243 |
for key, value in line.items(): |
|
244 |
if key not in sums: |
|
245 |
sums[key] = 0.0 |
|
246 |
|
|
247 |
if key == 'mode': |
|
248 |
sums[key] = max(sums[key], value) |
|
249 |
else: |
|
250 |
sums[key] += value |
|
251 |
|
|
252 |
self.time += self.period |
|
253 |
|
|
254 |
for key, value in sums.items(): |
|
255 |
if key == 'mode': |
|
256 |
continue |
|
257 |
sums[key] = value / self.aggregate |
|
258 |
|
|
259 |
return sums |