Newer
Older
import logging
from typing import Dict, Tuple, List

Robert Goldmann
committed
from timeago import format

Robert Goldmann
committed
from TheCodeLabs_BaseUtils.MultiCacheKeyService import MultiCacheKeyService
from logic.service.ServiceManager import ServiceManager
from logic.tile.Tile import Tile
LOGGER = logging.getLogger(Constants.APP_NAME)
class SensorType:
TEMPERATURE = 'temperature'
HUMIDITY = 'humidity'
EXAMPLE_SETTINGS = {
"title": "My Room",
"url": "http://127.0.0.1:10003",
"sensorID": 1,

Robert Goldmann
committed
"sensorIDsForMinMax": [2, 3, 4],
"fillColor": "rgba(254, 151, 0, 0.2)",

Robert Goldmann
committed
"showAxes": True,

Robert Goldmann
committed
"outdatedValueWarning": {
"enable": False,
"limitInSeconds": 300
}
SensorType.TEMPERATURE: '°C',
SensorType.HUMIDITY: '%'
SensorType.TEMPERATURE: 'wi-thermometer',
SensorType.HUMIDITY: 'wi-humidity'
DATETIME_UNIX_TIMESTAMP_START = datetime(year=1970, month=1, day=1, hour=0, minute=0, second=0)

Robert Goldmann
committed
MAX_Y_AXIS_SPACING = 2
def __init__(self, uniqueName: str, settings: Dict, intervalInSeconds: int):
super().__init__(uniqueName, settings, intervalInSeconds)
def fetch(self, pageName: str) -> Dict:
storageLeafService = ServiceManager.get_instance().get_service_by_type_name('StorageLeafService')

Robert Goldmann
committed
startDateTime = datetime.strftime(datetime.now() - timedelta(hours=self._settings['numberOfHoursToShow']),
self.DATE_FORMAT)
endDateTime = datetime.strftime(datetime.now(), self.DATE_FORMAT)
sensorData = self.__get_sensor_data_from_service(endDateTime, pageName, startDateTime, storageLeafService)
x, y = self._prepare_measurement_data(sensorData['sensorValue'])
latestTime = datetime.strptime(x[-1], self.DATE_FORMAT) if x else self.DATETIME_UNIX_TIMESTAMP_START

Robert Goldmann
committed
latestValue = y[-1] if y else ''
minValue, maxValue = self._get_min_and_max(pageName,
sensorData['sensorInfo']['type'],
startDateTime,
endDateTime,
storageLeafService,
y)

Robert Goldmann
committed
# Check if all values are above zero and the min value for the sensor group is below zero.
# Therefore a ghost trace must be generated that fills the area underneath the x-axis.
ghostTraceX, ghostTraceY = self._prepare_ghost_trace(minValue, x, y)

Robert Goldmann
committed
'latest': latestValue,
'x': x,
'y': y,

Robert Goldmann
committed
'sensorInfo': sensorData['sensorInfo'],
'min': minValue,
'max': maxValue,
'ghostTraceX': ghostTraceX,

Robert Goldmann
committed
'ghostTraceY': ghostTraceY,
'latestTime': latestTime

Robert Goldmann
committed
}
def __get_sensor_data_from_service(self, endDateTime, pageName, startDateTime, storageLeafService):
serviceSettings = {
'url': self._settings['url'],
'sensorID': self._settings['sensorID'],
'fetchType': 'all',
'startDateTime': startDateTime,
'endDateTime': endDateTime
}
cacheKey = f'{pageName}_{self._uniqueName}_all'
sensorData = storageLeafService.get_data(cacheKey, self._intervalInSeconds, serviceSettings)
return sensorData
def _get_min_and_max(self, pageName: str, sensorType: Dict,
startDateTime: str, endDateTime: str,
storageLeafService: MultiCacheKeyService, y: List) -> Tuple[float, float]:

Robert Goldmann
committed
return 0, 100

Robert Goldmann
committed
# prevent fetching min/max from StorageLeaf as this would consume a lot of time due to huge timespans
if self._settings['showAxes']:
yValues = [float(item) for item in y]

