From 39b0c3b7419d7e94351d3bbcbb6ae49ce2c32fa2 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sat, 3 Aug 2019 02:06:06 +0200 Subject: [PATCH] Added GPS backend --- docs/source/conf.py | 1 + platypush/backend/gps.py | 103 +++++++++++++++++++++++++++++++++ platypush/backend/midi.py | 1 - platypush/message/event/gps.py | 44 ++++++++++++++ requirements.txt | 2 + setup.py | 1 + 6 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 platypush/backend/gps.py create mode 100644 platypush/message/event/gps.py diff --git a/docs/source/conf.py b/docs/source/conf.py index fabe8f48b..2ff4db2c5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -221,6 +221,7 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers', 'pyHS100', 'grpc', 'envirophat', + 'gps', ] sys.path.insert(0, os.path.abspath('../..')) diff --git a/platypush/backend/gps.py b/platypush/backend/gps.py new file mode 100644 index 000000000..9620ad2c8 --- /dev/null +++ b/platypush/backend/gps.py @@ -0,0 +1,103 @@ +import gps +import threading +import time + +from platypush.backend import Backend +from platypush.message.event.gps import GPSVersionEvent, GPSDeviceEvent, GPSUpdateEvent + + +class GpsBackend(Backend): + """ + This backend can interact with a GPS device and listen for events. + + Triggers: + + * :class:`platypush.message.event.gps.GPSVersionEvent` when a GPS device advertises its version data + * :class:`platypush.message.event.gps.GPSDeviceEvent` when a GPS device is connected or updated + * :class:`platypush.message.event.gps.GPSUpdateEvent` when a GPS device has new data + + Requires: + + * **gps** (``pip install gps``) + * **gpsd** daemon running (``apt-get install gpsd`` or ``pacman -S gpsd`` depending on your distro) + + Once installed gpsd you need to run it and associate it to your device. Example if your GPS device communicates + over USB and is available on /dev/ttyUSB0:: + + [sudo] gpsd /dev/ttyUSB0 -F /var/run/gpsd.sock + + The best option is probably to run gpsd at startup as a systemd service. + """ + + _fail_sleep_time = 5.0 + + def __init__(self, gpsd_server='localhost', gpsd_port=2947, **kwargs): + """ + :param gpsd_server: gpsd daemon server name/address (default: localhost) + :type gpsd_server: str + :param gpsd_port: Port of the gpsd daemon (default: 2947) + :type gpsd_port: int or str + """ + super().__init__(**kwargs) + + self.gpsd_server = gpsd_server + self.gpsd_port = gpsd_port + self._session = None + self._session_lock = threading.RLock() + self._devices = {} + + def _get_session(self): + with self._session_lock: + if not self._session: + self._session = gps.gps(host=self.gpsd_server, port=self.gpsd_port, reconnect=True) + self._session.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE) + + return self._session + + def _gps_report_to_event(self, report): + if report.get('class').lower() == 'version': + return GPSVersionEvent(release=report.get('release'), + rev=report.get('rev'), + proto_major=report.get('proto_major'), + proto_minor=report.get('proto_minor')) + if report.get('class').lower() == 'devices': + for device in report.get('devices', []): + if device.get('path') not in self._devices or device != self._devices.get('path'): + self._devices[device.get('path')] = device + return GPSDeviceEvent(path=device.get('path'), activated=device.get('activated'), + native=device.get('native'), bps=device.get('bps'), + parity=device.get('parity'), stopbits=device.get('stopbits'), + cycle=device.get('cycle'), driver=device.get('driver')) + if report.get('class').lower() == 'device': + self._devices[report.get('path')] = report + return GPSDeviceEvent(path=report.get('path'), activated=report.get('activated'), + native=report.get('native'), bps=report.get('bps'), + parity=report.get('parity'), stopbits=report.get('stopbits'), + cycle=report.get('cycle'), driver=report.get('driver')) + if report.get('class').lower() == 'tpv': + return GPSUpdateEvent(device=report.get('device'), latitude=report.get('lat'), longitude=report.get('lon'), + altitude=report.get('alt'), mode=report.get('mode'), epv=report.get('epv'), + eph=report.get('eph'), sep=report.get('sep')) + + def run(self): + super().run() + self.logger.info('Initialized GPS backend on {}:{}'.format(self.gpsd_server, self.gpsd_port)) + + while not self.should_stop(): + try: + session = self._get_session() + report = session.next() + event = self._gps_report_to_event(report) + if event: + self.bus.post(event) + except Exception as e: + if isinstance(e, StopIteration): + self.logger.warning('GPS service connection lost, check that gpsd is running') + else: + self.logger.exception(e) + + self._session = None + time.sleep(self._fail_sleep_time) + + +# vim:sw=4:ts=4:et: diff --git a/platypush/backend/midi.py b/platypush/backend/midi.py index 791c401d2..6481e80a0 100644 --- a/platypush/backend/midi.py +++ b/platypush/backend/midi.py @@ -1,4 +1,3 @@ -import json import time from threading import Timer diff --git a/platypush/message/event/gps.py b/platypush/message/event/gps.py new file mode 100644 index 000000000..7b657a171 --- /dev/null +++ b/platypush/message/event/gps.py @@ -0,0 +1,44 @@ +from platypush.message.event import Event + + +class GPSEvent(Event): + """ + Generic class for GPS events + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class GPSVersionEvent(GPSEvent): + """ + Event usually triggered on startup or reconnection, when the GPS device advertises its version parameters + """ + + def __init__(self, release=None, rev=None, proto_major=None, proto_minor=None, *args, **kwargs): + super().__init__(release=release, rev=rev, proto_major=proto_major, proto_minor=proto_minor, *args, **kwargs) + + +class GPSDeviceEvent(GPSEvent): + """ + Event triggered when a new GPS device is connected or reconfigured + """ + + def __init__(self, path, activated=None, native=False, bps=None, parity=None, stopbits=None, + cycle=None, driver=None, *args, **kwargs): + super().__init__(*args, path=path, activated=activated, native=native, bps=bps, parity=parity, + stopbits=stopbits, cycle=cycle, driver=driver, **kwargs) + + +class GPSUpdateEvent(GPSEvent): + """ + Event triggered upon GPS status update + """ + + def __init__(self, device=None, latitude=None, longitude=None, altitude=None, mode=None, epv=None, eph=None, + sep=None, *args, **kwargs): + super().__init__(*args, device=device, latitude=latitude, longitude=longitude, altitude=altitude, + mode=mode, epv=epv, eph=eph, sep=sep, **kwargs) + + +# vim:sw=4:ts=4:et: diff --git a/requirements.txt b/requirements.txt index b3fd8cdd1..2c30716d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -150,3 +150,5 @@ pyScss # Support for enviropHAT # envirophat +# Support for GPS integration +# gps diff --git a/setup.py b/setup.py index 09146fd34..9a151da5b 100755 --- a/setup.py +++ b/setup.py @@ -172,6 +172,7 @@ setup( 'Support for compiling SASS/SCSS styles to CSS': ['pyScss'], 'Support for NFC tags': ['nfcpy>=1.0', 'ndef'], 'Support for enviropHAT': ['envirophat'], + 'Support for GPS': ['gps'], # 'Support for Leap Motion backend': ['git+ssh://git@github.com:BlackLight/leap-sdk-python3.git'], # 'Support for Flic buttons': ['git+https://@github.com/50ButtonsEach/fliclib-linux-hci.git'] # 'Support for media subtitles': ['git+https://github.com/agonzalezro/python-opensubtitles#egg=python-opensubtitles']