import abc import collections import datetime from math import floor import math import pytz import solartime solartime.basestring = str class DataReader(abc.ABC): def __init__(self): self.lines = [] @abc.abstractmethod def getNextLine(self): pass def peek(self, line=0): while len(self.lines) <= line: self.lines.append(self.getNextLine()) return self.lines[line] def peekColumn(self, column, line=0): return self.peek(line)[column] def pop(self) -> dict: self.peek() return self.lines.pop(0) def __iter__(self): return self def __next__(self): return self.pop() class CsvDataReader(DataReader): def __init__(self, filename, **kwargs): super().__init__() import csv self.filehandle = open(filename, 'r') self.reader = csv.reader(self.filehandle, delimiter=' ', quoting=csv.QUOTE_NONE, **kwargs) def transformLine(self, line): return line def getNextLine(self): return self.transformLine(next(self.reader)) class HistoricalDataReader(DataReader): def __init__(self, filename, **kwargs): super().__init__() self.last_flow = 100 self.last_return = 100 self.last_mode = 0 import csv filehandle = open(filename, 'r') self.reader = csv.DictReader( filehandle, fieldnames=['time', 'temp_in', 'temp_out', 'temp_target', 'temp_flow', 'temp_return', 'mode'], delimiter=' ', quoting=csv.QUOTE_NONE, **kwargs ) def transformLine(self, line): return { 'time': int(line['time']), 'temp_in': float(line['temp_in']), 'temp_out': float(line['temp_out']), 'temp_target': float(line['temp_target']), 'temp_flow': float(line['temp_flow']), 'temp_return': float(line['temp_return']), 'mode': int(line['mode']), } def getNextLine(self): line = self.transformLine(next(self.reader)) if ( not line['mode'] and self.last_flow < line['temp_flow'] ) or (line['mode'] and not self.last_mode): line['temp_flow'] = self.last_flow self.last_flow = line['temp_flow'] if not line['mode'] and self.last_return < line['temp_return']: line['temp_return'] = self.last_return self.last_return = line['temp_return'] self.last_mode = line['mode'] return line class DataGenerator(DataReader): def __init__(self): super().__init__() self.x = -60 def getNextLine(self): self.x += 60 return { 'time': self.x, 'temp_in': 20.0, 'temp_out': 10 * math.sin(self.x / 24 / 500), # around 20 hours 'mode': 0, } class WeatherDataReader(DataReader): def __init__(self, filename, period=3, utc_offset=2): super().__init__() import json with open(filename, 'r') as filehandle: self.data = json.load(filehandle, object_pairs_hook=collections.OrderedDict) self.iterator = iter(self.data) self.period = period * 3600 self.utc_offset = utc_offset * 3600 self.start_time = int(next(iter(self.data))) self.end_time = int(next(iter(reversed(self.data)))) def getNextLine(self): i = next(self.iterator) result = {k: float(v) for k, v in self.data[i].items()} result['time'] = int(i) + self.utc_offset return result def getWeatherForTime(self, time): time = int(time) - self.utc_offset if time < self.start_time or time > self.end_time: raise Exception('Weather for time {} is unavailable'.format(time)) period_no = (time - self.start_time) / self.period first = floor(period_no) * self.period + self.start_time second = first + self.period if second > self.end_time: second = first first -= self.period result = {} for i in self.data[str(first)]: result[i] = (float(self.data[str(first)][i]) * (second - time) + float(self.data[str(second)][i]) * (time - first)) / self.period result['time'] = time return result class WeatherDataWrapper(DataReader): def __init__(self, reader: DataReader, weather: WeatherDataReader): super().__init__() self.reader = reader self.weather = weather def getNextLine(self): data = self.reader.getNextLine() weather = self.weather.getWeatherForTime(data['time']) return {**data, **weather} class RadiationDataWrapper(DataReader): def __init__(self, weather: WeatherDataWrapper, latitude=49.88, longitude=19.49, localtz=None): super().__init__() self.weather = weather self.localtz = localtz if localtz else pytz.timezone('Europe/Warsaw') self.latitude = latitude self.longitude = longitude self.solartime = solartime.SolarTime() def getNextLine(self): data = self.weather.getNextLine() dtime = datetime.datetime.fromtimestamp(data['time']).replace(tzinfo=self.localtz).astimezone(pytz.utc) sunrise = self.solartime.sunrise_utc(dtime, self.latitude, self.longitude) + datetime.timedelta(hours=1) sunset = self.solartime.sunset_utc(dtime, self.latitude, self.longitude) - datetime.timedelta(hours=1) data['day'] = int(sunrise < dtime < sunset) data['radiation'] = (100 - data['cloudiness']) * data['day'] data['humid'] = 0 if data['humidity'] > 75 else int(75 - data['humidity']) return data class PeriodicReaderWrapper(DataReader): def __init__(self, reader, period=60, max_difference=60 * 60): super().__init__() self.reader = reader self.period = period self.max_difference = max_difference self.time = None self.last = None def getNextLine(self): if self.last is None: self.last = self.reader.pop() self.time = self.last['time'] else: self.time += self.period while self.reader.peekColumn('time') < self.time: self.last = self.reader.pop() if abs(self.last['time'] - self.time) > self.max_difference: self.last = None self.time = None raise StopIteration() return { **self.last, 'time': self.time, } class AggregatorReaderWrapper(DataReader): def __init__(self, reader, period=60, aggregate=10): super().__init__() self.reader = reader self.period = period self.aggregate = aggregate self.time = None def getNextLine(self): if self.time is None: self.time = self.reader.peekColumn('time') sums = {} for i in range(self.aggregate): line = self.reader.pop() if self.time != line['time']: raise Exception('Invalid time series to aggregate, ' + 'expected time {}, got {}'.format(self.time, line['time'])) for key, value in line.items(): if key not in sums: sums[key] = 0.0 if key == 'mode': sums[key] = max(sums[key], value) else: sums[key] += value self.time += self.period for key, value in sums.items(): if key == 'mode': continue sums[key] = value / self.aggregate return sums