diff --git a/.gitignore b/.gitignore index 4edb0069..78234dac 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,5 @@ docs/_build/ # PyBuilder target/ -.pypirc \ No newline at end of file +.pypirc +doorpi/docs/service/doorpi \ No newline at end of file diff --git a/.travis/install.sh b/.travis/install.sh index f62ab411..22f7ff54 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -3,7 +3,10 @@ set -e # Exit immediately if a command exits with a non-zero status. set -x # Print commands and their arguments as they are executed. +sudo pip install --upgrade linphone4raspberry python-daemon + if [[ $START_MODE = "application" ]]; then + sudo mkdir /usr/local/etc/DoorPi && sudo chmod -R a+rw /usr/local/etc/DoorPi python setup.py install fi diff --git a/.travis/prepare.sh b/.travis/prepare.sh deleted file mode 100644 index b80eaf8b..00000000 --- a/.travis/prepare.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -set -e # Exit immediately if a command exits with a non-zero status. -set -x # Print commands and their arguments as they are executed. - -#pip install --upgrade pip -#pip install --upgrade setuptools diff --git a/.travis/script.sh b/.travis/script.sh index aa689148..cc16ca5f 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -4,7 +4,7 @@ set -x # Print commands and their arguments as they are executed. if [[ $START_MODE = "application" ]]; then - sudo doorpi_cli --trace --test + doorpi_cli --trace --test fi if [[ $START_MODE = "daemon" ]]; then @@ -30,5 +30,6 @@ if [[ $START_MODE = "daemon" ]]; then if [ $? -ne 3 ]; then exit 1 fi + cat /usr/local/etc/DoorPi/log/doorpi.log exit 0 fi diff --git a/README.rst b/README.rst index 1f430e7f..ffe4bdf8 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ Einführung --------------- Ziel des Projektes DoorPi ist die Steuerung einer Türsprechanlage mittels einem Einplatiniencomputer wie dem Raspberry Pi und dem Kommunikationsprotokoll `VoIP`_. -DoorPi ist ein Event-Action basierendes System. Es gibt Komponenten die Events auslösen und Komponenten die aufgrund dieser Events reagieren. Dazu sollen Ereignisse (Events) wie "Drücken einer Türklingel" oder "RFID Chip xyz vorgehalten" die Auslöser von Aktionen (Actions) wie "Anruf bei Telefon xyz", "E-Mail an xxx" oder "Öffne Tür" sein. +DoorPi ist ein Event-Action basierendes System. Es gibt Komponenten die Events auslösen und Komponenten, die aufgrund dieser Events reagieren. Dazu sollen Ereignisse (Events) wie "Drücken einer Türklingel" oder "RFID Chip xyz vorgehalten" die Auslöser von Aktionen (Actions) wie "Anruf bei Telefon xyz", "E-Mail an xxx" oder "Öffne Tür" sein. --------------- Event-Quellen @@ -84,33 +84,33 @@ Mittlerweile gibt es auch Video-Support, so dass an der Haustür eine Kamera ins Installation ----------------- -Es wird aktuell empfohlen DoorPi nur auf Raspbian Wheezy zu installieren da das Installationskrip für Raspbian Jessie angepasst werden muss. +Die Installationen werden `hier beschrieben `_ + +Empfohlen wird die Installation via `PyPi`_ (in Kurzfassung): via `PyPi`_: .. code-block:: bash sudo pip install doorpi && - doorpi_cli --trace + sudo doorpi_cli --trace -via `GitHub`_: -.. code-block:: bash +----------------- +Daemon +----------------- - sudo rm -r -f /tmp/DoorPi - git clone https://github.com/motom001/DoorPi.git /tmp/DoorPi - cd /tmp/DoorPi - sudo python setup.py install - doorpi_cli --trace +Als Erstes sollte DoorPi als Anwendung gestartet werden, damit mögliche Fehler sofort angezeigt werden können. Außerdem wird beim ersten Start das DoorPi Basis-Verzeichnis unter /usr/local/etc/DoorPi eingerichtet. + +Die Einrichtung als Daemon wird `hier beschrieben `_ - ----------------- Konfiguration ----------------- Der Start von DoorPi endet mit der Ausgabe der Weboberfläche-URL wie hier: - 2015-09-10 17:52:28,085 [INFO] [doorpi.status.webserver] DoorPiWeb URL is http://raspberrypi:53540/ + 2015-09-10 17:52:28,085 [INFO] [doorpi.status.webserver] DoorPiWeb URL is http://raspberrypi/ Aktuell bin ich noch nicht dazu gekommen, die Config pro Gerät (GPIO, PiFace, ...) zu individualisieren. In der Weboberfläche ist auf dem Startbildschirm die Übersicht der Module (z.B. GPIO). Rechts von dem Modul gibt es den Button Info. @@ -118,93 +118,32 @@ In der Info-Seite findest Du neben der Beschreibung auch die möglichen Paramete Parallel dazu gibt es in der Navigation den Konfig-Editor. Dort kannst Du die Config bearbeiten, wenn Du weißt, welche Parameter wo hin gehören. Auch die Config abspeichern kannst Du in der Übersicht. ------------------ -Daemon ------------------ - -Anleitung um DoorPi als Daemon einzurichten ist hier zu finden: -https://github.com/motom001/DoorPi/tree/master/doorpi/docs/service +Hilfe zur DoorPi Konfiguration (egal ob im Dashboard oder per Konfigurationsdatei) gibt es im DoorPi Wiki: -Es sollte aber auf jeden Fall der `BASE_PATH `_ auf den Ablageort der Config-Datei angepasst werden. +`DoorPi Wiki `_ ----------------- -DoorPi Threads +DoorPi-Hilfe ----------------- -Link zu Foren mit DoorPi Threads: +Link zu Foren mit DoorPi Beiträgen: + +`DoorPi Forum `_ -:forum-raspberrypi.de: `[Haussteuerung] DoorPi (VoIP Wechselsprechanlage / Türsprechanlage mit Video-Support) `_ +`[Haussteuerung] DoorPi (VoIP Wechselsprechanlage / Türsprechanlage mit Video-Support) `_ -:ip-symcon.de: `DoorPI / VoIP Door-Intercomstation with Raspberry Pi `_ +`DoorPI / VoIP Door-Intercomstation with Raspberry Pi `_ ============= -English +Changelog ============= ---------------- -Introduction ---------------- - -coming soon - ---------------- -Event-Sorces ---------------- - -coming soon - ------------------ -Action-Receiver ------------------ - -coming soon - ------------------ -Examples ------------------ - -coming soon - ------------------ -Installation ------------------ - -via `PyPi`_: - -.. code-block:: bash - - sudo pip install doorpi && - sudo doorpi_cli --trace - -via `GitHub`_: - -.. code-block:: bash - - sudo rm -r -f /tmp/DoorPi - git clone https://github.com/motom001/DoorPi.git /tmp/DoorPi - cd /tmp/DoorPi - sudo python setup.py install - doorpi_cli --trace - ------------------ -Configuration ------------------ - -coming soon - ------------------ -Daemon ------------------ -The readme to install doorpi as daemon is here: -https://github.com/motom001/DoorPi/tree/master/doorpi/docs/service +see `changelog.txt`_ -But you should change the `BASE_PATH `_ to the path of the config file. .. _VoIP: https://de.wikipedia.org/wiki/IP-Telefonie .. _PyPi: https://pypi.python.org/pypi/DoorPi .. _GitHub: https://github.com/motom001/DoorPi -.. _GitHubDaemonReadme: https://github.com/motom001/DoorPi/tree/master/doorpi/docs/service -.. _GitHubDaemonFileLine17: https://github.com/motom001/DoorPi/blob/master/doorpi/docs/service/doorpi#L17 .. |travis_status_master| image:: https://travis-ci.org/motom001/DoorPi.svg?branch=master :target: https://travis-ci.org/motom001/DoorPi diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 00000000..44abad87 --- /dev/null +++ b/changelog.txt @@ -0,0 +1,49 @@ +============= +Changelog +============= + +Legend: ++ add +- remove +~ change +/ reference to commit +! fix issue or kown bug + +Version: 2.4.1.8 (2016-03-12) +------------------------------- +/ 20895649e196795734acd9b5016684e7dd6af0f8 +~ BASE_PATH set to /usr/local/etc/DoorPi + +/ 598f559cb9cfab3fe5533d2cd2ba2d04c6fc6ce1 +~ daemon file template with exitcode (for jessie) (thx to Postler) + + +/ 5f681f10b18fcfcdac84ab06e9ae9a500549450c +! fix "Exception NameError: 'bool' object has no attribute 'upper'" @ doorpi/keyboard/from_gpio.py (thx to cubeschrauber) + +/ f24ac1b8df34bfe17f08b3ae78558ab10af2c22d ++ add cat of logfile for test @ travis + +/ 047aa727da62262f5b0815222d65ffec123af549 +~ change code to PEP8 style ++ add changelog reference to README.rst +~ change behavior of restart daemon: check until doorpi is stopped before to start it again (default sleep between 2 sec) #132 +- remove of duplicate parameter start in daemonfile doorpi/docs/daemon/doorpi.tpl +~ change doorpi/docs/daemon/README.md +~ change behavior of doorpi_base_path - is now only /usr/local/etc/DoorPi on posix systems and raise exception if this couldn't create ++ add mkdir and chmod to .travis/install.sh to preserve error on new doorpi_base_path behavior +- remove useless logline when stopping doorpi + +/ 7119caffd79cd02322a224df8ae102e1096e9e8a ++ add changelog to README.rst ++ config property keyboard piface pressed_on_keydown #134 +~ ATTENTION: changed behavior of keyboard piface event OnKeyPressed from fire with OnKeyUp to fire with OnKeyDown #134 ++ config property keyboard gpio pressed_on_keydown #134 ++ config property keyboard gpio mode ++ config property keyboard gpio pull_up_down #134 ++ config property keyboard filesystem pressed_on_keydown #134 +~ try to register keyboard gpio inputpins as list but except TypeError and register it as int (old gpio behavior) #133 +~ change code to PEP8 style + +/ 50943b5c30cbe5a10678b5ea929afa200916d9a5 +- remove sudo for `doorpi_cli --trace --test` for test @ travis in application mode diff --git a/doorpi/docs/service/README.md b/doorpi/docs/service/README.md deleted file mode 100644 index 42a507c2..00000000 --- a/doorpi/docs/service/README.md +++ /dev/null @@ -1,10 +0,0 @@ - -try first to run as application via `sudo doorpi_cli --trace` and see logoutput at screen - -``` -sudo cp doorpi /etc/init.d/doorpi -sudo update-rc.d doorpi defaults -sudo service doorpi start -``` - -see logfile @ /var/log/doorpi/* diff --git a/doorpi/docs/service/doorpi.tpl b/doorpi/docs/service/doorpi.tpl index e246cdad..485c22c4 100755 --- a/doorpi/docs/service/doorpi.tpl +++ b/doorpi/docs/service/doorpi.tpl @@ -21,7 +21,7 @@ SCRIPTNAME=!!daemon_folder!!/!!daemon_name!! # Exit if the package is not installed if [ none != "$DAEMON" ] && [ ! -x "$DAEMON" ] ; then - exit 0 + exit 3 fi # Read configuration variable file if it is present @@ -35,17 +35,20 @@ fi do_start_cmd() { - status_of_proc "$DAEMON" "$NAME" > /dev/null && return 1 - $DAEMON start $DAEMON_ARGS || return 2 + status_of_proc "$DAEMON" "$NAME" > /dev/null && return 1 + $DAEMON start $DAEMON_ARGS || return 2 } do_test_cmd() { - status_of_proc "$DAEMON" "$NAME" > /dev/null && return 1 - $DAEMON start $DAEMON_ARGS --test || return 2 + status_of_proc "$DAEMON" "$NAME" > /dev/null && return 1 + $DAEMON start $DAEMON_ARGS --test || return 2 +} +is_doorpi_running() +{ + status_of_proc "$DAEMON" "$NAME" > /dev/null && return 0 + return 1 } - - do_stop_cmd() { status_of_proc "$DAEMON" "$NAME" > /dev/null || return 1 @@ -54,21 +57,16 @@ do_stop_cmd() return 0 } +EX=0 case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start_cmd case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - start) - [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" - do_test_cmd - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + 2) + [ "$VERBOSE" != no ] && log_end_msg 1 + EX=1 ;; esac ;; stop) @@ -76,17 +74,30 @@ case "$1" in do_stop_cmd case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + 2) + [ "$VERBOSE" != no ] && log_end_msg 1 + EX=1 ;; esac ;; restart) [ "$VERBOSE" != no ] && log_daemon_msg "Restarting $DESC" "$NAME" do_stop_cmd - sleep 5 + # issue #132 + echo waiting until !!package!! is stopped + sleep 3 + is_doorpi_running + while [ $? -eq 0 ]; do + echo !!package!! is still running - wait one more second + is_doorpi_running + sleep 1 + done + sleep 2 do_start_cmd case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + 2) + [ "$VERBOSE" != no ] && log_end_msg 1 + EX=1 ;; esac ;; status) @@ -97,3 +108,5 @@ case "$1" in exit 3 ;; esac + +exit $EX diff --git a/doorpi/keyboard/AbstractBaseClass.py b/doorpi/keyboard/AbstractBaseClass.py index dd40ac01..221a5d6c 100644 --- a/doorpi/keyboard/AbstractBaseClass.py +++ b/doorpi/keyboard/AbstractBaseClass.py @@ -19,34 +19,45 @@ def register_destroy_action(self, destroy_function = False): if destroy_function is False: destroy_function = self.destroy return doorpi.DoorPi().event_handler.register_action('OnShutdown', KeyboardDestroyAction(destroy_function)) - ############## methods to implement ############## + # -------------methods to implement-------------- def __init__(self): raise NotImplementedError("Subclasses should implement this!") - #def destroy(self): pass #logger.warning("Subclasses should implement this!") + + # def destroy(self): pass #logger.warning("Subclasses should implement this!") + def self_test(self): pass # optional - raise NotImplementedError("Subclasses should implement this!") + def status_input(self, pin): raise NotImplementedError("Subclasses should implement this!") + def set_output(self, pin, value, log_output = True): raise NotImplementedError("Subclasses should implement this!") - ############ ############ ############ ############ + # ----------------------------------------------- keyboard_name = '' + _pressed_on_key_down = True + @property def keyboard_typ(self): return self.__class__.__name__ + @property def name(self): if self.keyboard_name is '': return '%s Keyboard' % self.keyboard_typ else: return '%s (Typ: %s) Keyboard' % (self.keyboard_name, self.keyboard_typ) _InputPins = [] + @property def input_pins(self): return self._InputPins _OutputPins = [] + @property def output_pins(self): return self._OutputPins _OutputStatus = {} + @property def output_status(self): return self._OutputStatus last_key = None #@property #def last_key(self): return self._last_key + @property def additional_info(self): return { 'keyboard_name' : self.keyboard_name, @@ -54,6 +65,7 @@ def additional_info(self): return { 'name' : self.name, 'pin' : self.last_key } + @property def pressed_keys(self): pressed_keys = [] @@ -61,11 +73,13 @@ def pressed_keys(self): if self.status_inputpin(input_pin): pressed_keys.append(input_pin) return pressed_keys + @property def pressed_key(self): - pressed_keys = self.pressed_keys() - if len(pressed_keys) > 0: return pressed_keys[0] - else: return None + try: + return self.pressed_keys[0] + except KeyError or IndexError or TypeError: + return None @property def is_destroyed(self): return self.__destroyed @@ -90,7 +104,9 @@ def _fire_EVENT(self, event_name, pin, name): doorpi.DoorPi().event_handler(event_name+'_'+self.keyboard_name+'.'+str(pin), name, self.additional_info) def _fire_OnKeyUp(self, pin, name): self._fire_EVENT('OnKeyUp', pin, name) + def _fire_OnKeyDown(self, pin, name): self._fire_EVENT('OnKeyDown', pin, name) + def _fire_OnKeyPressed(self, pin, name): self._fire_EVENT('OnKeyPressed', pin, name) get_input = status_input diff --git a/doorpi/keyboard/KeyboardInterface.py b/doorpi/keyboard/KeyboardInterface.py index 745568a7..05c7e7eb 100644 --- a/doorpi/keyboard/KeyboardInterface.py +++ b/doorpi/keyboard/KeyboardInterface.py @@ -1,142 +1,148 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -logger = logging.getLogger(__name__) -logger.debug("%s loaded", __name__) - -import importlib - -import doorpi -from doorpi.keyboard.AbstractBaseClass import KeyboardAbstractBaseClass - -class KeyboardImportError(ImportError): pass -class UnknownOutputPin(Exception): pass - -def load_keyboard(): - config_keyboards = doorpi.DoorPi().config.get_keys(section = 'keyboards') - if len(config_keyboards) > 0: - logger.info("using multi-keyboard mode (keyboards: %s)", ', '.join(config_keyboards)) - return KeyboardHandler(config_keyboards) - else: - logger.info("using multi-keyboard mode with dummy keyboard") - return KeyboardHandler(['dummy']) - -def load_single_keyboard(keyboard_name): - conf_pre = keyboard_name+'_' - conf_post = '' - - keyboard_type = doorpi.DoorPi().config.get('keyboards', keyboard_name, 'dummy').lower() - store_if_not_exists = False if keyboard_type == "dummy" else True - - input_pins = doorpi.DoorPi().config.get_keys(conf_pre+'InputPins'+conf_post) - output_pins = doorpi.DoorPi().config.get_keys(conf_pre+'OutputPins'+conf_post) - bouncetime = doorpi.DoorPi().config.get_float(conf_pre+'keyboard'+conf_post, 'bouncetime', 2000, store_if_not_exists = store_if_not_exists) - polarity = doorpi.DoorPi().config.get_int(conf_pre+'keyboard'+conf_post, 'polarity', 0, store_if_not_exists = store_if_not_exists) - - try: - keyboard = importlib.import_module('doorpi.keyboard.from_'+keyboard_type).get( - input_pins = input_pins, - output_pins = output_pins, - bouncetime = bouncetime, - polarity = polarity, - keyboard_name = keyboard_name, - keyboard_type = keyboard_type, - conf_pre = conf_pre, - conf_post = conf_post - ) - except ImportError as exp: - logger.exception('keyboard %s not found @ keyboard.from_%s (msg: %s)', keyboard_name, keyboard_type, exp) - return None - - return keyboard - -class KeyboardHandler(KeyboardAbstractBaseClass): - @property - def name(self): - keyboard_names = [] - for Keyboard in self.__keyboards: - keyboard_names.append(Keyboard) - return 'KeyboardHandler (with %s)' % ', '.join(keyboard_names) - - @property - def input_pins(self): - return_list = [] - for Keyboard in self.__keyboards: - for input_pin in self.__keyboards[Keyboard].input_pins: - return_list.append(Keyboard+'.'+str(input_pin)) - return return_list - - @property - def output_pins(self): - return_list = [] - for Keyboard in self.__keyboards: - for pin in self.__keyboards[Keyboard].output_pins: - return_list.append(Keyboard+'.'+str(pin)) - return return_list - - @property - def output_status(self): - return_dict = {} - for Keyboard in self.__keyboards: - for pin in self.__keyboards[Keyboard].output_pins: - return_dict[Keyboard+'.'+str(pin)] = self.__keyboards[Keyboard].status_output(pin) - return return_dict - - @property - def loaded_keyboards(self): - return_dict = {} - for keyboard in self.__keyboards: - return_dict[keyboard] = self.__keyboards[keyboard].keyboard_typ - return return_dict - - def __init__(self, config_keyboards): - self.__OutputMappingTable = {} - self.__keyboards = {} - for keyboard_name in config_keyboards: - logger.info("trying to add keyboard '%s' to handler", keyboard_name) - self.__keyboards[keyboard_name] = load_single_keyboard(keyboard_name) - if self.__keyboards[keyboard_name] is None: - logger.error("couldn't load keyboard %s", keyboard_name) - del self.__keyboards[keyboard_name] - continue - - output_pins = doorpi.DoorPi().config.get_keys(keyboard_name+'_OutputPins') - for output_pin in output_pins: - output_pin_name = doorpi.DoorPi().config.get(keyboard_name+'_OutputPins', output_pin) - if output_pin_name in self.__OutputMappingTable: - logger.warning('overwriting existing name of outputpin "%s" (exists in %s and %s)', - output_pin_name, - self.__OutputMappingTable[output_pin_name], - keyboard_name - ) - self.__OutputMappingTable[output_pin_name] = keyboard_name - - if len(self.__keyboards) is 0: - logger.error('No Keyboards loaded - load dummy!') - self.__keyboards['dummy'] = load_single_keyboard('dummy') - - def destroy(self): - try: - for Keyboard in self.__keyboards: - self.__keyboards[Keyboard].destroy() - except: pass - - def set_output(self, pin, value, log_output = True): - if pin not in self.__OutputMappingTable: - raise UnknownOutputPin('outputpin with name %s is unknown %s' % (pin, self.__OutputMappingTable)) - self.__keyboards[self.__OutputMappingTable[pin]].set_output(pin, value, log_output) - - def status_input(self, pin): - for keyboard in self.__keyboards: - if pin.startswith(keyboard+'.'): - return self.__keyboards[keyboard].status_input(pin[len(keyboard+'.'):]) - return None - - def status_output(self, pin): - for keyboard in self.__keyboards: - if pin.startswith(keyboard+'.'): - return self.__keyboards[keyboard].status_output(pin[len(keyboard+'.'):]) - return None - +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +logger = logging.getLogger(__name__) +logger.debug("%s loaded", __name__) + +import importlib + +import doorpi +from doorpi.keyboard.AbstractBaseClass import KeyboardAbstractBaseClass + +class KeyboardImportError(ImportError): pass +class UnknownOutputPin(Exception): pass + +def load_keyboard(): + config_keyboards = doorpi.DoorPi().config.get_keys(section = 'keyboards') + if len(config_keyboards) > 0: + logger.info("using multi-keyboard mode (keyboards: %s)", ', '.join(config_keyboards)) + return KeyboardHandler(config_keyboards) + else: + logger.info("using multi-keyboard mode with dummy keyboard") + return KeyboardHandler(['dummy']) + +def load_single_keyboard(keyboard_name): + conf_pre = keyboard_name+'_' + conf_post = '' + + keyboard_type = doorpi.DoorPi().config.get('keyboards', keyboard_name, 'dummy').lower() + store_if_not_exists = False if keyboard_type == "dummy" else True + + section_name = conf_pre+'keyboard'+conf_post + input_pins = doorpi.DoorPi().config.get_keys(conf_pre+'InputPins'+conf_post) + output_pins = doorpi.DoorPi().config.get_keys(conf_pre+'OutputPins'+conf_post) + bouncetime = doorpi.DoorPi().config.get_float(section_name, 'bouncetime', 2000, + store_if_not_exists=store_if_not_exists) + polarity = doorpi.DoorPi().config.get_int(section_name, 'polarity', 0, + store_if_not_exists=store_if_not_exists) + pressed_on_key_down = doorpi.DoorPi().config.get_bool(section_name, 'pressed_on_keydown', + True, store_if_not_exists=store_if_not_exists) + try: + keyboard = importlib.import_module('doorpi.keyboard.from_'+keyboard_type).get( + input_pins=input_pins, + output_pins=output_pins, + bouncetime=bouncetime, + polarity=polarity, + keyboard_name=keyboard_name, + keyboard_type=keyboard_type, + conf_pre=conf_pre, + conf_post=conf_post, + pressed_on_key_down=pressed_on_key_down + ) + except ImportError as exp: + logger.exception('keyboard %s not found @ keyboard.from_%s (msg: %s)', keyboard_name, keyboard_type, exp) + return None + + return keyboard + + +class KeyboardHandler(KeyboardAbstractBaseClass): + @property + def name(self): + keyboard_names = [] + for Keyboard in self.__keyboards: + keyboard_names.append(Keyboard) + return 'KeyboardHandler (with %s)' % ', '.join(keyboard_names) + + @property + def input_pins(self): + return_list = [] + for Keyboard in self.__keyboards: + for input_pin in self.__keyboards[Keyboard].input_pins: + return_list.append(Keyboard+'.'+str(input_pin)) + return return_list + + @property + def output_pins(self): + return_list = [] + for Keyboard in self.__keyboards: + for pin in self.__keyboards[Keyboard].output_pins: + return_list.append(Keyboard+'.'+str(pin)) + return return_list + + @property + def output_status(self): + return_dict = {} + for Keyboard in self.__keyboards: + for pin in self.__keyboards[Keyboard].output_pins: + return_dict[Keyboard+'.'+str(pin)] = self.__keyboards[Keyboard].status_output(pin) + return return_dict + + @property + def loaded_keyboards(self): + return_dict = {} + for keyboard in self.__keyboards: + return_dict[keyboard] = self.__keyboards[keyboard].keyboard_typ + return return_dict + + def __init__(self, config_keyboards): + self.__OutputMappingTable = {} + self.__keyboards = {} + for keyboard_name in config_keyboards: + logger.info("trying to add keyboard '%s' to handler", keyboard_name) + self.__keyboards[keyboard_name] = load_single_keyboard(keyboard_name) + if self.__keyboards[keyboard_name] is None: + logger.error("couldn't load keyboard %s", keyboard_name) + del self.__keyboards[keyboard_name] + continue + + output_pins = doorpi.DoorPi().config.get_keys(keyboard_name+'_OutputPins') + for output_pin in output_pins: + output_pin_name = doorpi.DoorPi().config.get(keyboard_name+'_OutputPins', output_pin) + if output_pin_name in self.__OutputMappingTable: + logger.warning('overwriting existing name of outputpin "%s" (exists in %s and %s)', + output_pin_name, + self.__OutputMappingTable[output_pin_name], + keyboard_name + ) + self.__OutputMappingTable[output_pin_name] = keyboard_name + + if len(self.__keyboards) is 0: + logger.error('No Keyboards loaded - load dummy!') + self.__keyboards['dummy'] = load_single_keyboard('dummy') + + def destroy(self): + try: + for Keyboard in self.__keyboards: + self.__keyboards[Keyboard].destroy() + except: pass + + def set_output(self, pin, value, log_output = True): + if pin not in self.__OutputMappingTable: + raise UnknownOutputPin('outputpin with name %s is unknown %s' % (pin, self.__OutputMappingTable)) + self.__keyboards[self.__OutputMappingTable[pin]].set_output(pin, value, log_output) + + def status_input(self, pin): + for keyboard in self.__keyboards: + if pin.startswith(keyboard+'.'): + return self.__keyboards[keyboard].status_input(pin[len(keyboard+'.'):]) + return None + + def status_output(self, pin): + for keyboard in self.__keyboards: + if pin.startswith(keyboard+'.'): + return self.__keyboards[keyboard].status_output(pin[len(keyboard+'.'):]) + return None + __del__ = destroy \ No newline at end of file diff --git a/doorpi/keyboard/from_gpio.py b/doorpi/keyboard/from_gpio.py index 6df06521..2fe461b5 100644 --- a/doorpi/keyboard/from_gpio.py +++ b/doorpi/keyboard/from_gpio.py @@ -6,37 +6,68 @@ logger.debug("%s loaded", __name__) import RPi.GPIO as RPiGPIO # basic for GPIO control - from doorpi.keyboard.AbstractBaseClass import KeyboardAbstractBaseClass, HIGH_LEVEL, LOW_LEVEL import doorpi + def get(**kwargs): return GPIO(**kwargs) + + class GPIO(KeyboardAbstractBaseClass): name = 'GPIO Keyboard' - def __init__(self, input_pins, output_pins, keyboard_name, bouncetime = 200, polarity = 0, *args, **kwargs): + def __init__(self, input_pins, output_pins, conf_pre, conf_post, keyboard_name, + bouncetime=200, polarity=0, pressed_on_key_down=True, *args, **kwargs): logger.debug("__init__(input_pins = %s, output_pins = %s, bouncetime = %s, polarity = %s)", input_pins, output_pins, bouncetime, polarity) self.keyboard_name = keyboard_name self._polarity = polarity self._InputPins = map(int, input_pins) self._OutputPins = map(int, output_pins) + self._pressed_on_key_down = pressed_on_key_down RPiGPIO.setwarnings(False) - RPiGPIO.setmode(RPiGPIO.BOARD) - RPiGPIO.setup(self._InputPins, RPiGPIO.IN, pull_up_down = RPiGPIO.PUD_DOWN) + section_name = conf_pre+'keyboard'+conf_post + if doorpi.DoorPi().config.get(section_name, 'mode', "BOARD").upper() == "BOARD": + RPiGPIO.setmode(RPiGPIO.BOARD) + else: + RPiGPIO.setmode(RPiGPIO.BCM) + + # issue 134 + pull_up_down = doorpi.DoorPi().config.get(section_name, 'pull_up_down', "PUD_OFF").upper() + if pull_up_down == "PUD_DOWN": + pull_up_down = RPiGPIO.PUD_DOWN + elif pull_up_down == "PUD_UP": + pull_up_down = RPiGPIO.PUD_UP + else: + pull_up_down = RPiGPIO.PUD_OFF + + # issue #133 + try: + RPiGPIO.setup(self._InputPins, RPiGPIO.IN, pull_up_down=pull_up_down) + except TypeError: + logger.warning('you use an old version of GPIO library - fallback to single-register of input pins') + for input_pin in self._InputPins: + RPiGPIO.setup(input_pin, RPiGPIO.IN, pull_up_down=pull_up_down) + for input_pin in self._InputPins: RPiGPIO.add_event_detect( input_pin, - RPiGPIO.BOTH , - callback = self.event_detect, - bouncetime = int(bouncetime) + RPiGPIO.BOTH, + callback=self.event_detect, + bouncetime=int(bouncetime) ) self._register_EVENTS_for_pin(input_pin, __name__) - RPiGPIO.setup(self._OutputPins, RPiGPIO.OUT) - #RPiGPIO.output(self._OutputPins, RPiGPIO.LOW) + # issue #133 + try: + RPiGPIO.setup(self._OutputPins, RPiGPIO.OUT) + except TypeError: + logger.warning('you use an old version of GPIO library - fallback to single-register of input pins') + for output_pin in self._OutputPins: + RPiGPIO.setup(output_pin, RPiGPIO.OUT) + # use set_output to register status @ dict self._OutputStatus for output_pin in self._OutputPins: self.set_output(output_pin, 0, False) @@ -44,7 +75,8 @@ def __init__(self, input_pins, output_pins, keyboard_name, bouncetime = 200, pol self.register_destroy_action() def destroy(self): - if self.is_destroyed: return + if self.is_destroyed: + return logger.debug("destroy") # shutdown all output-pins @@ -57,9 +89,12 @@ def destroy(self): def event_detect(self, pin): if self.status_input(pin): self._fire_OnKeyDown(pin, __name__) - self._fire_OnKeyPressed(pin, __name__) + if self._pressed_on_key_down: # issue 134 + self._fire_OnKeyPressed(pin, __name__) else: self._fire_OnKeyUp(pin, __name__) + if not self._pressed_on_key_down: # issue 134 + self._fire_OnKeyPressed(pin, __name__) def status_input(self, pin): if self._polarity is 0: @@ -67,18 +102,21 @@ def status_input(self, pin): else: return str(RPiGPIO.input(int(pin))).lower() in LOW_LEVEL - def set_output(self, pin, value, log_output = True): + def set_output(self, pin, value, log_output=True): parsed_pin = doorpi.DoorPi().parse_string("!"+str(pin)+"!") if parsed_pin != "!"+str(pin)+"!": pin = parsed_pin pin = int(pin) value = str(value).lower() in HIGH_LEVEL - if self._polarity is 1: value = not value + if self._polarity is 1: + value = not value log_output = str(log_output).lower() in HIGH_LEVEL - if pin not in self._OutputPins: return False - if log_output: logger.debug("out(pin = %s, value = %s, log_output = %s)", pin, value, log_output) + if pin not in self._OutputPins: + return False + if log_output: + logger.debug("out(pin = %s, value = %s, log_output = %s)", pin, value, log_output) RPiGPIO.output(pin, value) self._OutputStatus[pin] = value diff --git a/doorpi/keyboard/from_piface.py b/doorpi/keyboard/from_piface.py index f4ec5548..590d3556 100644 --- a/doorpi/keyboard/from_piface.py +++ b/doorpi/keyboard/from_piface.py @@ -5,31 +5,34 @@ logger = logging.getLogger(__name__) logger.debug("%s loaded", __name__) -import pifacedigitalio as p # basic for PiFce control - +import pifacedigitalio as p # basic for PiFce control from doorpi.keyboard.AbstractBaseClass import KeyboardAbstractBaseClass, HIGH_LEVEL, LOW_LEVEL import doorpi + def get(**kwargs): return PiFace(**kwargs) + + class PiFace(KeyboardAbstractBaseClass): - def __init__(self, input_pins, output_pins, keyboard_name, bouncetime, polarity = 0, *args, **kwargs): + def __init__(self, input_pins, output_pins, keyboard_name, bouncetime, + polarity=0, pressed_on_key_down=True, *args, **kwargs): logger.debug("__init__(input_pins = %s, output_pins = %s, polarity = %s)", input_pins, output_pins, polarity) self.keyboard_name = keyboard_name self._polarity = polarity self._InputPins = map(int, input_pins) self._OutputPins = map(int, output_pins) + self._pressed_on_key_down = pressed_on_key_down p.init() - self.__listener = p.InputEventListener() for input_pin in self._InputPins: self.__listener.register( - pin_num = input_pin, - direction = p.IODIR_BOTH, - callback = self.event_detect, - settle_time = bouncetime / 1000 # from milliseconds to seconds + pin_num=input_pin, + direction=p.IODIR_BOTH, + callback=self.event_detect, + settle_time=bouncetime / 1000 # from milliseconds to seconds ) self._register_EVENTS_for_pin(input_pin, __name__) self.__listener.activate() @@ -41,7 +44,8 @@ def __init__(self, input_pins, output_pins, keyboard_name, bouncetime, polarity self.register_destroy_action() def destroy(self): - if self.is_destroyed: return + if self.is_destroyed: + return logger.debug("destroy") # shutdown listener @@ -57,9 +61,12 @@ def destroy(self): def event_detect(self, event): if self.status_input(event.pin_num): self._fire_OnKeyDown(event.pin_num, __name__) + if self._pressed_on_key_down: # issue 134 + self._fire_OnKeyPressed(event.pin_num, __name__) else: self._fire_OnKeyUp(event.pin_num, __name__) - self._fire_OnKeyPressed(event.pin_num, __name__) + if not self._pressed_on_key_down: # issue 134 + self._fire_OnKeyPressed(event.pin_num, __name__) def status_input(self, pin): if self._polarity is 0: diff --git a/doorpi/main.py b/doorpi/main.py index 4c578524..e39dd388 100755 --- a/doorpi/main.py +++ b/doorpi/main.py @@ -64,7 +64,7 @@ def parse_arguments(argv): dest='configfile' ) try: - if len(sys.argv) > 1 and sys.argv[1] in ['start', 'stop', 'restart', 'status']: # running as daemon? cut first argument + if len(sys.argv) > 1 and sys.argv[1] in ['start', 'stop', 'restart', 'status']: return arg_parser.parse_args(args=sys.argv[2:]) else: return arg_parser.parse_args(args=sys.argv[1:]) @@ -76,7 +76,7 @@ def parse_arguments(argv): def files_preserve_by_path(*paths): - wanted=[] + wanted = [] for path in paths: fd = os.open(path, os.O_RDONLY) try: @@ -91,7 +91,7 @@ def fd_wanted(fd): return False fd_max = getrlimit(RLIMIT_NOFILE)[1] - return [ fd for fd in xrange(fd_max) if fd_wanted(fd) ] + return [fd for fd in xrange(fd_max) if fd_wanted(fd)] def main_as_daemon(argv): @@ -130,7 +130,6 @@ def main_as_daemon(argv): daemon_runner.daemon_context.files_preserve = files_preserve_by_path(log_file) try: daemon_runner.do_action() - logger.info('loaded with arguments: %s', str(argv)) except DaemonRunnerStopFailureError as ex: print("can't stop DoorPi daemon - maybe it's not running? (Message: %s)" % ex) return 1 diff --git a/doorpi/metadata.py b/doorpi/metadata.py index 134a8d8a..bdd276ea 100755 --- a/doorpi/metadata.py +++ b/doorpi/metadata.py @@ -9,7 +9,7 @@ package = 'DoorPi' project = "VoIP Door-Intercomstation with Raspberry Pi" project_no_spaces = project.replace(' ', '') -version = '2.4.1.6' +version = '2.4.1.8' description = 'provide intercomstation to the doorstation by VoIP' keywords = ['intercom', 'VoIP', 'doorstation', 'home automation', 'IoT'] authors = ['Thomas Meissner'] @@ -33,6 +33,7 @@ copyright = "%s, 2014-2015" % authors[0] license = 'CC BY-NC 4.0' url = 'https://github.com/motom001/DoorPi' +url_raw = 'https://raw.githubusercontent.com/motom001/DoorPi' # created with: http://patorjk.com/software/taag/#p=display&f=Ogre&t=DoorPi epilog = ''' @@ -54,21 +55,21 @@ if os.name == 'posix': - dummy_file = 'doorpi/docs/dummy_file' doorpi_path = os.path.join('/usr/local/etc', package) + pidfile = '/var/run/%s.pid' % package.lower() - daemon_folder = '/etc/init.d' + daemon_name = package.lower() - daemon_name_template = 'doorpi/docs/service/doorpi.tpl' - daemon_name_template_parsed = 'doorpi/docs/service/doorpi' - daemon_args = '--configfile $DOORPI_PATH/conf/doorpi.ini --trace' + daemon_folder = '/etc/init.d' + daemon_file = os.path.join(daemon_folder, daemon_name) + + daemon_online_template = url_raw+'/development/'+'doorpi/docs/service/doorpi.tpl' + + daemon_args = '--configfile $DOORPI_PATH/conf/doorpi.ini' doorpi_executable = '/usr/local/bin/doorpi_cli' log_folder = '%s/log' % doorpi_path - try: - if not os.path.exists(doorpi_path): - os.makedirs(doorpi_path) - except OSError: - doorpi_path = os.path.join(os.path.expanduser('~'), package) + if not os.path.exists(doorpi_path): + os.makedirs(doorpi_path) else: raise Exception('os unknown') diff --git a/doorpi/sipphone/linphone_lib/CallBacks.py b/doorpi/sipphone/linphone_lib/CallBacks.py index ed607b39..e60784f2 100755 --- a/doorpi/sipphone/linphone_lib/CallBacks.py +++ b/doorpi/sipphone/linphone_lib/CallBacks.py @@ -213,7 +213,7 @@ def call_log_updated(self, core, new_call_log_entry): pass def message_received(self, core, linphone_chat_room, message): pass def is_composing_received(self, core, linphone_chat_room): pass def dtmf_received(self, core, call, digits): - logger.debug("on_dtmf_digit (%s)",str(digits)) + logger.debug("on_dtmf_digit (%s)", str(digits)) digits = chr(digits) DoorPi().event_handler('OnDTMF', __name__, {'digits':digits}) self.__DTMF += str(digits) diff --git a/requirements.txt b/requirements.txt index 040f509e..b11ed016 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,4 @@ requests >= 2.7.0 RPi.GPIO >= 0.5.11 pifacedigitalio >= 3.0.5 pyserial >= 2.7 -watchdog >= 0.8.3 -python-daemon -linphone4raspberry \ No newline at end of file +watchdog >= 0.8.3 \ No newline at end of file diff --git a/setup.py b/setup.py index d40d04c0..b26075d8 100755 --- a/setup.py +++ b/setup.py @@ -4,6 +4,7 @@ import os import uuid import sys +import urllib2 # Check for pip, setuptools and wheel try: @@ -27,13 +28,19 @@ base_path = os.path.dirname(os.path.abspath(__file__)) metadata = imp.load_source('metadata', os.path.join(base_path, 'doorpi', 'metadata.py')) + +def parse_string(raw_string): + for meta_key in dir(metadata): + if not meta_key.startswith('__'): + raw_string = raw_string.replace('!!%s!!' % meta_key, str(getattr(metadata, meta_key))) + return raw_string + + def read(filename, parse_file_content=False, new_filename=None): with open(os.path.join(base_path, filename)) as f: file_content = f.read() if parse_file_content: - for meta_key in dir(metadata): - if not meta_key.startswith('__'): - file_content = file_content.replace('!!%s!!' % meta_key, str(getattr(metadata, meta_key))) + file_content = parse_string(file_content) if new_filename: with open(os.path.join(base_path, new_filename), 'w') as f: f.write(file_content) @@ -41,14 +48,6 @@ def read(filename, parse_file_content=False, new_filename=None): return file_content -def return_parsed_filename(old_filename, new_filename, make_it_executeable=True): - new_filename = os.path.join(base_path, new_filename) - with open(new_filename, 'w') as f: - f.write(read(old_filename, True)) - if make_it_executeable: - os.chmod(new_filename, 0755) - return new_filename - from setuptools import setup, find_packages from pip.req import parse_requirements install_reqs = parse_requirements(os.path.join(base_path, 'requirements.txt'), session=uuid.uuid1()) @@ -106,24 +105,19 @@ def return_parsed_filename(old_filename, new_filename, make_it_executeable=True) entry_points={ 'console_scripts': [ 'doorpi_cli = doorpi.main:entry_point' - ], - # if you have a gui, use this - # 'gui_scripts': [ - # 'doorpi_gui = doorpi.gui:entry_point' - # ] + ] } - ) -if os.name == 'posix' and os.geteuid() == 0: - setup_dict.update(dict( - data_files=[( - metadata.daemon_folder, [ - return_parsed_filename(metadata.daemon_name_template, metadata.daemon_name_template_parsed) - ] - )] - )) + def main(): + if os.name == 'posix' and os.geteuid() == 0 and \ + not os.path.isfile(metadata.daemon_file) and not os.path.exists(metadata.daemon_file): + with open(metadata.daemon_file, "w") as daemon_file: + for line in urllib2.urlopen(metadata.daemon_online_template): + daemon_file.write(parse_string(line)) + os.chmod(metadata.daemon_file, 0755) + setup(**setup_dict) if __name__ == '__main__':