From ffb7a3e5a38ca0f7e095122d56b1afff31314a99 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Mon, 22 Feb 2021 01:20:01 +0100 Subject: [PATCH] Extended and updated pieces of documentation on the HTTP server, Zigbee2mqtt and mpd. Also added example dashboard template and event hook script. --- examples/conf/config.yaml | 170 ++++++++++-------------- examples/conf/dashboard.xml | 33 +++++ examples/conf/hook.py | 43 ++++++ platypush/backend/http/__init__.py | 137 ++++++++++++++++--- platypush/plugins/music/mpd/__init__.py | 4 + platypush/plugins/zigbee/mqtt.py | 4 +- 6 files changed, 272 insertions(+), 119 deletions(-) create mode 100644 examples/conf/dashboard.xml create mode 100644 examples/conf/hook.py diff --git a/examples/conf/config.yaml b/examples/conf/config.yaml index bb70e562..263366dd 100644 --- a/examples/conf/config.yaml +++ b/examples/conf/config.yaml @@ -16,21 +16,21 @@ # Using multiple files is encouraged in the case of large configurations # that can easily end up in a messy config.yaml file, as they help you # keep your configuration more organized. -include: - - include/logging.yaml - - include/media.yaml - - include/sensors.yaml +#include: +# - include/logging.yaml +# - include/media.yaml +# - include/sensors.yaml # platypush logs on stdout by default. You can use the logging section to specify # an alternative file or change the logging level. -logging: - filename: ~/.local/log/platypush/platypush.log - level: INFO +#logging: +# filename: ~/.local/log/platypush/platypush.log +# level: INFO # The device_id is used by many components of platypush and it should uniquely # identify a device in your network. If nothing is specified then the hostname # will be used. -device_id: myname +#device_id: my_device ## -- ## Plugin configuration examples @@ -46,31 +46,31 @@ device_id: myname # https://docs.platypush.tech/en/latest/platypush/plugins/light.hue.html # for reference. You can easily install the required dependencies for the plugin through # pip install 'platypush[hue]' -light.hue: - # IP address or hostname of the Hue bridge - bridge: 192.168.1.10 - # Groups that will be handled by default if nothing is specified on the request - groups: - - Living Room +#light.hue: +# # IP address or hostname of the Hue bridge +# bridge: 192.168.1.10 +# # Groups that will be handled by default if nothing is specified on the request +# groups: +# - Living Room # Example configuration of music.mpd plugin, see # https://docs.platypush.tech/en/latest/platypush/plugins/music.mpd.html # You can easily install the dependencies through pip install 'platypush[mpd]' -music.mpd: - host: localhost - port: 6600 +#music.mpd: +# host: localhost +# port: 6600 # Example configuration of media.chromecast plugin, see # https://docs.platypush.tech/en/latest/platypush/plugins/media.chromecast.html # You can easily install the dependencies through pip install 'platypush[chromecast]' -media.chromecast: - chromecast: Living Room TV +#media.chromecast: +# chromecast: Living Room TV # Plugins with empty configuration can also be explicitly enabled by specifying # enabled=True or disabled=False (it's a good practice if you want the # corresponding web panel to be enabled, if available) -camera: - enabled: True +#camera: +# enabled: True # Support for last.fm scrobbling. Install dependencies with 'pip install "platypush[lastfm]" lastfm: @@ -81,13 +81,11 @@ lastfm: # Support for calendars - in this case Google and Facebook calendars # Installing the dependencies: pip install 'platypush[ical,google]' -calendar: - calendars: - - - type: platypush.plugins.google.calendar.GoogleCalendarPlugin - - - type: platypush.plugins.calendar.ical.CalendarIcalPlugin - url: https://www.facebook.com/events/ical/upcoming/?uid=your_user_id&key=your_key +#calendar: +# calendars: +# - type: platypush.plugins.google.calendar.GoogleCalendarPlugin +# - type: platypush.plugins.calendar.ical.CalendarIcalPlugin +# url: https://www.facebook.com/events/ical/upcoming/?uid=your_user_id&key=your_key ## -- ## Backends configuration examples @@ -118,77 +116,49 @@ calendar: backend.http: # Listening port port: 8008 + # Websocket port + websocket_port: 8009 # Through resource_dirs you can specify external folders whose content can be accessed on # the web server through a custom URL. In the case below we have a Dropbox folder containing # our pictures and we mount it to the '/carousel' endpoint. resource_dirs: - carousel: ~/Dropbox/Photos/carousel - - # Dashboard configuration. The dashboard is a collection of widgets and it's organized in - # multiple rows. Each rows can be split in 12 columns. Therefore 'columns: 12' will make - # a widget span over the whole row, while 'columns: 6' will make a widget take half the - # horizontal space of a column. - dashboard: - widgets: - - - widget: calendar - columns: 6 - - - widget: music - columns: 3 - - - widget: date-time-weather - columns: 3 - - - widget: image-carousel - columns: 6 - images_path: ~/Dropbox/Photos/carousel - refresh_seconds: 15 - - - widget: rss-news - # Requires backend.http.poll to be enabled with some - # RSS sources and write them to sqlite db - columns: 6 - limit: 25 - db: "sqlite:////home/user/.local/share/platypush/feeds/rss.db" + carousel: /mnt/hd/photos/carousel # The HTTP poll backend is a versatile backend that can monitor for HTTP-based resources and # trigger events whenever new entries are available. In the example below we show how to use # the backend to listen for changes on a set of RSS feeds. New content will be stored by default # on a SQLite database under ~/.local/share/platypush/feeds/rss.db. # Install the required dependencies through 'pip install "platypush[rss,db]"' -backend.http.poll: - requests: - - - # HTTP poll type (RSS) - type: platypush.backend.http.request.rss.RssUpdates - # Remote URL - url: http://www.theguardian.com/rss/world - # Custom title - title: The Guardian - World News - # How often we should check for changes - poll_seconds: 600 - # Maximum number of new entries to be processed - max_entries: 10 - - - type: platypush.backend.http.request.rss.RssUpdates - url: http://www.physorg.com/rss-feed - title: Phys.org - poll_seconds: 600 - max_entries: 10 - - - type: platypush.backend.http.request.rss.RssUpdates - url: http://feeds.feedburner.com/Techcrunch - title: Tech Crunch - poll_seconds: 600 - max_entries: 10 - - - type: platypush.backend.http.request.rss.RssUpdates - url: http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml - title: The New York Times - poll_seconds: 300 - max_entries: 10 +#backend.http.poll: +# requests: +# - type: platypush.backend.http.request.rss.RssUpdates # HTTP poll type (RSS) +# # Remote URL +# url: http://www.theguardian.com/rss/world +# # Custom title +# title: The Guardian - World News +# # How often we should check for changes +# poll_seconds: 600 +# # Maximum number of new entries to be processed +# max_entries: 10 +# +# - type: platypush.backend.http.request.rss.RssUpdates +# url: http://www.physorg.com/rss-feed +# title: Phys.org +# poll_seconds: 600 +# max_entries: 10 +# +# - type: platypush.backend.http.request.rss.RssUpdates +# url: http://feeds.feedburner.com/Techcrunch +# title: Tech Crunch +# poll_seconds: 600 +# max_entries: 10 +# +# - type: platypush.backend.http.request.rss.RssUpdates +# url: http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml +# title: The New York Times +# poll_seconds: 300 +# max_entries: 10 # MQTT backend. Installed required dependencies through 'pip install "platypush[mqtt]"' backend.mqtt: @@ -196,15 +166,15 @@ backend.mqtt: host: mqtt-server # By default the backend will listen for messages on the platypush_bus_mq/device_id # topic, but you can change the prefix using the topic attribute - topic: my_platypush_bus +# topic: MyBus # Raw TCP socket backend. It can run commands sent as JSON over telnet or netcat -backend.tcp: - port: 3333 +#backend.tcp: +# port: 3333 # Websocket backend. Install required dependencies through 'pip install "platypush[http]"' -backend.websocket: - port: 8765 +#backend.websocket: +# port: 8765 ## -- ## Assistant configuration examples @@ -254,9 +224,12 @@ backend.assistant.snowboy: assistant.echo: audio_player: mplayer -# Install Google Assistant dependencies with 'pip install "platypush[google-assistant]"' -assistant.google.pushtotalk: - language: en-US +# Install Google Assistant dependencies with 'pip install "platypush[google-assistant-legacy]"' +assistant.google: + enabled: True + +backend.assistant.google: + enabled: True ## -- ## Procedure examples @@ -327,14 +300,14 @@ procedure.outside_home: procedure.send_request(target, action, args): - action: mqtt.send_message args: - topic: my_platypush_bus/${target} + topic: platypush_bus_mq/${target} host: mqtt-server port: 1883 msg: type: request target: ${target} action: ${action} - args: "${context.get('args', {}}" + args: ${args} ## -- ## Event hook examples @@ -418,4 +391,3 @@ cron.TestCron: - action: shell.exec args: cmd: ~/bin/myscript.sh - diff --git a/examples/conf/dashboard.xml b/examples/conf/dashboard.xml new file mode 100644 index 00000000..71e5e8aa --- /dev/null +++ b/examples/conf/dashboard.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/conf/hook.py b/examples/conf/hook.py new file mode 100644 index 00000000..2ad765f4 --- /dev/null +++ b/examples/conf/hook.py @@ -0,0 +1,43 @@ +# A more versatile way to define event hooks than the YAML format of `config.yaml` is through native Python scripts. +# You can define hooks as simple Python functions that use the `platypush.event.hook.hook` decorator to specify on +# which event type they should be called, and optionally on which event attribute values. +# +# Event hooks should be stored in Python files under `~/.config/platypush/scripts`. All the functions that use the +# @hook decorator will automatically be discovered and imported as event hooks into the platform at runtime. + +# `run` is a utility function that runs a request by name (e.g. `light.hue.on`). +from platypush.utils import run + +# @hook decorator +from platypush.event.hook import hook + +# Event types that you want to react to +from platypush.message.event.assistant import ConversationStartEvent, SpeechRecognizedEvent + + +@hook(SpeechRecognizedEvent, phrase='play ${title} by ${artist}') +def on_music_play_command(event, title=None, artist=None, **context): + """ + This function will be executed when a SpeechRecognizedEvent with `phrase="play the music"` is triggered. + `event` contains the event object and `context` any key-value info from the running context. + Note that in this specific case we can leverage the token-extraction feature of SpeechRecognizedEvent through + ${} that operates on regex-like principles to extract any text that matches the pattern into context variables. + """ + results = run('music.mpd.search', filter={ + 'artist': artist, + 'title': title, + }) + + if results: + run('music.mpd.play', results[0]['file']) + else: + run('tts.say', "I can't find any music matching your query") + + +@hook(ConversationStartEvent) +def on_conversation_start(event, **context): + """ + A simple hook that gets invoked when a new conversation starts with a voice assistant and simply pauses the music + to make sure that your speech is properly detected. + """ + run('music.mpd.pause_if_playing') diff --git a/platypush/backend/http/__init__.py b/platypush/backend/http/__init__.py index 9ab87039..55d86bea 100644 --- a/platypush/backend/http/__init__.py +++ b/platypush/backend/http/__init__.py @@ -12,36 +12,137 @@ from platypush.utils import get_ssl_server_context, set_thread_name class HttpBackend(Backend): """ - The HTTP backend is a general-purpose web server that you can leverage: + The HTTP backend is a general-purpose web server. - * To execute Platypush commands via HTTP calls. Example:: + Example configuration: - curl -XPOST -H 'Content-Type: application/json' -H "X-Token: your_token" \\ - -d '{ - "type":"request", - "target":"nodename", - "action":"tts.say", - "args": {"phrase":"This is a test"} - }' \\ - http://localhost:8008/execute + .. code-block:: yaml + + backend.http: + # Default HTTP listen port + port: 8008 + # Default websocket port + websocket_port: 8009 + # External folders that will be exposed over `/resources/` + resource_dirs: + photos: /mnt/hd/photos + videos: /mnt/hd/videos + music: /mnt/hd/music + + You can leverage this backend: + + * To execute Platypush commands via HTTP calls. In order to do so: + + ** Register a user to Platypush through the web panel (usually served on ``http://host:8008/``). + + ** Generate a token for your user, either through the web panel (Settings -> Generate Token) or via API: + + .. code-block:: shell + + curl -XPOST -H 'Content-Type: application/json' -d ' + { + "username": "$YOUR_USER", + "password": "$YOUR_PASSWORD" + }' http://host:8008/auth + + ** Execute actions through the ``/execute`` endpoint: + + .. code-block:: shell + + curl -XPOST -H 'Content-Type: application/json' -H "Authorization: Bearer $YOUR_TOKEN" -d ' + { + "type": "request", + "action": "tts.say", + "args": { + "text": "This is a test" + } + }' http://localhost:8008/execute * To interact with your system (and control plugins and backends) through the Platypush web panel, - by default available on your web root document. Any plugin that you have configured and available as a panel - plugin will appear on the web panel as well as a tab. + by default available on ``http://host:8008/``. Any configured plugin that has an available panel + plugin will be automatically added to the web panel. - * To display a fullscreen dashboard with your configured widgets, by default available under ``/dashboard/`` + * To display a fullscreen dashboard with custom widgets. - * To stream media over HTTP through the ``/media`` endpoint + ** Widgets are available as Vue.js components under + ``platypush/backend/http/webapp/src/components/widgets``. + + ** Explore their options (some may require some plugins or backends to be configured in order to work) and + create a new dashboard template under ``~/.config/platypush/dashboards``- e.g. ``main.xml``: + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + ** The dashboard will be accessible under ``http://host:8008/dashboard/``, where ``name=main`` if for + example you stored your template under ``~/.config/platypush/dashboards/main.xml``. + + * To expose custom endpoints that can be called as web hooks by other applications and run some custom logic. + All you have to do in this case is to create a hook on a + :class:`platypush.message.event.http.hook.WebhookEvent` with the endpoint that you want to expose and store + it under e.g. ``~/.config/platypush/scripts/hooks.py``: + + .. code-block:: python + + from platypush.context import get_plugin + from platypush.event.hook import hook + from platypush.message.event.http.hook import WebhookEvent + + hook_token = 'abcdefabcdef' + + # Expose the hook under the /hook/lights_toggle endpoint + @hook(WebhookEvent, hook='lights_toggle') + def lights_toggle(event, **context): + # Do any checks on the request + assert event.headers.get('X-Token') == hook_token, 'Unauthorized' + + # Run some actions + lights = get_plugin('light.hue') + lights.toggle() Any plugin can register custom routes under ``platypush/backend/http/app/routes/plugins``. Any additional route is managed as a Flask blueprint template and the `.py` module can expose lists of routes to the main webapp through the ``__routes__`` object (a list of Flask blueprints). - Note that if you set up a main token, it will be required for any HTTP - interaction - either as ``X-Token`` HTTP header, on the query string - (attribute name: ``token``), as part of the JSON payload root (attribute - name: ``token``), or via HTTP basic auth (any username works). + Security: Access to the endpoints requires at least one user to be registered. Access to the endpoints is regulated + in the following ways (with the exception of event hooks, whose logic is up to the user): + + * **Simple authentication** - i.e. registered username and password. + * **JWT token** provided either over as ``Authorization: Bearer`` header or ``GET`` ``?token=`` + parameter. A JWT token can be generated either through the web panel or over the ``/auth`` endpoint. + * **Global platform token**, usually configured on the root of the ``config.yaml`` as ``token: ``. + It can provided either over on the ``X-Token`` header or as a ``GET`` ``?token=`` parameter. + * **Session token**, generated upon login, it can be used to authenticate requests through the ``Cookie`` header + (cookie name: ``session_token``). Requires: diff --git a/platypush/plugins/music/mpd/__init__.py b/platypush/plugins/music/mpd/__init__.py index b0743619..eef92932 100644 --- a/platypush/plugins/music/mpd/__init__.py +++ b/platypush/plugins/music/mpd/__init__.py @@ -16,9 +16,13 @@ class MusicMpdPlugin(MusicPlugin): sources through plugins (e.g. Spotify, TuneIn, Soundcloud, local files etc.). + **NOTE**: As of Mopidy 3.0 MPD is an optional interface provided by the ``mopidy-mpd`` extension. Make sure that you + have the extension installed and enabled on your instance to use this plugin with your server. + Requires: * **python-mpd2** (``pip install python-mpd2``) + """ _client_lock = threading.RLock() diff --git a/platypush/plugins/zigbee/mqtt.py b/platypush/plugins/zigbee/mqtt.py index ba05bf7d..f17a050f 100644 --- a/platypush/plugins/zigbee/mqtt.py +++ b/platypush/plugins/zigbee/mqtt.py @@ -33,8 +33,8 @@ class ZigbeeMqttPlugin(MqttPlugin, SwitchPlugin): .. code-block:: shell - wget https://github.com/Koenkk/Z-Stack-firmware/raw/master/coordinator/Z-Stack_Home_1.2/bin/default/CC2531_DEFAULT_20190608.zip - unzip CC2531_DEFAULT_20190608.zip + wget https://github.com/Koenkk/Z-Stack-firmware/raw/master/coordinator/Z-Stack_Home_1.2/bin/default/CC2531_DEFAULT_20201127.zip + unzip CC2531_DEFAULT_20201127.zip [sudo] cc-tool -e -w CC2531ZNP-Prod.hex - You can disconnect your debugger and downloader cable once the firmware is flashed.