Robert Goldmann
committed
minValue = min(yValues, default=0)
maxValue = max(yValues, default=0)
else:
minValue, maxValue = self.__get_min_max_from_service(pageName, startDateTime, endDateTime,
storageLeafService)
return min(0, minValue) - self.MAX_Y_AXIS_SPACING, maxValue + self.MAX_Y_AXIS_SPACING

Robert Goldmann
committed
def __get_min_max_from_service(self, pageName: str, startDateTime: str, endDateTime: str,
storageLeafService: MultiCacheKeyService):

Robert Goldmann
committed
minMaxSettings = {
'url': self._settings['url'],
'sensorIDsForMinMax': self._settings['sensorIDsForMinMax'],
'fetchType': 'minMax',
'startDateTime': startDateTime,
'endDateTime': endDateTime

Robert Goldmann
committed

Robert Goldmann
committed
cacheKey = f'{pageName}_{self._uniqueName}_minMax'
minMaxData = storageLeafService.get_data(cacheKey, self._intervalInSeconds, minMaxSettings)
LOGGER.debug(f'Received min/max: {minMaxData} for sensorIDs: {self._settings["sensorIDsForMinMax"]}')

Robert Goldmann
committed
return minMaxData.get('min', 0) or 0, minMaxData.get('max', 0) or 0
def _prepare_measurement_data(self, measurements: List[Dict]) -> Tuple[List[str], List[str]]:
for measurement in measurements:
timestamp = measurement['timestamp']
x.append(timestamp)

Robert Goldmann
committed
value = float(measurement['value'])
y.append(Helpers.round_to_decimals(value, self._settings['decimals']))
def _prepare_ghost_trace(self, minValue, x, y):
ghostTraceX = []
ghostTraceY = []

Robert Goldmann
committed
if not x or not y:
return ghostTraceX, ghostTraceY
if all(float(i) >= 0 for i in y):
if minValue < 0:
ghostTraceX = [x[0], x[-1]]
ghostTraceY = [minValue, minValue]
return ghostTraceX, ghostTraceY
def render(self, data: Dict) -> str:
sensorType = data['sensorInfo']['type']
unit = self.UNIT_BY_SENSOR_TYPE.get(sensorType, '')
unescapedUnit = html.unescape(unit)
icon = self.ICON_BY_SENSOR_TYPE.get(sensorType, '')
textLabels = [f'{self.__format_date(xItem)} - {yItem}{unescapedUnit}' for xItem, yItem in zip(data['x'],
data['y'])]
title = self._settings['title']
if self._settings['showAxes']:
days = int(self._settings['numberOfHoursToShow'] / 24)
title = f'{title} - {days} days'

Robert Goldmann
committed
warningSettings = self._settings['outdatedValueWarning']
timeSinceLastValue = self.__get_time_since_last_value(warningSettings, data)

Robert Goldmann
committed
return Tile.render_template(os.path.dirname(__file__), __class__.__name__,
x=data['x'],
y=data['y'],

Robert Goldmann
committed
min=data['min'],
max=data['max'],
latest=data['latest'],
unit=unit,
icon=icon,
title=title,
lineColor=self._settings['lineColor'],
fillColor=self._settings['fillColor'],
chartId=str(uuid.uuid4()),
ghostTraceX=data['ghostTraceX'],
ghostTraceY=data['ghostTraceY'],

Robert Goldmann
committed
showAxes=self._settings['showAxes'],

Robert Goldmann
committed
timeSinceLastValue=timeSinceLastValue)
def __get_time_since_last_value(self, warningSettings: Dict, data):
timeAgo = ''
if not warningSettings['enable']:
return timeAgo
now = datetime.now()
limitInSeconds = warningSettings['limitInSeconds']
if limitInSeconds > 0:
timeDifference = now - data['latestTime']
if timeDifference.total_seconds() > limitInSeconds:
timeAgo = format(timeDifference)
return timeAgo
def __format_date(self, dateTime: str):
parsedDateTime = datetime.strptime(dateTime, self.DATE_FORMAT)
return datetime.strftime(parsedDateTime, self.DATE_FORMAT_CHART)

Robert Goldmann
committed
def construct_blueprint(self, pageName: str, *args, **kwargs):
return Blueprint(f'{pageName}_{__class__.__name__}_{self.get_uniqueName()}', __name__)