1 | #!/usr/bin/env python |
||
2 | # -*- coding: utf-8 -*- |
||
3 | |||
4 | import logging |
||
5 | logger = logging.getLogger(__name__) |
||
6 | logger.debug("%s loaded", __name__) |
||
7 | |||
8 | import sys |
||
9 | import argparse |
||
10 | |||
11 | import time # used by: DoorPi.run |
||
12 | import os # used by: DoorPi.load_config |
||
13 | |||
14 | import datetime # used by: parse_string |
||
15 | import cgi # used by: parse_string |
||
16 | import tempfile |
||
17 | #import threading |
||
18 | #import BaseHTTPServer |
||
19 | |||
20 | import metadata |
||
21 | from keyboard.KeyboardInterface import load_keyboard |
||
22 | from sipphone.SipphoneInterface import load_sipphone |
||
23 | from status.webserver import load_webserver |
||
24 | from conf.config_object import ConfigObject |
||
25 | from action.handler import EventHandler |
||
26 | from status.status_class import DoorPiStatus |
||
27 | #from status.webservice import run_webservice, WebService |
||
28 | from action.base import SingleAction |
||
29 | |||
30 | |||
31 | class DoorPiShutdownAction(SingleAction): pass |
||
32 | class DoorPiNotExistsException(Exception): pass |
||
33 | class DoorPiEventHandlerNotExistsException(Exception): pass |
||
34 | class DoorPiRestartException(Exception): pass |
||
35 | |||
36 | class Singleton(type): |
||
37 | _instances = {} |
||
38 | def __call__(cls, *args, **kwargs): |
||
39 | if cls not in cls._instances: |
||
40 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) |
||
41 | return cls._instances[cls] |
||
42 | |||
43 | class DoorPi(object): |
||
44 | __metaclass__ = Singleton |
||
45 | |||
46 | __prepared = False |
||
47 | |||
48 | __config = None |
||
49 | @property |
||
50 | def config(self): return self.__config |
||
51 | |||
52 | __keyboard = None |
||
53 | @property |
||
54 | def keyboard(self): return self.__keyboard |
||
55 | #def get_keyboard(self): return self.__keyboard |
||
56 | |||
57 | __sipphone = None |
||
58 | @property |
||
59 | def sipphone(self): return self.__sipphone |
||
60 | |||
61 | @property |
||
62 | def additional_informations(self): |
||
63 | if self.event_handler is None: return {} |
||
64 | else: return self.event_handler.additional_informations |
||
65 | |||
66 | __event_handler = None |
||
67 | @property |
||
68 | def event_handler(self): return self.__event_handler |
||
69 | |||
70 | __webserver = None |
||
71 | @property |
||
72 | def webserver(self): return self.__webserver |
||
73 | |||
74 | @property |
||
75 | def status(self): return DoorPiStatus(self) |
||
76 | def get_status(self, modules = '', value= '', name = ''): return DoorPiStatus(self, modules, value, name) |
||
77 | |||
78 | @property |
||
79 | def epilog(self): return metadata.epilog |
||
80 | |||
81 | @property |
||
82 | def name(self): return str(metadata.package) |
||
83 | @property |
||
84 | def name_and_version(self): return str(metadata.package) + " - version: " + metadata.version |
||
85 | |||
86 | __shutdown = False |
||
87 | @property |
||
88 | def shutdown(self): return self.__shutdown |
||
89 | |||
90 | _base_path = metadata.doorpi_path |
||
91 | @property |
||
92 | def base_path(self): |
||
93 | if self._base_path is None: |
||
94 | try: |
||
95 | self._base_path = os.path.join(os.path.expanduser('~'), metadata.package) |
||
96 | assert os.access(self._base_path, os.W_OK), 'use fallback for base_path (see tmp path)' |
||
97 | except Exception as exp: |
||
98 | logger.error(exp) |
||
99 | import tempfile |
||
0 ignored issues
–
show
|
|||
100 | self._base_path = tempfile.gettempdir() |
||
101 | return self._base_path |
||
102 | |||
103 | def __init__(self, parsed_arguments = None): |
||
104 | self.__parsed_arguments = parsed_arguments |
||
105 | # for start as daemon - if start as app it will not matter to load this vars |
||
106 | self.stdin_path = '/dev/null' |
||
107 | self.stdout_path = '/dev/null' |
||
108 | self.stderr_path = '/dev/null' |
||
109 | self.pidfile_path = '/var/run/doorpi.pid' |
||
110 | self.pidfile_timeout = 5 |
||
111 | |||
112 | self.__last_tick = time.time() |
||
113 | |||
114 | def doorpi_shutdown(self, time_until_shutdown=10): |
||
115 | time.sleep(time_until_shutdown) |
||
116 | self.__shutdown = True |
||
117 | |||
118 | def prepare(self, parsed_arguments): |
||
119 | logger.debug("prepare") |
||
120 | logger.debug("given arguments argv: %s", parsed_arguments) |
||
121 | |||
122 | self.__config = ConfigObject.load_config(parsed_arguments.configfile) |
||
123 | self._base_path = self.config.get('DoorPi', 'base_path', self.base_path) |
||
124 | self.__event_handler = EventHandler() |
||
125 | |||
126 | if self.config.config_file is None: |
||
127 | self.event_handler.register_action('AfterStartup', self.config.save_config) |
||
128 | self.config.get('EVENT_OnStartup', '10', 'sleep:1') |
||
129 | |||
130 | if 'test' in parsed_arguments and parsed_arguments.test is True: |
||
131 | logger.warning('using only test-mode and destroy after 5 seconds') |
||
132 | self.event_handler.register_action('AfterStartup', DoorPiShutdownAction(self.doorpi_shutdown)) |
||
133 | |||
134 | # register own events |
||
135 | self.event_handler.register_event('BeforeStartup', __name__) |
||
136 | self.event_handler.register_event('OnStartup', __name__) |
||
137 | self.event_handler.register_event('AfterStartup', __name__) |
||
138 | self.event_handler.register_event('BeforeShutdown', __name__) |
||
139 | self.event_handler.register_event('OnShutdown', __name__) |
||
140 | self.event_handler.register_event('AfterShutdown', __name__) |
||
141 | self.event_handler.register_event('OnTimeTick', __name__) |
||
142 | self.event_handler.register_event('OnTimeTickRealtime', __name__) |
||
143 | |||
144 | # register base actions |
||
145 | self.event_handler.register_action('OnTimeTick', 'time_tick:!last_tick!') |
||
146 | |||
147 | # register modules |
||
148 | self.__webserver = load_webserver() |
||
149 | self.__keyboard = load_keyboard() |
||
150 | self.__sipphone = load_sipphone() |
||
151 | self.sipphone.start() |
||
152 | |||
153 | # register eventbased actions from configfile |
||
154 | for event_section in self.config.get_sections('EVENT_'): |
||
155 | logger.info("found EVENT_ section '%s' in configfile", event_section) |
||
156 | event_name = event_section[len('EVENT_'):] |
||
157 | for action in sorted(self.config.get_keys(event_section)): |
||
158 | logger.info("registering action '%s' for event '%s'", action, event_name) |
||
159 | self.event_handler.register_action(event_name, self.config.get(event_section, action)) |
||
160 | |||
161 | # register actions for inputpins |
||
162 | if 'KeyboardHandler' not in self.keyboard.name: |
||
163 | section_name = 'InputPins' |
||
164 | for input_pin in sorted(self.config.get_keys(section_name)): |
||
165 | self.event_handler.register_action('OnKeyPressed_'+input_pin, self.config.get(section_name, input_pin)) |
||
166 | else: |
||
167 | for keyboard_name in self.keyboard.loaded_keyboards: |
||
168 | section_name = keyboard_name+'_InputPins' |
||
169 | for input_pin in self.config.get_keys(section_name, log = False): |
||
170 | self.event_handler.register_action( |
||
171 | 'OnKeyPressed_'+keyboard_name+'.'+input_pin, |
||
172 | self.config.get(section_name, input_pin) |
||
173 | ) |
||
174 | |||
175 | # register actions for DTMF |
||
176 | section_name = 'DTMF' |
||
177 | for DTMF in sorted(self.config.get_keys(section_name)): |
||
178 | self.event_handler.register_action('OnDTMF_'+DTMF, self.config.get(section_name, DTMF)) |
||
179 | |||
180 | # register keep_alive_led |
||
181 | is_alive_led = self.config.get('DoorPi', 'is_alive_led', '') |
||
182 | if is_alive_led is not '': |
||
183 | self.event_handler.register_action('OnTimeSecondEvenNumber', 'out:%s,HIGH,False'%is_alive_led) |
||
184 | self.event_handler.register_action('OnTimeSecondUnevenNumber', 'out:%s,LOW,False'%is_alive_led) |
||
185 | |||
186 | self.__prepared = True |
||
187 | return self |
||
188 | |||
189 | def __del__(self): |
||
190 | return self.destroy() |
||
191 | |||
192 | @property |
||
193 | def modules_destroyed(self): |
||
194 | if len(self.event_handler.sources) > 1: return False |
||
195 | return self.event_handler.idle |
||
196 | |||
197 | def destroy(self): |
||
198 | logger.debug('destroy doorpi') |
||
199 | |||
200 | if not self.event_handler or self.event_handler.threads == None: |
||
201 | DoorPiEventHandlerNotExistsException("don't try to stop, when not prepared") |
||
202 | return False |
||
203 | |||
204 | logger.debug("Threads before starting shutdown: %s", self.event_handler.threads) |
||
205 | |||
206 | self.event_handler.fire_event('BeforeShutdown', __name__) |
||
207 | self.event_handler.fire_event_synchron('OnShutdown', __name__) |
||
208 | self.event_handler.fire_event('AfterShutdown', __name__) |
||
209 | |||
210 | timeout = 5 |
||
211 | waiting_between_checks = 0.5 |
||
212 | time.sleep(waiting_between_checks) |
||
213 | while timeout > 0 and self.modules_destroyed is not True: |
||
214 | # while not self.event_handler.idle and timeout > 0 and len(self.event_handler.sources) > 1: |
||
215 | logger.debug('wait %s seconds for threads %s and %s event', |
||
216 | timeout, len(self.event_handler.threads[1:]), len(self.event_handler.sources)) |
||
217 | logger.trace('still existing threads: %s', self.event_handler.threads[1:]) |
||
218 | logger.trace('still existing event sources: %s', self.event_handler.sources) |
||
219 | time.sleep(waiting_between_checks) |
||
220 | timeout -= waiting_between_checks |
||
221 | |||
222 | if timeout <= 0: |
||
223 | logger.warning("waiting for threads to time out - there are still threads: %s", self.event_handler.threads[1:]) |
||
224 | |||
225 | logger.info('======== DoorPi successfully shutdown ========') |
||
226 | return True |
||
227 | |||
228 | def restart(self): |
||
229 | if self.destroy(): self.run() |
||
230 | else: raise DoorPiRestartException() |
||
231 | |||
232 | def run(self): |
||
233 | logger.debug("run") |
||
234 | if not self.__prepared: self.prepare(self.__parsed_arguments) |
||
235 | |||
236 | self.event_handler.fire_event('BeforeStartup', __name__) |
||
237 | self.event_handler.fire_event_synchron('OnStartup', __name__) |
||
238 | self.event_handler.fire_event('AfterStartup', __name__) |
||
239 | |||
240 | # self.event_handler.register_action('OnTimeMinuteUnevenNumber', 'doorpi_restart') |
||
241 | |||
242 | logger.info('DoorPi started successfully') |
||
243 | logger.info('BasePath is %s', self.base_path) |
||
244 | if self.__webserver: |
||
245 | logger.info('Weburl is %s', self.__webserver.own_url) |
||
246 | else: |
||
247 | logger.info('no Webserver loaded') |
||
248 | |||
249 | time_ticks = 0 |
||
250 | |||
251 | while True and not self.__shutdown: |
||
252 | time_ticks += 0.05 |
||
253 | self.check_time_critical_threads() |
||
254 | if time_ticks > 0.5: |
||
255 | self.__last_tick = time.time() |
||
256 | self.__event_handler.fire_event_asynchron('OnTimeTick', __name__) |
||
257 | time_ticks = 0 |
||
258 | time.sleep(0.05) |
||
259 | return self |
||
260 | |||
261 | def check_time_critical_threads(self): |
||
262 | if self.sipphone: self.sipphone.self_check() |
||
263 | |||
264 | def parse_string(self, input_string): |
||
265 | parsed_string = datetime.datetime.now().strftime(str(input_string)) |
||
266 | |||
267 | if self.keyboard is None or self.keyboard.last_key is None: |
||
268 | self.additional_informations['LastKey'] = "NotSetYet" |
||
269 | else: |
||
270 | self.additional_informations['LastKey'] = str(self.keyboard.last_key) |
||
271 | |||
272 | infos_as_html = '<table>' |
||
273 | for key in self.additional_informations.keys(): |
||
274 | infos_as_html += '<tr><td>' |
||
275 | infos_as_html += '<b>'+key+'</b>' |
||
276 | infos_as_html += '</td><td>' |
||
277 | infos_as_html += '<i>'+cgi.escape( |
||
278 | str(self.additional_informations.get(key)).replace("\r\n", "<br />") |
||
279 | )+'</i>' |
||
280 | infos_as_html += '</td></tr>' |
||
281 | infos_as_html += '</table>' |
||
282 | |||
283 | mapping_table = { |
||
284 | 'INFOS_PLAIN': str(self.additional_informations), |
||
285 | 'INFOS': infos_as_html, |
||
286 | 'BASEPATH': self.base_path, |
||
287 | 'last_tick': str(self.__last_tick) |
||
288 | } |
||
289 | |||
290 | if self.keyboard and 'KeyboardHandler' not in self.keyboard.name: |
||
291 | for output_pin in self.config.get_keys('OutputPins', log = False): |
||
292 | mapping_table[self.config.get('OutputPins', output_pin, log = False)] = output_pin |
||
293 | elif self.keyboard and 'KeyboardHandler' in self.keyboard.name: |
||
294 | for outputpin_section in self.config.get_sections('_OutputPins', False): |
||
295 | for output_pin in self.config.get_keys(outputpin_section, log = False): |
||
296 | mapping_table[self.config.get(outputpin_section, output_pin, log = False)] = output_pin |
||
297 | |||
298 | for key in mapping_table.keys(): |
||
299 | parsed_string = parsed_string.replace( |
||
300 | "!"+key+"!", |
||
301 | mapping_table[key] |
||
302 | ) |
||
303 | |||
304 | for key in self.additional_informations.keys(): |
||
305 | parsed_string = parsed_string.replace( |
||
306 | "!"+key+"!", |
||
307 | str(self.additional_informations[key]) |
||
308 | ) |
||
309 | |||
310 | return parsed_string |
||
311 | |||
312 | if __name__ == '__main__': |
||
313 | raise Exception('use main.py to start DoorPi') |
It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior: