#!/usr/bin/env python # -*- coding: UTF-8 -*- # import config from rpi_envsens.dht import dht_22 from rpi_envsens.bmp import bmp_180 import helpers import garage_protocol import gui import logging import os import report import socket_protocol import task import tcp_socket import time import wetation_protocol import wx ############################################################################################################################ # # # Application Structure # # # # +-----------------------------+ +------------------------------+ +---------------------------+ # # | Data Request Task (1s) | | Data Receive Callback | | GUI Update WX-IDLE-TASK | # # | Counter 0-9 | | | | | # # | | | Append data to ValueStorage | | - Update all GUI Elements | # # | *: Request door_pos | +------------------------------+ | - Check Data Status to | # # | 0: Request env-data-in bmp | | mark all fragile con- | # # | 1: Request env-data-in dht | | tent | # # | 5: Request env-data-out bmp | +---------------------------+ # # | 6: Request env-data-out dht | # # | 9: Reconnect if needed | # # +-----------------------------+ # # # ############################################################################################################################ try: from config import APP_NAME as ROOT_LOGGER_NAME except ImportError: ROOT_LOGGER_NAME = 'root' logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) class ValueStorage(dict): MAX_AGE = 60 # Seconds DATA_KEY_GATE_POSITION = 'gate_position' DATA_KEY_HUMIDITY_IN = 'humidity_in' DATA_KEY_HUMIDITY_OUT = 'humidity_out' DATA_KEY_PRESSURE_IN = 'pressure_in' DATA_KEY_PRESSURE_OUT = 'pressure_out' DATA_KEY_TEMPERATURE_IN = 'temp_in' DATA_KEY_TEMPERATURE_OUT = 'temp_out' DATA_KEY_2ND_TEMPERATURE_IN = '2nd_temp_in' DATA_KEY_2ND_TEMPERATURE_OUT = '2nd_temp_out' FORMATTER_TEMP = ('%.1f °C', '-.- °C') FORMATTER_HUMI = ('%.1f %%', '-.- %') FORMATTER_PRES = ('%4d hPa', '- hPa') DATA_KEYS_CONT_STAT = [DATA_KEY_HUMIDITY_IN, DATA_KEY_HUMIDITY_OUT, DATA_KEY_PRESSURE_IN, DATA_KEY_PRESSURE_OUT, DATA_KEY_TEMPERATURE_IN, DATA_KEY_TEMPERATURE_OUT, DATA_KEY_2ND_TEMPERATURE_IN, DATA_KEY_2ND_TEMPERATURE_OUT] def append(self, drk, value): tm = time.time() logger.info('Value stored (%s): %s', drk, repr(value)) if drk in self.DATA_KEYS_CONT_STAT: self[drk] = (value.mean, tm) if drk + '.min' not in self or value.min < self[drk + '.min'][0]: self[drk + '.min'] = (value.min, tm) if drk + '.max' not in self or value.max < self[drk + '.max'][0]: self[drk + '.max'] = (value.max, tm) else: self[drk] = (value, tm) def reset_min_max(self, data_key): del self[data_key + '.min'] del self[data_key + '.max'] def __get_formatter__(self, key): return { self.DATA_KEY_2ND_TEMPERATURE_IN: self.FORMATTER_TEMP, self.DATA_KEY_2ND_TEMPERATURE_OUT: self.FORMATTER_TEMP, self.DATA_KEY_HUMIDITY_IN: self.FORMATTER_HUMI, self.DATA_KEY_HUMIDITY_OUT: self.FORMATTER_HUMI, self.DATA_KEY_PRESSURE_IN: self.FORMATTER_PRES, self.DATA_KEY_PRESSURE_OUT: self.FORMATTER_PRES, self.DATA_KEY_TEMPERATURE_IN: self.FORMATTER_TEMP, self.DATA_KEY_TEMPERATURE_OUT: self.FORMATTER_TEMP, }.get(key) def get_validated(self, key, value_type=None, default=None): tm = time.time() fallback = { self.DATA_KEY_TEMPERATURE_IN: self.DATA_KEY_2ND_TEMPERATURE_IN, self.DATA_KEY_TEMPERATURE_OUT: self.DATA_KEY_2ND_TEMPERATURE_OUT, }.get(key) if value_type in ['min', 'max']: key = key + '.' + value_type value = dict.get(self, key) if value is None and fallback is not None: value = dict.get(self, fallback) if value is None: return default if tm - value[1] <= self.MAX_AGE: if key == self.DATA_KEY_GATE_POSITION: return int(value[0] * 50) else: return value[0] else: return default def get(self, key, value_type=None, formatted=True, default=None): formatter = self.__get_formatter__(key) value = self.get_validated(key, value_type, default=default) if not formatted: return value else: if formatter is None: return default else: try: return formatter[0] % value except Exception: return formatter[1] class Wetation(gui.Wetation): SRC_ID_GARAGE = 0 SRC_ID_IN = 1 SRC_ID_OUT = 2 ALL_SRC_IDS = [SRC_ID_GARAGE, SRC_ID_IN, SRC_ID_OUT, ] SRC_NAMES = { SRC_ID_GARAGE: u'garage', SRC_ID_IN: u'wet-in', SRC_ID_OUT: u'wet-out', } SRC_IPS = { SRC_ID_GARAGE: config.server_garage_ip, SRC_ID_IN: config.server_in_ip, SRC_ID_OUT: config.server_out_ip, } SRC_PORTS = { SRC_ID_GARAGE: config.server_garage_port, SRC_ID_IN: config.server_in_port, SRC_ID_OUT: config.server_out_port, } SRC_SECRETS = { SRC_ID_GARAGE: config.server_garage_secret, SRC_ID_IN: config.server_in_secret, SRC_ID_OUT: config.server_out_secret, } SRC_CLASSES = { SRC_ID_GARAGE: garage_protocol.my_base_protocol_tcp, SRC_ID_IN: wetation_protocol.my_base_protocol_tcp, SRC_ID_OUT: wetation_protocol.my_base_protocol_tcp, } def __init__(self, *args, **kwds): gui.Wetation.__init__(self, *args, **kwds) self.Bind(wx.EVT_IDLE, self.gui_update) self.in_temperature_min.Bind(wx.EVT_LEFT_DOWN, self.reset_in_temp_minmax_evt) self.in_temperature_max.Bind(wx.EVT_LEFT_DOWN, self.reset_in_temp_minmax_evt) self.out_temperature_min.Bind(wx.EVT_LEFT_DOWN, self.reset_out_temp_minmax_evt) self.out_temperature_max.Bind(wx.EVT_LEFT_DOWN, self.reset_out_temp_minmax_evt) self.ShowFullScreen(config.FULL_SCREEN) # self.__src__ = {} self.__value_storage__ = ValueStorage() self.__init_communication__() # self.__drt_cnt__ = -1 self.__task_1s__ = task.periodic(1, self.data_request_task) def __init_communication__(self): # # Start TCP-Clients for src_id in self.ALL_SRC_IDS: cn = self.SRC_NAMES[src_id] c_tcp = tcp_socket.tcp_client_stp(self.SRC_IPS[src_id], self.SRC_PORTS[src_id], channel_name=cn) self.__src__[src_id] = self.SRC_CLASSES[src_id](c_tcp, secret=self.SRC_SECRETS[src_id], auto_auth=True, channel_name=cn) # self.__src__[src_id].register_callback(None, None, self.data_receive_callback, src_id) def gate_oc_evt(self, event): r = wx.MessageDialog( self, "Soll das Garagentor betätigt werden?", "Garage", wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING ).ShowModal() if r == wx.ID_YES: logger.info("Gate open/close request") self.__src__[self.SRC_ID_GARAGE].send(socket_protocol.SID_EXECUTE_REQUEST, garage_protocol.OPEN_CLOSE_GATE, None) event.Skip() def reset_in_temp_minmax_evt(self, event): self.__value_storage__.reset_min_max(ValueStorage.DATA_KEY_TEMPERATURE_IN) self.__value_storage__.reset_min_max(ValueStorage.DATA_KEY_2ND_TEMPERATURE_IN) event.Skip() def reset_out_temp_minmax_evt(self, event): self.__value_storage__.reset_min_max(ValueStorage.DATA_KEY_TEMPERATURE_OUT) self.__value_storage__.reset_min_max(ValueStorage.DATA_KEY_2ND_TEMPERATURE_OUT) event.Skip() def data_request_task(self, rt): (rt) if self.__drt_cnt__ < 9: self.__drt_cnt__ += 1 else: self.__drt_cnt__ = 0 logger.debug('data_request_task stated with cnt = %d', self.__drt_cnt__) # # Request GATE_POSITION # self.__src__[self.SRC_ID_GARAGE].send(service_id=socket_protocol.SID_READ_REQUEST, data_id=garage_protocol.GATE_POSITION, data=None) # # Request WET-OUT-DATA # if self.__drt_cnt__ == 0: self.__src__[self.SRC_ID_OUT].send(service_id=socket_protocol.SID_READ_REQUEST, data_id=wetation_protocol.ENVDATA_STATISTIC_BMP, data=None) elif self.__drt_cnt__ == 1: self.__src__[self.SRC_ID_OUT].send(service_id=socket_protocol.SID_READ_REQUEST, data_id=wetation_protocol.ENVDATA_STATISTIC_DHT, data=None) # # Request WET-IN-DATA # elif self.__drt_cnt__ == 5: self.__src__[self.SRC_ID_IN].send(service_id=socket_protocol.SID_READ_REQUEST, data_id=wetation_protocol.ENVDATA_STATISTIC_BMP, data=None) elif self.__drt_cnt__ == 6: self.__src__[self.SRC_ID_IN].send(service_id=socket_protocol.SID_READ_REQUEST, data_id=wetation_protocol.ENVDATA_STATISTIC_DHT, data=None) elif self.__drt_cnt__ == 9: self.__reconnect__() # def __reconnect__(self): for src_id in self.ALL_SRC_IDS: if not self.__src__[src_id].is_connected(): logger.warning("Trying to reconnect prot_id %s", self.SRC_NAMES.get(src_id, 'Unknown')) self.__src__[src_id].reconnect() def __data_receive_key__(self, src_id, data_id, key=None, default_value=None): return { self.SRC_ID_GARAGE: { garage_protocol.GATE_POSITION: { None: ValueStorage.DATA_KEY_GATE_POSITION, } }, self.SRC_ID_IN: { wetation_protocol.ENVDATA_STATISTIC_BMP: { bmp_180.KEY_PRESSURE: ValueStorage.DATA_KEY_PRESSURE_IN, bmp_180.KEY_TEMPERATURE: ValueStorage.DATA_KEY_2ND_TEMPERATURE_IN }, wetation_protocol.ENVDATA_STATISTIC_DHT: { dht_22.KEY_HUMIDITY: ValueStorage.DATA_KEY_HUMIDITY_IN, dht_22.KEY_TEMPERATURE: ValueStorage.DATA_KEY_TEMPERATURE_IN }, }, self.SRC_ID_OUT: { wetation_protocol.ENVDATA_STATISTIC_BMP: { bmp_180.KEY_PRESSURE: ValueStorage.DATA_KEY_PRESSURE_OUT, bmp_180.KEY_TEMPERATURE: ValueStorage.DATA_KEY_2ND_TEMPERATURE_OUT }, wetation_protocol.ENVDATA_STATISTIC_DHT: { dht_22.KEY_HUMIDITY: ValueStorage.DATA_KEY_HUMIDITY_OUT, dht_22.KEY_TEMPERATURE: ValueStorage.DATA_KEY_TEMPERATURE_OUT }, } }.get(src_id, {}).get(data_id, {}).get(key, default_value) def data_receive_callback(self, data, src_id): if src_id in [self.SRC_ID_IN, self.SRC_ID_OUT] and data.get_data_id() in [wetation_protocol.ENVDATA_STATISTIC_DHT, wetation_protocol.ENVDATA_STATISTIC_BMP]: try: for key in data.get_data(): if key != bmp_180.KEY_TIME: drk = self.__data_receive_key__(src_id, data.get_data_id(), key) if drk is not None: self.__process_rx_data__( drk, helpers.continues_statistic(**data.get_data()[key]) ) else: self.__warning_rx_data__('Unknown RX-Data', data, src_id) except TypeError as e: self.__warning_rx_data__('Wront DataType in RX-Data (%s)' % e, data, src_id) else: drk = self.__data_receive_key__(src_id, data.get_data_id()) if drk is not None: self.__process_rx_data__( drk, data.get_data() ) else: self.__warning_rx_data__('Unknown RX-Data', data, src_id) def __process_rx_data__(self, drk, data): self.__value_storage__.append(drk, data) def __warning_rx_data__(self, desc, data, src_id): logger.warning('%s from %s: %s', desc, self.SRC_NAMES.get(src_id, 'Unknown'), repr(data)) def __check_data_status__(self): if dict.get(self.__value_storage__, ValueStorage.DATA_KEY_TEMPERATURE_IN) is None and dict.get(self.__value_storage__, ValueStorage.DATA_KEY_2ND_TEMPERATURE_IN) is not None: self.in_temperature.SetBackgroundColour((255, 255, 200, 255)) else: self.in_temperature.SetBackgroundColour((250, 249, 255, 255)) if dict.get(self.__value_storage__, ValueStorage.DATA_KEY_TEMPERATURE_OUT) is None and dict.get(self.__value_storage__, ValueStorage.DATA_KEY_2ND_TEMPERATURE_OUT) is not None: self.out_temperature.SetBackgroundColour((255, 255, 200, 255)) else: self.out_temperature.SetBackgroundColour((250, 249, 255, 255)) if dict.get(self.__value_storage__, ValueStorage.DATA_KEY_GATE_POSITION) is None: self.heading_garage.SetBackgroundColour((142, 35, 35, 255)) else: self.heading_garage.SetBackgroundColour((35, 35, 142, 255)) def gui_update(self, event): # # TIME and DATE # self.time.SetLabel(time.strftime("%H:%M")) self.date.SetLabel(time.strftime("%d.%m.%Y")) # update_list = [ # WET-OUT (self.out_humidity.SetLabel, [ValueStorage.DATA_KEY_HUMIDITY_OUT, 'mean', True]), (self.out_pressure.SetLabel, [ValueStorage.DATA_KEY_PRESSURE_OUT, 'mean', True]), (self.out_temperature.SetLabel, [ValueStorage.DATA_KEY_TEMPERATURE_OUT, 'mean', True]), (self.out_temperature_max.SetLabel, [ValueStorage.DATA_KEY_TEMPERATURE_OUT, 'max', True]), (self.out_temperature_min.SetLabel, [ValueStorage.DATA_KEY_TEMPERATURE_OUT, 'min', True]), # WET-IN (self.in_humidity.SetLabel, [ValueStorage.DATA_KEY_HUMIDITY_IN, 'mean', True]), (self.in_pressure.SetLabel, [ValueStorage.DATA_KEY_PRESSURE_IN, 'mean', True]), (self.in_temperature.SetLabel, [ValueStorage.DATA_KEY_TEMPERATURE_IN, 'mean', True]), (self.in_temperature_max.SetLabel, [ValueStorage.DATA_KEY_TEMPERATURE_IN, 'max', True]), (self.in_temperature_min.SetLabel, [ValueStorage.DATA_KEY_TEMPERATURE_IN, 'min', True]), # GATE (self.gate_position.SetValue, [ValueStorage.DATA_KEY_GATE_POSITION, None, False, 50]) ] # for func, args in update_list: func(self.__value_storage__.get(*args)) self.__check_data_status__() # # self.Layout() event.Skip() def run(self): self.__task_1s__.run() def close(self): self.__task_1s__.stop() self.__task_1s__.join() def __del__(self): self.close() class MyApp(wx.App): def OnInit(self): self.frame = Wetation(None, wx.ID_ANY, "") self.SetTopWindow(self.frame) self.frame.Show() return True if __name__ == "__main__": report.appLoggingConfigure(os.path.dirname(__file__), config.LOGTARGET, ((config.APP_NAME, config.LOGLVL), ), fmt=config.formatter, host=config.LOGHOST, port=config.LOGPORT) # app = MyApp(0) app.frame.run() app.MainLoop() app.frame.close()