From e9e0512a521ffc163b5339b7584a43de64e53f75 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Mon, 11 Dec 2017 16:48:28 +0100 Subject: [PATCH] Implemented local backend --- README.md | 17 ++++++++-- runbullet/backend/__init__.py | 6 +++- runbullet/backend/local/__init__.py | 49 +++++++++++++++++++++++++++++ runbullet/bin/pusher | 22 +++++++++++-- runbullet/config.example.yaml | 3 ++ 5 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 runbullet/backend/local/__init__.py diff --git a/README.md b/README.md index 1928b0c7..6fba50cc 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,22 @@ Copy /etc/runbullet/config.example.yaml to /etc/runbullet/config.yaml (system-wi Edit the file to include: +### For the PushBullet backend + * Your PushBullet access token (create one [here](https://www.pushbullet.com/#settings/account)); * The name of the (virtual) PushBullet device used to listen for events (create one [here](https://www.pushbullet.com/#devices)). +### For the Apache Kafka backend + +* The host and port of the Kafka installation +* The topic that will be used to deliver and process messages + +### For the local socket backend + +* The name of the local FIFO that will be used to deliver and process messages + +### device_id + Each target device is identified by a unique device_id in the messages sent over your account. The device_id is the hostname by default, unless changed in config.yaml. Shell interface @@ -65,10 +78,10 @@ music.mpd: pusher --target raspberry --action switch.wemo.on ``` -* *TODO* `runbullet.plugins.light.hue`: Controls a Philips Hue smart lights system. Requires the package `phue` on the target machine. Example: +* `runbullet.plugins.light.hue`: Controls a Philips Hue smart lights system. Requires the package `phue` on the target machine. Example: ```shell -pusher --target raspberry --action light.hue.set_scene --scene "Sunset" --group "Living Room" +pusher --target raspberry --action light.hue.scene --name "Sunset" --group "Living Room" ``` Writing your plugins diff --git a/runbullet/backend/__init__.py b/runbullet/backend/__init__.py index bdb858ab..2ad00cea 100644 --- a/runbullet/backend/__init__.py +++ b/runbullet/backend/__init__.py @@ -39,12 +39,16 @@ class Backend(Thread): if cls is not object and hasattr(cls, '_init'): cls._init(self, **config) + def is_local(self): + from runbullet.backend.local import LocalBackend + return isinstance(self, LocalBackend) + def on_msg(self, msg): if 'target' not in msg: return # No target target = msg.pop('target') - if target != runbullet.get_device_id(): + if target != runbullet.get_device_id() and not self.is_local(): return # Not for me if 'action' not in msg: diff --git a/runbullet/backend/local/__init__.py b/runbullet/backend/local/__init__.py new file mode 100644 index 00000000..6d74cf9a --- /dev/null +++ b/runbullet/backend/local/__init__.py @@ -0,0 +1,49 @@ +import logging +import json +import os +import time + +from .. import Backend + +class LocalBackend(Backend): + def _init(self, fifo): + self.fifo = fifo + try: os.mkfifo(self.fifo) + except FileExistsError as e: pass + logging.info('Initialized local backend on fifo {}'.format(self.fifo)) + + def send_msg(self, msg): + if isinstance(msg, dict): + msg = json.dumps(msg) + if not isinstance(msg, str): + msg = json.dumps(msg) + raise RuntimeError('Invalid non-JSON message') + + msglen = len(msg)+1 # Include \n + msg = bytearray((str(msglen) + '\n' + msg + '\n').encode('utf-8')) + with open(self.fifo, 'wb') as f: + f.write(msg) + + def run(self): + with open(self.fifo, 'rb', 0) as f: + while True: + try: + msglen = int(f.readline()) + except ValueError as e: + time.sleep(0.1) + continue + + msg = f.read(msglen-1) + if not msg: continue + + try: + msg = json.loads(msg.decode('utf-8')) + except Exception as e: + logging.exception(e) + continue + + logging.debug('Received message: {}'.format(msg)) + self.on_msg(msg) + +# vim:sw=4:ts=4:et: + diff --git a/runbullet/bin/pusher b/runbullet/bin/pusher index 91bb64f8..d2d2458f 100755 --- a/runbullet/bin/pusher +++ b/runbullet/bin/pusher @@ -10,6 +10,7 @@ import yaml from pushbullet import Pushbullet from runbullet import parse_config_file from runbullet.backend.kafka import KafkaBackend +from runbullet.backend.local import LocalBackend def print_usage(): @@ -40,10 +41,18 @@ def send_pb_message(pb, device_name, msg): def send_kafka_message(backend, msg): backend.send_msg(msg) +def send_local_message(backend, msg): + backend.send_msg(msg) + def get_backend(config): # TODO Refactor this as something better and reuse the same # backend classes from the runbullet consumer module - if 'backend.pushbullet' in config \ + if 'backend.local' in config \ + and 'pusher' in config['backend.local'] \ + and config['backend.local']['pusher']: + c = config['backend.local'] + return LocalBackend({'fifo': c['fifo']}) + elif 'backend.pushbullet' in config \ and 'pusher' in config['backend.pushbullet'] \ and config['backend.pushbullet']['pusher']: API_KEY = config['backend.pushbullet']['token'] @@ -66,8 +75,8 @@ def main(): parser.add_argument('--backend', '-b', dest='backend', required=False, help="Backend to deliver the message " + - "[pushbullet|kafka] (default: whatever specified " + - "in your config with pusher=True)") + "[pushbullet|kafka|local] (default: whatever " + + "specified in your config with pusher=True)") opts, args = parser.parse_known_args(sys.argv[1:]) payload = {} @@ -76,6 +85,11 @@ def main(): raise RuntimeError('Odd number of key-value options passed: {}'. format(args)) + if opts.target == 'localhost' and 'backend.local' in config: + cfg = config['backend.local'] + cfg['pusher'] = True + config = { 'backend.local': cfg } + if opts.backend: backend_cfg_name = 'backend.' + opts.backend if backend_cfg_name not in config: @@ -103,6 +117,8 @@ def main(): send_pb_message(backend, config['backend.pushbullet']['device'], msg) elif isinstance(backend, KafkaBackend): send_kafka_message(backend, msg) + elif isinstance(backend, LocalBackend): + send_local_message(backend, msg) if __name__ == '__main__': diff --git a/runbullet/config.example.yaml b/runbullet/config.example.yaml index 66349df2..1cd0ad3c 100644 --- a/runbullet/config.example.yaml +++ b/runbullet/config.example.yaml @@ -11,6 +11,9 @@ backend.pushbullet: token: your_pushbullet_token_here device: your_pushbullet_virtual_device_name +backend.local: + fifo: /tmp/runbullet.fifo + # device_id: (default: current hostname) # debug: True (default: False)