383 lines
16 KiB
Python
383 lines
16 KiB
Python
#!/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()
|