Leyk lightener application for raspberry pi and piface module.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

piface_function.py 17KB

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