wether station gui client
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

smarthome.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. #
  4. import config
  5. from rpi_envsens.dht import dht_22
  6. from rpi_envsens.bmp import bmp_180
  7. import helpers
  8. import garage_protocol
  9. import gui
  10. import logging
  11. import numbers
  12. import os
  13. import report
  14. import task
  15. import tcp_socket
  16. import time
  17. import wetation_protocol
  18. import wx
  19. try:
  20. from config import APP_NAME as ROOT_LOGGER_NAME
  21. except ImportError:
  22. ROOT_LOGGER_NAME = 'root'
  23. class WetationFrameProt(gui.Wetation):
  24. PROT_ID_GARAGE = 0
  25. PROT_ID_IN = 1
  26. PROT_ID_OUT = 2
  27. ALL_PROT_IDS = [PROT_ID_GARAGE, PROT_ID_IN, PROT_ID_OUT, ]
  28. PROT_NAMES = {
  29. PROT_ID_GARAGE: u'garage',
  30. PROT_ID_IN: u'wet-in',
  31. PROT_ID_OUT: u'wet-out',
  32. }
  33. PROT_IPS = {
  34. PROT_ID_GARAGE: config.server_garage_ip,
  35. PROT_ID_IN: config.server_in_ip,
  36. PROT_ID_OUT: config.server_out_ip,
  37. }
  38. PROT_PORTS = {
  39. PROT_ID_GARAGE: config.server_garage_port,
  40. PROT_ID_IN: config.server_in_port,
  41. PROT_ID_OUT: config.server_out_port,
  42. }
  43. PROT_SECRETS = {
  44. PROT_ID_GARAGE: config.server_garage_secret,
  45. PROT_ID_IN: config.server_in_secret,
  46. PROT_ID_OUT: config.server_out_secret,
  47. }
  48. PROT_CLASSES = {
  49. PROT_ID_GARAGE: garage_protocol.my_base_protocol_tcp,
  50. PROT_ID_IN: wetation_protocol.my_base_protocol_tcp,
  51. PROT_ID_OUT: wetation_protocol.my_base_protocol_tcp,
  52. }
  53. REQUEST_MSGS_SLOW = {
  54. PROT_ID_IN: [
  55. {
  56. 'service_id': wetation_protocol.my_base_protocol_tcp.SID_READ_REQUEST,
  57. 'data_id': wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_DHT,
  58. },
  59. {
  60. 'service_id': wetation_protocol.my_base_protocol_tcp.SID_READ_REQUEST,
  61. 'data_id': wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_BMP,
  62. },
  63. ],
  64. PROT_ID_OUT: [
  65. {
  66. 'service_id': wetation_protocol.my_base_protocol_tcp.SID_READ_REQUEST,
  67. 'data_id': wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_DHT,
  68. },
  69. {
  70. 'service_id': wetation_protocol.my_base_protocol_tcp.SID_READ_REQUEST,
  71. 'data_id': wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_BMP,
  72. },
  73. ],
  74. }
  75. REQUEST_MSGS_FAST = {
  76. PROT_ID_GARAGE: [
  77. {
  78. 'service_id': garage_protocol.my_base_protocol_tcp.SID_READ_REQUEST,
  79. 'data_id': garage_protocol.my_base_protocol_tcp.GATE_POSITION,
  80. },
  81. ],
  82. }
  83. def __init__(self, *args, **kwds):
  84. self.__min_temp_in__ = None
  85. self.__min_temp_out__ = None
  86. self.__max_temp_in__ = None
  87. self.__max_temp_out__ = None
  88. self.__prot__ = {}
  89. gui.Wetation.__init__(self, *args, **kwds)
  90. self.in_temperature_min.Bind(wx.EVT_LEFT_DOWN, self.reset_in_temp_minmax_evt)
  91. self.in_temperature_max.Bind(wx.EVT_LEFT_DOWN, self.reset_in_temp_minmax_evt)
  92. self.out_temperature_min.Bind(wx.EVT_LEFT_DOWN, self.reset_out_temp_minmax_evt)
  93. self.out_temperature_max.Bind(wx.EVT_LEFT_DOWN, self.reset_out_temp_minmax_evt)
  94. self.ShowFullScreen(config.FULL_SCREEN)
  95. self.__init_communication__()
  96. time.sleep(3.5) # Wait for established connections before starting the tasks
  97. self.__task_1s__ = task.periodic(1, self.__task_1s_callback__)
  98. self.__task_10s__ = task.periodic(10, self.__task_10s_callback__)
  99. self.__task_60s__ = task.periodic(60, self.__task_60s_callback__)
  100. def __init_communication__(self):
  101. #
  102. # Start TCP-Clients
  103. for prot_id in self.ALL_PROT_IDS:
  104. cn = self.PROT_NAMES[prot_id]
  105. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + cn)
  106. logger.info('Initiating communication channel')
  107. c_tcp = tcp_socket.tcp_client_stp(self.PROT_IPS[prot_id], self.PROT_PORTS[prot_id], rx_log_lvl=logging.DEBUG)
  108. self.__prot__[prot_id] = self.PROT_CLASSES[prot_id](c_tcp, secret=self.PROT_SECRETS[prot_id], auto_auth=True, channel_name=cn)
  109. #
  110. self.__prot__[prot_id].register_callback(None, None, self.__prot_resp_callbacks__, prot_id)
  111. def __initiate_data_request__(self, rt, request_msgs):
  112. for prot_id in request_msgs:
  113. cn = self.PROT_NAMES[prot_id]
  114. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + cn)
  115. for request_msg in request_msgs.get(prot_id, []):
  116. service_id = request_msg['service_id']
  117. data_id = request_msg['data_id']
  118. if self.__prot__[prot_id].connection_established():
  119. logger.debug('Sending data request for service_id %d and data_id %d', service_id, data_id)
  120. self.__prot__[prot_id].send(service_id, data_id, None)
  121. else:
  122. wx.CallAfter(self.__no_data__, prot_id, service_id + 1, data_id)
  123. def __task_1s_callback__(self, rt):
  124. # request data from fast prot ids
  125. self.__initiate_data_request__(rt, self.REQUEST_MSGS_FAST)
  126. # set date and time
  127. wx.CallAfter(self.update_time)
  128. def __task_10s_callback__(self, rt):
  129. # request data from slow prot ids
  130. self.__initiate_data_request__(rt, self.REQUEST_MSGS_SLOW)
  131. def __task_60s_callback__(self, rt):
  132. # reconnect prots if needed
  133. for prot_id in self.ALL_PROT_IDS:
  134. cn = self.PROT_NAMES[prot_id]
  135. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + cn)
  136. if not self.__prot__[prot_id].connected():
  137. logger.warning("Trying to reconnect prot_id %d", prot_id)
  138. self.__prot__[prot_id].reconnect()
  139. def __validate_response_data__(self, prot_id, service_id, data_id, data):
  140. rv = False
  141. cn = self.PROT_NAMES[prot_id]
  142. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + cn)
  143. if prot_id == self.PROT_ID_GARAGE:
  144. if service_id == garage_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == garage_protocol.my_base_protocol_tcp.GATE_POSITION:
  145. rv = isinstance(data, numbers.Number)
  146. elif service_id == garage_protocol.my_base_protocol_tcp.SID_EXECUTE_RESPONSE and data_id == garage_protocol.my_base_protocol_tcp.OPEN_CLOSE_GATE:
  147. if data is True:
  148. rv = True
  149. elif prot_id in [self.PROT_ID_IN, self.PROT_ID_OUT]:
  150. if service_id == wetation_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_BMP:
  151. rv = True
  152. for main_key in [bmp_180.KEY_PRESSURE, bmp_180.KEY_TEMPERATURE, bmp_180.KEY_TIME]:
  153. if main_key not in data:
  154. rv = False
  155. break
  156. else:
  157. for sub_key in ['mean', 'min_val', 'max_val', 'quantifier']:
  158. if not isinstance(data[main_key].get(sub_key), numbers.Number):
  159. rv = False
  160. break
  161. elif service_id == wetation_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_DHT:
  162. rv = True
  163. for main_key in [dht_22.KEY_HUMIDITY, dht_22.KEY_TEMPERATURE, dht_22.KEY_TIME]:
  164. if main_key not in data:
  165. rv = False
  166. break
  167. else:
  168. for sub_key in ['mean', 'min_val', 'max_val', 'quantifier']:
  169. if not isinstance(data[main_key].get(sub_key), numbers.Number):
  170. rv = False
  171. break
  172. if rv is False:
  173. logger.warning("Validation failed for message: prot_id=%s, service_id=%s, data_id=%s, data=%s", repr(prot_id), repr(service_id), repr(data_id), repr(data))
  174. return rv
  175. def __prot_resp_callbacks__(self, msg, prot_id):
  176. service_id = msg.get_service_id()
  177. data_id = msg.get_data_id()
  178. data = msg.get_data()
  179. if self.__validate_response_data__(prot_id, service_id, data_id, data):
  180. wx.CallAfter(self.__data__, prot_id, service_id, data_id, data)
  181. return wetation_protocol.my_base_protocol_tcp.STATUS_OKAY, None
  182. else:
  183. wx.CallAfter(self.__no_data__, prot_id, service_id, data_id)
  184. return wetation_protocol.my_base_protocol_tcp.STATUS_SERVICE_OR_DATA_UNKNOWN, None
  185. def __no_data__(self, prot_id, service_id, data_id):
  186. cn = self.PROT_NAMES[prot_id]
  187. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + cn)
  188. if prot_id == self.PROT_ID_GARAGE:
  189. if service_id == garage_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == garage_protocol.my_base_protocol_tcp.GATE_POSITION:
  190. logger.warning('Resetting GUI elements for %s', cn)
  191. self.heading_garage.Show(False)
  192. self.gate_oc.Show(False)
  193. self.gate_open.Show(False)
  194. self.gate_position.Show(False)
  195. self.gate_close.Show(False)
  196. self.Layout()
  197. return
  198. elif service_id == garage_protocol.my_base_protocol_tcp.SID_EXECUTE_RESPONSE and data_id == garage_protocol.my_base_protocol_tcp.OPEN_CLOSE_GATE:
  199. return
  200. elif prot_id in [self.PROT_ID_IN, self.PROT_ID_OUT]:
  201. if service_id == wetation_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_BMP:
  202. logger.warning('Resetting GUI elements for %s', cn)
  203. txt_pressure = '- hPa'
  204. if prot_id == self.PROT_ID_IN:
  205. self.in_pressure.SetLabel(txt_pressure)
  206. else:
  207. self.out_pressure.SetLabel(txt_pressure)
  208. self.Layout()
  209. return
  210. elif service_id == wetation_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_DHT:
  211. logger.warning('Resetting GUI elements for %s', cn)
  212. txt_temperature = '-.- °C'
  213. txt_humidity = '-.- %'
  214. if prot_id == self.PROT_ID_IN:
  215. self.in_temperature.SetLabel(txt_temperature)
  216. self.in_humidity.SetLabel(txt_humidity)
  217. else:
  218. self.out_temperature.SetLabel(txt_temperature)
  219. self.out_humidity.SetLabel(txt_humidity)
  220. self.Layout()
  221. return
  222. logger.warning("Unknown response with no valid data for prot_id %d, service_id=%d, data_id=%d", prot_id, service_id, data_id)
  223. def __data__(self, prot_id, service_id, data_id, data):
  224. cn = self.PROT_NAMES[prot_id]
  225. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + cn)
  226. if prot_id == self.PROT_ID_GARAGE:
  227. if service_id == garage_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == garage_protocol.my_base_protocol_tcp.GATE_POSITION:
  228. logger.debug('Setting %s position to %3d', cn, int(data * 100))
  229. self.heading_garage.Show(True)
  230. self.gate_oc.Show(True)
  231. self.gate_open.Show(True)
  232. self.gate_position.Show(True)
  233. self.gate_close.Show(True)
  234. self.gate_position.SetValue(int(data * 100))
  235. self.Layout()
  236. return
  237. elif service_id == garage_protocol.my_base_protocol_tcp.SID_EXECUTE_RESPONSE and data_id == garage_protocol.my_base_protocol_tcp.OPEN_CLOSE_GATE:
  238. return
  239. elif prot_id in [self.PROT_ID_IN, self.PROT_ID_OUT]:
  240. if service_id == wetation_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_BMP:
  241. data = helpers.continues_statistic_multivalue(**data)
  242. logger.debug('Setting %s pressure to %4d hPa', cn, data[bmp_180.KEY_PRESSURE].mean)
  243. #
  244. # Current environmental data
  245. if prot_id == self.PROT_ID_IN:
  246. wx.CallAfter(self.update_current_bmp_env_data, data, self.in_pressure)
  247. else:
  248. wx.CallAfter(self.update_current_bmp_env_data, data, self.out_pressure)
  249. return
  250. elif service_id == wetation_protocol.my_base_protocol_tcp.SID_READ_RESPONSE and data_id == wetation_protocol.my_base_protocol_tcp.ENVDATA_STATISTIC_DHT:
  251. data = helpers.continues_statistic_multivalue(**data)
  252. #
  253. # Current environmental data
  254. temp = data[dht_22.KEY_TEMPERATURE].mean
  255. logger.debug('Setting %s temperature to %4.1f °C', cn, temp)
  256. logger.debug('Setting %s humidity to %3.1f %%', cn, data[dht_22.KEY_HUMIDITY].mean)
  257. if prot_id == self.PROT_ID_IN:
  258. if self.__max_temp_in__ is None or temp > self.__max_temp_in__:
  259. self.__max_temp_in__ = temp
  260. if self.__min_temp_in__ is None or temp < self.__min_temp_in__:
  261. self.__min_temp_in__ = temp
  262. wx.CallAfter(self.update_current_dht_env_data, data, self.in_temperature, self.in_humidity, self.in_temperature_min, self.in_temperature_max, self.__min_temp_in__, self.__max_temp_in__)
  263. else:
  264. if self.__max_temp_out__ is None or temp > self.__max_temp_out__:
  265. self.__max_temp_out__ = temp
  266. if self.__min_temp_out__ is None or temp < self.__min_temp_out__:
  267. self.__min_temp_out__ = temp
  268. wx.CallAfter(self.update_current_dht_env_data, data, self.out_temperature, self.out_humidity, self.out_temperature_min, self.out_temperature_max, self.__min_temp_out__, self.__max_temp_out__)
  269. return
  270. logger.warning("Unknown response with valid data for prot_id %d, service_id=%d, data_id=%d", prot_id, service_id, data_id)
  271. def update_current_bmp_env_data(self, env_data, pressure):
  272. pressure.SetLabel('%.0f hPa' % env_data[bmp_180.KEY_PRESSURE].mean)
  273. self.Layout()
  274. def update_current_dht_env_data(self, env_data, temperature, humidity, temperature_min, temperature_max, value_min, value_max):
  275. if isinstance(value_min, numbers.Number) and isinstance(value_max, numbers.Number):
  276. temperature_min.SetLabel('%.1f' % value_min)
  277. temperature_max.SetLabel('%.1f' % value_max)
  278. temperature.SetLabel('%.1f °C' % env_data[dht_22.KEY_TEMPERATURE].mean)
  279. humidity.SetLabel('%.1f %%' % env_data[dht_22.KEY_HUMIDITY].mean)
  280. self.Layout()
  281. def update_time(self):
  282. self.time.SetLabel(time.strftime("%H:%M"))
  283. self.date.SetLabel(time.strftime("%d.%m.%Y"))
  284. self.Layout()
  285. def gate_oc_evt(self, event):
  286. cn = self.PROT_NAMES[self.PROT_ID_GARAGE]
  287. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + cn)
  288. r = wx.MessageDialog(
  289. self,
  290. "Soll das Garagentor betätigt werden?",
  291. "Garage",
  292. wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING
  293. ).ShowModal()
  294. if r == wx.ID_YES:
  295. logger.debug("Gate open/close request")
  296. self.__prot__[self.PROT_ID_GARAGE].send(garage_protocol.my_base_protocol_tcp.SID_EXECUTE_REQUEST, garage_protocol.my_base_protocol_tcp.OPEN_CLOSE_GATE, None)
  297. event.Skip()
  298. def reset_in_temp_minmax_evt(self, event):
  299. self.__min_temp_in__ = None
  300. self.__max_temp_in__ = None
  301. self.in_temperature_min.SetLabel("-.- °C")
  302. self.in_temperature_max.SetLabel("-.- °C")
  303. self.Layout()
  304. event.Skip()
  305. def reset_out_temp_minmax_evt(self, event):
  306. self.__min_temp_out__ = None
  307. self.__max_temp_out__ = None
  308. self.out_temperature_min.SetLabel("-.- °C")
  309. self.out_temperature_max.SetLabel("-.- °C")
  310. self.Layout()
  311. event.Skip()
  312. def run(self):
  313. self.__task_1s__.run()
  314. self.__task_10s__.run()
  315. self.__task_60s__.run()
  316. def close(self):
  317. self.__task_1s__.stop()
  318. self.__task_1s__.join()
  319. self.__task_10s__.stop()
  320. self.__task_10s__.join()
  321. self.__task_60s__.stop()
  322. self.__task_60s__.join()
  323. def __del__(self):
  324. self.close()
  325. class MyApp(wx.App):
  326. def OnInit(self):
  327. self.frame = WetationFrameProt(None, wx.ID_ANY, "")
  328. self.SetTopWindow(self.frame)
  329. self.frame.Show()
  330. return True
  331. if __name__ == "__main__":
  332. report.appLoggingConfigure(os.path.dirname(__file__), config.LOGTARGET, ((config.APP_NAME, 'DEBUG'), ), fmt=config.formatter, host=config.LOGHOST, port=config.LOGPORT)
  333. #
  334. app = MyApp(0)
  335. app.frame.run()
  336. app.MainLoop()
  337. app.frame.close()