Leyk lightener application for raspberry pi and piface module.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

piface_function.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. try:
  4. import pifacedigitalio
  5. except ImportError:
  6. pifacedigitalio = None
  7. import geo
  8. import logging
  9. import state_machine
  10. import task
  11. import random
  12. import time
  13. logger = logging.getLogger('APP')
  14. geo_position = geo.gps.coordinate(lat=49.976596, lon=9.1481443)
  15. class pi_face(object):
  16. LOG_PREFIX = 'PiFace:'
  17. PF_OUT_NAMES = ['Output 0', # 0
  18. 'Output 1', # 1
  19. 'Output 2', # 2
  20. 'Ploenlein', # 3
  21. 'Bakery', # 4
  22. 'Mill', # 5
  23. 'Reese House', # 6
  24. 'Bake House'] # 7
  25. def __init__(self):
  26. if pifacedigitalio is not None:
  27. pifacedigitalio.init()
  28. pi = pifacedigitalio.PiFaceDigital()
  29. self.__pf_outputs__ = pi.output_pins
  30. self.__pf_output_states__ = 8 * [False]
  31. #
  32. self._reload = 0
  33. self._task_10ms = task.periodic(0.01 - 0.007, self.task_10ms)
  34. #
  35. for i in range(0, 8):
  36. self.set_output(i, self.__pf_output_states__[i])
  37. def set_output(self, index, state, tries=5):
  38. tries = min(max(1, tries), 5)
  39. try_txt = ['1st', '2nd', '3rd', '4th', '5th']
  40. state = state is True
  41. if pifacedigitalio is not None:
  42. cnt = 0
  43. while (cnt < tries) and (self.__pf_outputs__[index].value != state):
  44. self.__pf_outputs__[index].value = 1 * state
  45. time.sleep(0.1)
  46. cnt += 1
  47. if self.__pf_outputs__[index].value != state:
  48. logger.warning('%s Control of output[%d] (%s) after %s try not successfull!', self.LOG_PREFIX, index, self.PF_OUT_NAMES[index], try_txt[cnt - 1])
  49. else:
  50. logger.info('%s Set output "%s" to %s.', self.LOG_PREFIX, self.PF_OUT_NAMES[index], repr(state))
  51. self.__pf_output_states__[index] = self.__pf_outputs__[index].value == 1
  52. else:
  53. logger.info('%s Set virtual output "%s" to %s.', self.LOG_PREFIX, self.PF_OUT_NAMES[index], repr(state))
  54. self.__pf_output_states__[index] = state
  55. def get_output(self, index):
  56. return self.__pf_output_states__[index]
  57. def get_ploenlein(self):
  58. return self.get_output(3)
  59. def set_bakery(self, state):
  60. self.set_output(4, state)
  61. def get_bakery(self):
  62. return self.get_output(4)
  63. def set_mill(self, state):
  64. self.set_output(5, state)
  65. def get_mill(self):
  66. return self.get_output(5)
  67. def set_ploenlein(self, state):
  68. self.set_output(3, state)
  69. def set_reese_house(self, state):
  70. self.set_output(6, state)
  71. def get_reese_house(self):
  72. return self.get_output(6)
  73. def set_bake_house(self, state):
  74. if state is True:
  75. self.set_output(7, state)
  76. self._task_10ms.run()
  77. elif state is False:
  78. self._task_10ms.stop()
  79. self.join()
  80. time.sleep(0.1)
  81. self.set_output(7, state)
  82. def get_bake_house(self):
  83. return not self._task_10ms._stopped
  84. def task_10ms(self, task_inst):
  85. if self._reload <= 0:
  86. if pifacedigitalio is not None:
  87. self.__pf_outputs__[7].turn_on()
  88. self._reload = random.choice(2 * [0] + 20 * [1] + 1 * [2])
  89. else:
  90. if pifacedigitalio is not None:
  91. self.__pf_outputs__[7].turn_off()
  92. self._reload -= 1
  93. def join(self):
  94. self._task_10ms.join()
  95. def stop(self):
  96. self._task_10ms.stop()
  97. self._task_10ms.join()
  98. for i in range(0, 8):
  99. self.control_output(i, False)
  100. pifacedigitalio.deinit()
  101. def __del__(self):
  102. self.stop()
  103. class state_machine_mode(state_machine.state_machine):
  104. LOG_PREFIX = 'LeykMode:'
  105. STATE_AUTOMATIC = 'automatic'
  106. STATE_MANUAL = 'manual'
  107. CONDITION_EXTERNAL_TRIGGER = 'external_trigger'
  108. TRANSITIONS = {
  109. STATE_AUTOMATIC: (
  110. (CONDITION_EXTERNAL_TRIGGER, 1, STATE_MANUAL),
  111. ),
  112. STATE_MANUAL: (
  113. (CONDITION_EXTERNAL_TRIGGER, 1, STATE_AUTOMATIC),
  114. ),
  115. }
  116. def __init__(self, **kwargs):
  117. state_machine.state_machine.__init__(self, self.STATE_AUTOMATIC, logging.INFO)
  118. self.__reset_triggers__()
  119. def __reset_triggers__(self):
  120. self.__to_automatic__ = False
  121. self.__to_manual__ = False
  122. def external_trigger(self):
  123. rv = False
  124. if self.this_state() == self.STATE_AUTOMATIC:
  125. rv = self.__to_manual__
  126. elif self.this_state() == self.STATE_MANUAL:
  127. rv = self.__to_automatic__
  128. self.__reset_triggers__()
  129. return rv
  130. def trigger_to_maual(self):
  131. if self.this_state() == self.STATE_AUTOMATIC:
  132. self.__to_manual__ = True
  133. return True
  134. return False
  135. def trigger_to_automatic(self):
  136. if self.this_state() == self.STATE_MANUAL:
  137. self.__to_automatic__ = True
  138. return True
  139. return False
  140. class state_machine_day_state(state_machine.state_machine):
  141. LOG_PREFIX = 'LeykState:'
  142. STATE_IDLE = 'idle'
  143. STATE_WAKE = 'wake'
  144. STATE_SUNRISE = 'sunrise'
  145. STATE_SUNSET = 'sunset'
  146. STATE_SLEEP = 'sleep'
  147. CONDITION_WAKE = 'condition_wake'
  148. CONDITION_SUNRISE = 'condition_sunrise'
  149. CONDITION_SUNSET = 'condition_sunset'
  150. CONDITION_SLEEP = 'condition_sleep'
  151. CONDITION_IDLE = 'condition_idle'
  152. TRANSITIONS = {
  153. STATE_IDLE: (
  154. (CONDITION_WAKE, 1, STATE_WAKE),
  155. (CONDITION_SUNRISE, 1, STATE_SUNRISE),
  156. (CONDITION_SUNSET, 1, STATE_SUNSET),
  157. (CONDITION_SLEEP, 1, STATE_SLEEP),
  158. ),
  159. STATE_WAKE: (
  160. (CONDITION_SUNRISE, 1, STATE_SUNRISE),
  161. (CONDITION_IDLE, 1, STATE_IDLE),
  162. ),
  163. STATE_SUNRISE: (
  164. (CONDITION_SLEEP, 1, STATE_SLEEP),
  165. (CONDITION_SUNSET, 1, STATE_SUNSET),
  166. (CONDITION_IDLE, 1, STATE_IDLE),
  167. ),
  168. STATE_SUNSET: (
  169. (CONDITION_SLEEP, 1, STATE_SLEEP),
  170. (CONDITION_IDLE, 1, STATE_IDLE),
  171. ),
  172. STATE_SLEEP: (
  173. (CONDITION_WAKE, 1, STATE_WAKE),
  174. (CONDITION_SUNRISE, 1, STATE_SUNRISE),
  175. (CONDITION_IDLE, 1, STATE_IDLE),
  176. ),
  177. }
  178. def __init__(self, **kwargs):
  179. state_machine.state_machine.__init__(self, self.STATE_IDLE, logging.INFO, **kwargs)
  180. def __current_state_calc__(self):
  181. def wake_time():
  182. tm = time.localtime()
  183. tm = list(tm)
  184. tm[3] = 6 # tm_hour
  185. tm[4] = 0 # tm_min
  186. tm[5] = 0 # tm_sec=0
  187. return time.mktime(time.struct_time(tm))
  188. def sunrise_time():
  189. return time.mktime(geo.sun.sunrise(geo_position)) + 30 * 60
  190. def sunset_time():
  191. return time.mktime(geo.sun.sunset(geo_position)) - 30 * 60
  192. def sleep_time():
  193. tm = time.localtime()
  194. tm = list(tm)
  195. tm[3] = 21 # tm_hour
  196. tm[4] = 15 # tm_min
  197. tm[5] = 0 # tm_sec=0
  198. return time.mktime(time.struct_time(tm))
  199. now = time.mktime(time.localtime())
  200. if now > sleep_time():
  201. return self.STATE_SLEEP
  202. elif now > sunset_time():
  203. return self.STATE_SUNSET
  204. elif now > sunrise_time():
  205. return self.STATE_SUNRISE
  206. elif now > wake_time():
  207. return self.STATE_WAKE
  208. else:
  209. return self.STATE_IDLE
  210. def condition_wake(self):
  211. if self.condition_idle():
  212. return False
  213. return self.__current_state_calc__() == self.STATE_WAKE
  214. def condition_sunrise(self):
  215. if self.condition_idle():
  216. return False
  217. return self.__current_state_calc__() == self.STATE_SUNRISE
  218. def condition_sunset(self):
  219. if self.condition_idle():
  220. return False
  221. return self.__current_state_calc__() == self.STATE_SUNSET
  222. def condition_sleep(self):
  223. if self.condition_idle():
  224. return False
  225. return self.__current_state_calc__() == self.STATE_SLEEP
  226. def condition_idle(self):
  227. return not self.sm_mode.this_state_is(self.sm_mode.STATE_AUTOMATIC)
  228. def report_sunset_sunrise(self):
  229. state_machine.logger.debug('Sunrise: %s - Sunset: %s;', time.strftime("%H:%M", geo.sun.sunrise(geo_position)), time.strftime("%H:%M", geo.sun.sunset(geo_position))
  230. )
  231. class leyk(object):
  232. LOG_PREFIX = 'Leyk:'
  233. def __init__(self):
  234. self.sm_mode = state_machine_mode()
  235. self.sm_day_state = state_machine_day_state(sm_mode=self.sm_mode)
  236. self.__pf__ = pi_face()
  237. self._queue = task.threaded_queue()
  238. self._queue.run()
  239. self._task_1s = task.periodic(1, self.task_1s)
  240. self._task_1s.run()
  241. self.sm_mode.register_state_change_callback(self.sm_mode.STATE_MANUAL, None, self._queue.clean_queue)
  242. self.sm_day_state.register_state_change_callback(None, None, self.clean_queue)
  243. self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SLEEP, None, self.sm_day_state.report_sunset_sunrise)
  244. self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SLEEP, None, self.fill_sleep_queue)
  245. self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNRISE, None, self.sm_day_state.report_sunset_sunrise)
  246. self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNRISE, None, self.fill_sunrise_queue)
  247. self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNSET, None, self.sm_day_state.report_sunset_sunrise)
  248. self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_SUNSET, None, self.fill_sunset_queue)
  249. self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_WAKE, None, self.sm_day_state.report_sunset_sunrise)
  250. self.sm_day_state.register_state_change_callback(self.sm_day_state.STATE_WAKE, None, self.fill_wake_queue)
  251. def set_mode(self, mode):
  252. if mode == self.sm_mode.STATE_AUTOMATIC:
  253. return self.sm_mode.trigger_to_automatic()
  254. elif mode == self.sm_mode.STATE_MANUAL:
  255. return self.sm_mode.trigger_to_maual()
  256. else:
  257. return False
  258. def get_mode(self):
  259. return self.sm_mode.this_state()
  260. def get_state(self):
  261. return self.sm_day_state.this_state()
  262. def __queue_wrapper__(self, queue_inst, function, *args, **kwargs):
  263. function(*args, **kwargs)
  264. def set_ploenlein(self, state):
  265. if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
  266. self.__pf__.set_ploenlein(state)
  267. return True
  268. else:
  269. return False
  270. def get_ploenlein(self):
  271. return self.__pf__.get_ploenlein()
  272. def set_bakery(self, state):
  273. if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
  274. self.__pf__.set_bakery(state)
  275. return True
  276. else:
  277. return False
  278. def get_bakery(self):
  279. return self.__pf__.get_bakery()
  280. def set_bake_house(self, state):
  281. if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
  282. self.__pf__.set_bake_house(state)
  283. return True
  284. else:
  285. return False
  286. def get_bake_house(self):
  287. return self.__pf__.get_bake_house()
  288. def set_mill(self, state):
  289. if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
  290. self.__pf__.set_mill(state)
  291. return True
  292. else:
  293. return False
  294. def get_mill(self):
  295. return self.__pf__.get_mill()
  296. def set_reese_house(self, state):
  297. if self.sm_mode.this_state_is(self.sm_mode.STATE_MANUAL):
  298. self.__pf__.set_reese_house(state)
  299. return True
  300. else:
  301. return False
  302. def get_reese_house(self):
  303. return self.__pf__.get_reese_house()
  304. def wake_time(self):
  305. tm = time.localtime()
  306. tm = list(tm)
  307. tm[3] = 6 # tm_hour
  308. tm[4] = 0 # tm_min
  309. tm[5] = 0 # tm_sec=0
  310. return time.mktime(time.struct_time(tm))
  311. def sunrise_time(self):
  312. return time.mktime(geo.sun.sunrise(geo_position)) + 30 * 60
  313. def sunset_time(self):
  314. return time.mktime(geo.sun.sunset(geo_position)) - 30 * 60
  315. def sleep_time(self):
  316. tm = time.localtime()
  317. tm = list(tm)
  318. tm[3] = 21 # tm_hour
  319. tm[4] = 15 # tm_min
  320. tm[5] = 0 # tm_sec=0
  321. return time.mktime(time.struct_time(tm))
  322. def identify_current_state(self):
  323. now = time.mktime(time.localtime())
  324. if now > self.sleep_time():
  325. return self.ST_SLEEP
  326. elif now > self.sunset_time():
  327. return self.ST_SUNSET
  328. elif now > self.sunrise_time():
  329. return self.ST_SUNRISE
  330. elif now > self.wake_time():
  331. return self.ST_WAKE
  332. else:
  333. return self.ST_SLEEP
  334. def wait(self, queue_inst, delay):
  335. if delay > 0:
  336. logger.debug('%s Wait for %d seconds initiated. %d elements left in queue.', self.LOG_PREFIX, delay, queue_inst.qsize())
  337. cnt = 0
  338. while cnt < delay * 5:
  339. if queue_inst.qsize() == 0:
  340. logger.debug('%s Quit wait for %d seconds. %d elements left in queue.', self.LOG_PREFIX, delay, queue_inst.qsize())
  341. break
  342. time.sleep(0.2)
  343. cnt += 1
  344. def clean_queue(self):
  345. if self._queue.qsize() > 0:
  346. logger.info('Cleaning up remaining %d elements from queue', self._queue.qsize())
  347. self._queue.clean_queue()
  348. def fill_wake_queue(self):
  349. no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE)
  350. # WAKE
  351. self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bakery, True)
  352. self._queue.enqueue(2, self.wait, 0 if no_delay else 5 * 60)
  353. self._queue.enqueue(3, self.__queue_wrapper__, self.__pf__.set_bake_house, True)
  354. self._queue.enqueue(4, self.wait, 0 if no_delay else 10 * 60)
  355. self._queue.enqueue(5, self.__queue_wrapper__, self.__pf__.set_reese_house, True)
  356. self._queue.enqueue(6, self.wait, 0 if no_delay else 7 * 60)
  357. self._queue.enqueue(7, self.__queue_wrapper__, self.__pf__.set_ploenlein, True)
  358. self._queue.enqueue(8, self.wait, 0 if no_delay else 8 * 60)
  359. self._queue.enqueue(9, self.__queue_wrapper__, self.__pf__.set_mill, True)
  360. def fill_sunrise_queue(self):
  361. no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE)
  362. # SUNRISE
  363. self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, True)
  364. self._queue.enqueue(2, self.__queue_wrapper__, self.__pf__.set_ploenlein, False)
  365. self._queue.enqueue(3, self.wait, 0 if no_delay else 5 * 60)
  366. self._queue.enqueue(4, self.__queue_wrapper__, self.__pf__.set_mill, False)
  367. self._queue.enqueue(5, self.wait, 0 if no_delay else 8 * 60)
  368. self._queue.enqueue(6, self.__queue_wrapper__, self.__pf__.set_bakery, False)
  369. self._queue.enqueue(7, self.wait, 0 if no_delay else 12 * 60)
  370. self._queue.enqueue(8, self.__queue_wrapper__, self.__pf__.set_reese_house, False)
  371. def fill_sunset_queue(self):
  372. no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE)
  373. # SUNSET
  374. self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, True)
  375. self._queue.enqueue(2, self.__queue_wrapper__, self.__pf__.set_bakery, True)
  376. self._queue.enqueue(3, self.wait, 0 if no_delay else 10 * 60)
  377. self._queue.enqueue(4, self.__queue_wrapper__, self.__pf__.set_reese_house, True)
  378. self._queue.enqueue(5, self.wait, 0 if no_delay else 8 * 60)
  379. self._queue.enqueue(6, self.__queue_wrapper__, self.__pf__.set_mill, True)
  380. self._queue.enqueue(7, self.wait, 0 if no_delay else 7 * 60)
  381. self._queue.enqueue(8, self.__queue_wrapper__, self.__pf__.set_ploenlein, True)
  382. def fill_sleep_queue(self):
  383. no_delay = self.sm_day_state.previous_state_was(self.sm_day_state.STATE_IDLE)
  384. # SLEEP
  385. self._queue.enqueue(1, self.__queue_wrapper__, self.__pf__.set_bake_house, False)
  386. self._queue.enqueue(2, self.wait, 0 if no_delay else 5 * 60)
  387. self._queue.enqueue(3, self.__queue_wrapper__, self.__pf__.set_bakery, False)
  388. self._queue.enqueue(4, self.wait, 0 if no_delay else 9 * 60)
  389. self._queue.enqueue(5, self.__queue_wrapper__, self.__pf__.set_ploenlein, False)
  390. self._queue.enqueue(6, self.wait, 0 if no_delay else 9 * 60)
  391. self._queue.enqueue(7, self.__queue_wrapper__, self.__pf__.set_mill, False)
  392. self._queue.enqueue(8, self.wait, 0 if no_delay else 6 * 60)
  393. self._queue.enqueue(9, self.__queue_wrapper__, self.__pf__.set_reese_house, False)
  394. def task_1s(self, task_inst):
  395. self.sm_mode.work()
  396. self.sm_day_state.work()
  397. def join(self):
  398. self._task_1s.join()
  399. self._queue.join()
  400. def stop(self):
  401. self._task_1s.stop()
  402. self._queue.stop()
  403. def __del__(self):
  404. self.stop()