Fabio Manganiello
f34f1f6232
Removed all the Python logic + templates and styles. Those have now been moved to a stand-alone project (madblog), therefore this repo should only contain the static blog pages and images.
631 lines
33 KiB
Markdown
631 lines
33 KiB
Markdown
[//]: # (title: Ultimate self-hosted automation with Platypush)
|
||
[//]: # (description: Get started with Platypush to automate your smart home and beyond)
|
||
[//]: # (image: /img/dashboard-1.png)
|
||
[//]: # (author: Fabio Manganiello <fabio@platypush.tech>)
|
||
[//]: # (published: 2019-07-28)
|
||
|
||
In the last few years we have experienced a terrific spike of products and solutions targeting home automation and
|
||
automation in general. After a couple of years of hype around IoT, assistants and home automation the market is slowly
|
||
cooling down, and a few solutions are emerging out of the primordial oh-look-at-this-shiny-new-toy chaos.
|
||
|
||
There are however a couple of issues I’ve still got with most of the available solutions that led me to invest more time
|
||
in building [Platypush](https://git.platypush.tech/platypush/platypush).
|
||
|
||
The rationale behind a self-hosted, open-source and modular automation service
|
||
------------------------------------------------------------------------------
|
||
|
||
First, one class of solutions for home automation is provided by large for-profit companies. Such solutions, like the
|
||
Google Home platform and Alexa, are intuitive to set up and use, but they’re quite rigid in the possibility of
|
||
customization — meaning that you’ve only got the integrations that Google or Amazon provide you with, based on the
|
||
partnerships they decide to build, you’ve only got to use the user interfaces they provide you with on the devices they
|
||
decide to support, you can’t install the platform wherever you like or interact with it in any way outside of what’s
|
||
provided by the company, and such decisions also subject to change over time. Additionally, a truly connected house will
|
||
generate a lot of data about you (much more than what you generate by browsing the web), and I feel a bit uncomfortable
|
||
sharing that data
|
||
with [people who don’t seem to bother to share it with anyone else without my consent](https://www.cnbc.com/2019/07/11/google-admits-leaked-private-voice-conversations.html).
|
||
Plus, the integrations provided by such platforms often break (see this, this and this, and they’re only a few examples)
|
||
. It can be quite annoying if all of a sudden you can no longer control your expensive lights or cameras from your
|
||
expensive assistant or smart screen, and the code for controlling them runs on somebody else’s cloud, and that somebody
|
||
else just informs you that “they’re working on that”.
|
||
|
||
Another issue with home automation solutions so far is fragmentation. You’ll easily have to buy 3–4 different bridges,
|
||
devices or adapters if you want to control your smart lights, smart buttons, switches or a living room media centre.
|
||
Compatibility with existing remotes is also most of the times neglected. Not to mention the number of apps you’ll have
|
||
to download — each provided by a different author, each eating up space on your phone, and don’t expect that many
|
||
options to make them communicate with each other. Such fragmentation has been indeed one of the core issues I’ve tried
|
||
to tackle when building platypush, as I aimed to have only one central entry point, one control panel, one dashboard,
|
||
one protocol to control everything, while still providing all the needed flexibility in terms of supported communication
|
||
backends and APIs. The need for multiple bridges for home automation also goes away once you provide modules to manage
|
||
Bluetooth, Zigbee and Z-Wave, and all you need to interact with your devices is a physical adapter connected to any kind
|
||
of computer. The core idea is that, if there is a Python library or API to do what you want to do (or at least
|
||
something that can be wrapped in a simple Python logic), then there should also be a plugin to effortlessly integrate
|
||
what you want to do into the ecosystem you already have. It’s similar to the solution that Google has later tried to
|
||
provide with the [device custom actions](https://developers.google.com/assistant/sdk/guides/library/python/extend/custom-actions), even though the latter is limited to assistant interactions so far, and it’s
|
||
currently subject to change because of the [deprecation of the assistant library](https://developers.google.com/assistant/sdk/guides/library/python/).
|
||
|
||
Another class of solutions for such problems come from open source products like [Home Assistant](https://www.home-assistant.io/).
|
||
While Platypush and the Home Assistant share quite a few things — they both started being developed around the same
|
||
time, both started out as a bunch of scripts that we developers used to control our own things, and we eventually ended
|
||
up gluing together in a larger platform for general use, both are open source and developed in Python, and both aim to
|
||
bring the logic for controlling your house inside your house instead of running it on somebody else’s cloud — there are a
|
||
couple of reasons why I eventually decided to keep working on my own platform instead of converging my efforts into Home
|
||
Assistant.
|
||
|
||
First, Home Assistant has a strong Raspberry Pi-first approach. The suggested way to get started is to flash the Hass.io
|
||
image to an SD card and boot up your RPi. While my solution is heavily tested within the Raspberry Pi ecosystem as well,
|
||
it aims to run on any device that comes with a CPU and a Python interpreter. You can easily install it and run it on any
|
||
x86/x86_64 architecture too if you want to handle your automation logic on an old laptop, a desktop or any Intel-based
|
||
micro-computer. You can easily run it on other single-board computers, such as the Asus Tinkerboard, any BananaPi or
|
||
Odroid device. You can even run it on Android if you have an implementation of the Python interpreter installed. You can
|
||
even run a stripped-down version on a microcontroller that runs MicroPython. While Home Assistant has tackled its growth
|
||
and increasing complexity by narrowing down the devices it supports, and providing pre-compiled OS and Docker images to
|
||
reduce the complex process of getting it to run on bare-metal, I've tried to keep Platypush as modular, lightweight and
|
||
easy to setup and run as possible. You can run it in a Python virtual environment, in a Docker container, in a virtual
|
||
machine or KVM — if you can name it, you can probably do it already. I have even managed to run it on an old Nokia N900,
|
||
both on the original Maemo and Arch Linux, and on several Android smartphones and tablets. And, most of all, it has a
|
||
very small memory and CPU footprint. Running hotword detection, assistant, web panel, camera, lights, music control and
|
||
a few sensors on a small Raspberry Zero is guaranteed to take not more than 5-10% of CPU load and just a few MBs of RAM.
|
||
|
||
The flexibility of Platypush comes however a slightly steeper learning curve, but it rewards the user with much more
|
||
room for customization. You are expected to install it via [pip](https://pypi.org/project/platypush/) or
|
||
the [Gitlab](https://git.platypush.tech/platypush/platypush) repo, install the dependencies based on the plugins you
|
||
want (although managing per-plugin dependencies is quite easy via `pip`), and manually create or edit a configuration
|
||
file. But it provides much, much more flexibility. It can listen for messages on MQTT, HTTP (but you don’t have to run
|
||
the webserver if you don’t want to), websocket, TCP socket, Redis, Kafka, Pushbullet — you name it, it has probably got
|
||
it already. It allows you to create powerful procedures and event hooks written either in an intuitive YAML-based language
|
||
or as drop-in Python scripts. Its original mission is to simplify home automation, but it doesn't stop there: you
|
||
can use it to send text messages, read notifications from Android devices, control robots, turn your old box into a fully
|
||
capable media center (with support for torrents, YouTube, Kodi, Plex, Chromecast, vlc, subtitles and many other players
|
||
and formats)
|
||
or a music center (with support for local collections, Spotify, SoundCloud, radios etc.), stream audio from your house,
|
||
play raw or sampled sounds from a MIDI interface, monitor access to the file system on a server, run custom actions when
|
||
some NFC tag is detected by your reader, read and extract content from RSS feeds and send it to your Kindle, read and
|
||
write data to a remote database, send metrics to Grafana, create a custom voice assistant, run and train machine
|
||
learning models, and so on — basically, you can do anything that comes
|
||
with [one of the hundreds of supported plugin](https://docs.platypush.tech/en/latest/).
|
||
|
||
Another issue I’ve tried to tackle is the developer and power user experience. I wanted to make it easy to use the
|
||
automation platform as a library or a general-purpose API, so you can easily invoke a custom logic to turn on the lights
|
||
or control the music in any of your custom scripts through something as simple as a get_plugin('light.hue').on() call,
|
||
or by sending a simple JSON request over whichever API, queue or socket communication you have set up. I also wanted to
|
||
make it easy to create complex custom actions (something like “when I get home, turn on the lights if it’s already dark,
|
||
turn on the fan if it’s hot, the thermostat if it’s cold, the dehumidifier if it’s too humid, and play the music you
|
||
were listening on your phone”) through native pre-configured action — similar to what is offered by Node-Red but with
|
||
more flexibility and ability to access the local context, and less agnostic when it comes to plugins, similar to what is
|
||
offered by IFTTT and Microsoft Flow but running in your own network instead of somebody else’s cloud, similar to the
|
||
flexibility offered by apps like Tasker and AutoApps, but not limited to your Android device. My goal was also to build
|
||
a platform that aims to be completely agnostic about how the messages are exchanged and which specific logic is
|
||
contained in the plugins. As long as you’ve got plugins and backends that implement a certain small set of elements,
|
||
then you can plug them in.
|
||
|
||
Another issue I’ve tried to tackle is the developer and power user experience. I wanted to make it easy to use the
|
||
automation platform as a library or a general-purpose API, so you can easily invoke a custom logic to turn on the lights
|
||
or control the music in any of your custom scripts through something as simple as a `get_plugin('light.hue').on()` call,
|
||
or by sending a simple JSON request over whichever API, queue or socket communication you have set up. I also wanted to
|
||
make it easy to create complex custom actions (something like _“when I get home, turn on the lights if it’s already dark,
|
||
turn on the fan if it’s hot, the thermostat if it’s cold, the dehumidifier if it’s too humid, and play the music I was
|
||
listening on my phone”_) through native pre-configured action — similar to what is offered by Node-Red but with
|
||
more flexibility and ability to access the local context, and without delegating too much of the integrations logic to
|
||
other blocks; similar to what is offered by IFTTT and Microsoft Flow, but running in your own network instead of somebody
|
||
else’s cloud; similar to the flexibility offered by apps like Tasker and AutoApps, but not limited to your Android
|
||
device.
|
||
|
||
Finally, extensibility was also a key factor I had in mind. I know how fundamental the contribution of other developers
|
||
is if you want your platform to support as many things as possible out there. One of my goals has been to provide
|
||
developers with the possibility of building a simple plugin in around 15 lines of Python code, and a UI integration in
|
||
around 40 lines of HTML+Javascript code thanks to a consistent API that takes care of all the boilerplate.
|
||
|
||
Let’s briefly analyze how platypush is designed to better grasp how it can provide more features and flexibility than
|
||
most of the platforms I’ve seen so far.
|
||
|
||
The building blocks
|
||
-------------------
|
||
|
||
There are a couple of connected elements at the foundations of platypush that allow users to build whichever solution
|
||
they like:
|
||
|
||
- **Plugins**: they are arguably the most important component of the platform. A plugin is a Python class that handles a
|
||
type of device or service (like lights, music, calendar etc.), and it exposes a set of methods that enable you to
|
||
programmatically invoke _actions_ over those devices and services (like turn on, play, get upcoming events etc.).
|
||
All plugins implement an abstract `Plugin` class and their configuration is completely transparent to the constructor
|
||
arguments of the plugin itself - i.e. you can look at the constructor itself in the source code to understand which
|
||
arguments a plugin takes, and you can fill those variables directly in your `config.yaml`. Example:
|
||
|
||
```yaml
|
||
light.hue:
|
||
bridge: 192.168.1.100
|
||
groups:
|
||
- Living Room
|
||
- Bathroom
|
||
```
|
||
|
||
- **Backends**: they are threads that run in the background and listen for something to happen (an HTTP request, a
|
||
websocket or message queue message, a voice assistant interaction, a new played song or movie…). When it happens, they
|
||
will trigger _events_, and other parts of the platform can asynchronously react to those events.
|
||
|
||
- **Messages**: a message in platypush is just a simple JSON string that comes with a type and a set of arguments. You
|
||
have three main types of messages on the platform:
|
||
|
||
- **Requests**: they are messages used to require a certain plugin action to be executed. The format of the action name
|
||
is quite simple (`plugin_name.method_name`), and you can, of course, pass extra arguments to the action. Actions are
|
||
mapped one-to-one to methods in the associated plugin class through the `@action` annotation. It means that a request
|
||
object is transparent to the organization of the plugin, and such a paradigm enables the user to build flexible
|
||
JSON-RPC-like APIs. For example, the `on` action of the `light.hue` plugin accepts lights and groups as optional
|
||
parameters. It means that you can easily build a request like this and deliver it to platypush through whichever
|
||
backend you prefer (note that `args` are optional in this case):
|
||
|
||
```json
|
||
{
|
||
"type":"request",
|
||
"action":"light.hue.on",
|
||
"args": {
|
||
"groups": [
|
||
"Living Room",
|
||
"Bathroom"
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
If you have the HTTP backend running, for example, you can easily dispatch such a request to it through the available
|
||
JSON-RPC execute endpoint.
|
||
|
||
First create a user through the web panel at `http://localhost:8008`, then generate a token for the user to authenticate
|
||
the API calls - you can easily generate a token from the web panel itself, Settings -> Generate token.
|
||
|
||
Store the token under an environment variable (e.g. `$PP_TOKEN`) and use it in your calls over the `Authorization: Bearer`
|
||
header:
|
||
|
||
```shell
|
||
# cURL example
|
||
curl -XPOST -H 'Content-Type: application/json' \
|
||
-H "Authorization: Bearer $PP_TOKEN" \
|
||
-d '{"type":"request", "action":"light.hue.on", "args": {"groups": ["Living Room", "Bedroom"]}}' \
|
||
http://localhost:8008/execute
|
||
|
||
# HTTPie example
|
||
echo '{
|
||
"type":"request",
|
||
"action":"light.hue.on",
|
||
"args": {
|
||
"groups": ["Living Room", "Bedroom"]
|
||
}
|
||
}' | http http://localhost:8008/execute "Authorization: Bearer $PP_TOKEN"
|
||
```
|
||
|
||
And you can also easily send requests programmatically through your own Python scripts, basically using Platypush as a
|
||
library in other scripts or projects:
|
||
|
||
```python
|
||
from platypush.context import get_plugin
|
||
|
||
response = get_plugin('light.hue').on(groups=['Living Room', 'Bathroom'])
|
||
print(response)
|
||
```
|
||
|
||
- **Responses**: they are messages that contain the `output` and `errors` resulting from the execution of a request. If
|
||
you send this request for example:
|
||
|
||
```json
|
||
{
|
||
"type":"request",
|
||
"action":"light.hue.get_lights"
|
||
}
|
||
```
|
||
|
||
You'll get back something like this:
|
||
|
||
```json
|
||
{
|
||
"id": "6e5383cee53e8330afc5dfb9bde12a25",
|
||
"type": "response",
|
||
"target": "http",
|
||
"origin": "your_server_name",
|
||
"_timestamp": 1564154465.715452,
|
||
"response": {
|
||
"output": {
|
||
"1": {
|
||
"state": {
|
||
"on": false,
|
||
"bri": 254,
|
||
"hue": 14916,
|
||
"sat": 142,
|
||
"effect": "none",
|
||
"xy": [
|
||
0.4584,
|
||
0.41
|
||
],
|
||
"ct": 366,
|
||
"alert": "lselect",
|
||
"colormode": "xy",
|
||
"mode": "homeautomation",
|
||
"reachable": true
|
||
},
|
||
"type": "Extended color light",
|
||
"name": "Living Room Ceiling Right",
|
||
"manufacturername": "Philips",
|
||
"productname": "Hue color lamp"
|
||
},
|
||
"errors": [
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
If you send a request over a synchronous backend (e.g. the HTTP or TCP backend) then you can expect the response to be
|
||
delivered back on the same channel. If you send it over an asynchronous backend (e.g. a message queue or a websocket)
|
||
then the response will be sent asynchronously, creating a dedicated queue or channel if required.
|
||
|
||
- **Events**: they are messages that can be triggered by backends (and also some plugins) when a certain condition is
|
||
verified. They can be delivered to connected web clients via websocket, or you can build your own custom logic on them
|
||
through pre-configured *event hooks*. Event hooks are similar to applets on IFTTT or profiles in Tasker — they execute a
|
||
certain action (or set of actions) when a certain event occurs. For example, if you enable the Google Assistant backend
|
||
and some speech is detected then a `SpeechRecognizedEvent` will be fired. You can create an event hook like this in your
|
||
configuration file to execute custom actions when a certain phrase is detected (note that regular expressions and
|
||
extractions of parts from the phrase, at least to some extent, are also supported):
|
||
|
||
```yaml
|
||
# Play a specific radio on the mpd (or mopidy) plugin
|
||
event.hook.PlayRadioParadiseAssistantCommand:
|
||
if:
|
||
type: platypush.message.event.assistant.SpeechRecognizedEvent
|
||
phrase: "play (the)? radio paradise"
|
||
then:
|
||
action: music.mpd.play
|
||
args:
|
||
resource: tunein:station:s13606
|
||
|
||
# Search and play a song by an artist. Note the use of ${} to identify
|
||
# parts of the target attribute that should be preprocessed by the hook
|
||
event.hook.SearchSongVoiceCommand:
|
||
if:
|
||
type: platypush.message.event.assistant.SpeechRecognizedEvent
|
||
phrase: "play ${title} by ${artist}"
|
||
then:
|
||
- action: music.mpd.clear # Clear the current playlist
|
||
- action: music.mpd.search
|
||
args:
|
||
artist: ${artist} # Note the special map variable "context" to access data from the current context
|
||
title: ${title}
|
||
|
||
# music.mpd.search will return a list of results under "output". context['output'] will
|
||
# therefore always contain the output of the last request. We can then get the first
|
||
# result and play it.
|
||
- action: music.mpd.play
|
||
args:
|
||
resource: ${context['output'][0]['file']}
|
||
```
|
||
|
||
You may have noticed that you can wrap Python expression by `${}` . You can also access context data through the special
|
||
context variable. As we saw in the example above, that allows you to easily access the output and errors of the latest
|
||
executed command (but also the event that triggered the hook, through `context.get('event')`). If the previous command
|
||
returned a key-value map, or if we extracted named-value pairs from one of the event arguments, then you can also omit
|
||
the context and access those directly by name — in the example above you can access the artist either through
|
||
`context.get('artist')` or simply `artist`, for example.
|
||
|
||
And you can also define event hooks directly in Python by just creating a script under `~/.config/platypush/scripts`:
|
||
|
||
```python
|
||
from platypush.context import get_plugin
|
||
from platypush.event.hook import hook
|
||
from platypush.message.event.assistant import SpeechRecognizedEvent
|
||
|
||
@hook(SpeechRecognizedEvent, phrase='play (the)? radio paradise')
|
||
def play_radio_hook(event, **context):
|
||
mpd = get_plugin('music.mpd')
|
||
mpd.play(resource='tunein:station:s13606')
|
||
```
|
||
|
||
- **Procedures**: the last fundamental element of platypush are procedures. They are groups of actions that can embed more
|
||
complex logic, like conditions and loops. They can also embed small snippets of Python logic to access the context
|
||
variables or evaluate expressions, as we have seen in the event hook example. For example, this procedure can execute
|
||
some custom code when you get home that queries a luminosity and a temperature sensor connected over USB interface (e.g.
|
||
Arduino), and turns on your Hue lights if it’s below a certain threshold, says a welcome message over the text-to-speech
|
||
plugin, switches on a fan connected over a TPLink smart plug if the temperature is above a certain threshold, and plays
|
||
your favourite Spotify playlist through mopidy:
|
||
|
||
```yaml
|
||
# Note: procedures can be synchronous (`procedure.sync` prefix) or asynchronous
|
||
# (`procedure.async` prefix). In a synchronous procedure the logic will wait for
|
||
# each action to be completed before proceeding with the next - useful if you
|
||
# want to link actions together, letting each action access the response of the
|
||
# previous one(s). An asynchronous procedure will execute instead all the actions
|
||
# in parallel. Useful if you want to execute a set of actions independent from
|
||
# each other, but be careful not to stack too many of them - each action will be
|
||
# executed in a new thread.
|
||
procedure.sync.at_home:
|
||
- action: serial.get_measurement
|
||
# Your device should return a JSON over the serial interface structured like:
|
||
# {"luminosity":45, "temperature":25}
|
||
|
||
# Note that you can either access the full output of the previous command through
|
||
# the `output` context variable, as we saw in the event hook example, or, if the
|
||
# output is a JSON-like object, you can access individual attributes of it
|
||
# directly through the context. It is indeed usually more handy to access individual
|
||
# attributes like this: the `output` context variable will be overwritten by the
|
||
# next response, while the individual attributes of a response will remain until
|
||
# another response overwrites them (they're similar to local variables)
|
||
- if ${luminosity < 30}:
|
||
- action: light.hue.on
|
||
|
||
- if ${temperature > 25}:
|
||
- action: switch.tplink.on
|
||
args:
|
||
device: Fan
|
||
|
||
- action: tts.google.say
|
||
args:
|
||
text: Welcome home
|
||
|
||
- action: music.mpd.play
|
||
args:
|
||
resource: spotify:user:1166720951:playlist:0WGSjpN497Ht2wYl0YTjvz
|
||
```
|
||
|
||
Again, you can also define procedures purely in Python by dropping a script under `~/.config/platypush/scripts` - just
|
||
make sure that those procedures are also imported in `~/.config/platypush/scripts/__init__.py` so they are visible to
|
||
the main application:
|
||
|
||
```python
|
||
# ~/.config/platypush/scripts/at_home.py
|
||
|
||
from platypush.procedure import procedure
|
||
from platypush.utils import run
|
||
|
||
@procedure
|
||
def at_home(**context):
|
||
sensors = run('serial.get_measurement')
|
||
|
||
if sensors['luminosity'] < 30:
|
||
run('light.hue.on')
|
||
|
||
if sensors['temperature'] > 25:
|
||
run('switch.tplink.on', device='Fan')
|
||
|
||
run('tts.google.say', text='Welcome home')
|
||
run('music.mpd.play', resource='spotify:user:1166720951:playlist:0WGSjpN497Ht2wYl0YTjvz')
|
||
|
||
# ~/.config/platypush/scripts/__init__.py
|
||
from scripts.at_home import at_home
|
||
```
|
||
|
||
In both cases, you can call the procedure either from an event hook or directly through API:
|
||
|
||
```shell
|
||
# cURL example
|
||
curl -XPOST -H 'Content-Type: application/json' \
|
||
-H "Authorization: Bearer $PP_TOKEN" \
|
||
-d '{"type":"request", "action":"procedure.at_home"}' \
|
||
http://localhost:8008/execute
|
||
```
|
||
|
||
You can also create a Tasker profile with a WiFi-connected or an AutoLocation trigger that fires when you enter your
|
||
home area, and that profile can send the JSON request to your platypush device over e.g. HTTP `/execute` endpoint or
|
||
MQTT — and you’ve got your custom welcome when you arrive home!
|
||
|
||
Now that you’ve got a basic idea of what’s possible with Platypush and which are its main components, it’s time to get
|
||
the hands dirty, getting it installed and configure your own plugins and rules.
|
||
|
||
Installation - Quickstart
|
||
-------------------------
|
||
|
||
First of all, Platypush relies on Redis as in-memory storage and internal message queue for delivering messages, so
|
||
that's the only important dependency to install:
|
||
|
||
```shell
|
||
# Installation on Debian and derived distros
|
||
[sudo] apt install redis-server
|
||
|
||
# Installation on Arch and derived distros
|
||
[sudo] pacman -S redis
|
||
|
||
# Enable and start the service
|
||
[sudo] systemctl enable redis.service
|
||
[sudo] systemctl start redis.service
|
||
```
|
||
|
||
You can then install Platypush through pip:
|
||
|
||
```shell
|
||
[sudo] pip install platypush
|
||
```
|
||
|
||
Or through the Gitlab repo:
|
||
|
||
```shell
|
||
git clone https://git.platypush.tech/platypush/platypush
|
||
cd platypush
|
||
python setup.py build
|
||
[sudo] python setup.py install
|
||
```
|
||
|
||
In both the cases, however, this will install only the dependencies for the core platform - that, by design, is kept
|
||
very small and relies on backends and plugins to actually do things.
|
||
|
||
The dependencies for each integration are reported in the documentation of that integration itself (
|
||
see [official documentation](https://docs.platypush.tech/en/latest/)), and there are mainly four ways to install
|
||
them:
|
||
|
||
- Through `pip` extras: this is probably the most immediate way, although (for now) it requires you to take a look at
|
||
the `extras_require` section of
|
||
the [`setup.py`](https://git.platypush.tech/platypush/platypush/-/blob/master/setup.py#L169) to see what's the name
|
||
of the extra required by your plugin/backend. For example, a common use case usually includes enabling the HTTP
|
||
backend (for the `/execute` endpoint and for the UI), and perhaps you may want to enable a plugin for managing your
|
||
lights (e.g. `light.hue`), your music server (e.g. `music.mpd`) and your Chromecasts (e.g. `media.chromecast`).
|
||
If that's the case, then you can just get the name of the extras required by these integrations from `setup.py` and
|
||
install them via pip:
|
||
|
||
```shell
|
||
# If you are installing Platypush directly from pip
|
||
[sudo] pip install 'platypush[http,hue,mpd,chromecast]'
|
||
|
||
# If you are installing Platypush from sources
|
||
cd /your/path/to/platypush
|
||
[sudo] pip install '.[http,hue,mpd,chromecast]'
|
||
```
|
||
|
||
- From `requirements.txt`. The file reports the core required dependencies as uncommented lines, and optional
|
||
dependencies as commented lines. Uncomment the dependencies you need for your integrations and then from the Platypush
|
||
source directory type:
|
||
|
||
```shell
|
||
cd /your/path/to/platypush
|
||
[sudo] pip install -r requirements.txt
|
||
```
|
||
|
||
- Manually. The official documentation reports the dependencies required by each integration and the commands to
|
||
install them, so an option would be to simply paste those commands. Another way to check the dependencies is by
|
||
inspecting the `__doc__` item of a plugin or backend through the Python interpreter itself:
|
||
|
||
```python
|
||
>>> from platypush.context import get_plugin
|
||
>>> plugin = get_plugin('light.hue')
|
||
>>> print(plugin.__doc__)
|
||
|
||
Philips Hue lights plugin.
|
||
|
||
Requires:
|
||
|
||
* **phue** (``pip install phue``)
|
||
```
|
||
|
||
- Through your OS package manager. This may actually be the best option if you want to install Platypush globally and
|
||
not in a virtual environment or in your user dir, as it helps keeping your system Python modules libraries clean
|
||
without too much pollution from `pip` modules and it would let your package manager take care of installing updates
|
||
when they are available or when you upgrade your version of Python. However, you may have to map the dependencies
|
||
provided by each integration to the corresponding package name on Debian/Ubuntu/Arch/CentOS etc.
|
||
|
||
Configuration
|
||
-------------
|
||
|
||
Once we have our dependencies installed, it’s time to configure the plugin. For example, if you want to manage your Hue
|
||
lights and your music server, create a `~/.config/platypush/config.yaml` configuration file (it's always advised to run
|
||
Platypush as a non-privileged user) with a configuration that looks like this:
|
||
|
||
```yaml
|
||
# Enable the web service and UI
|
||
backend.http:
|
||
enabled: True
|
||
|
||
light.hue:
|
||
bridge: 192.168.1.10 # IP address of your Hue bridge
|
||
groups: # Default groups that you want to control
|
||
- Living Room
|
||
|
||
# Enable also the light.hue backend to get events when the status
|
||
# of the lights changes
|
||
backend.light.hue:
|
||
poll_seconds: 20 # Check every 20 seconds
|
||
|
||
music.mpd:
|
||
host: localhost
|
||
|
||
# Enable either backend.music.mpd, or backend.music.mopidy if you
|
||
# use Mopidy instead of MPD, to receive events when the playback state
|
||
# or the played track change
|
||
backend.music.mpd:
|
||
poll_seconds: 10 # Check every 10 seconds
|
||
```
|
||
|
||
A few notes:
|
||
|
||
- The list of events triggered by each backend is also available in the documentation of those backends, and you can write
|
||
your own hooks (either in YAML inside of `config.yaml` or as Python drop-in scripts) to capture them and execute custom
|
||
logic - for instance, [`backend.light.hue`](https://docs.platypush.tech/en/latest/platypush/backend/light.hue.html) can
|
||
trigger [`platypush.message.event.light.LightStatusChangeEvent`](https://docs.platypush.tech/en/latest/platypush/events/light.html#platypush.message.event.light.LightStatusChangeEvent).
|
||
|
||
- By convention, plugins are identified by the lowercase name of their class without the `Plugin` suffix (e.g.
|
||
`light.hue`) while backends are identified by the lowercase name of their class without the `Backend` suffix.
|
||
|
||
- The configuration of a plugin or backend expects exactly the parameters of the constructor of its class, so it's very
|
||
easy to look either at the source code or the documentation and get the parameters required by the configuration and
|
||
their default values.
|
||
|
||
- By default, the HTTP backend will run the web service directly through a Flask wrapper. If you are planning to run
|
||
the service on a machine with more traffic or in production mode, then it's advised to use a uwsgi+nginx wrapper -
|
||
the [official documentation](https://docs.platypush.tech/en/latest/platypush/backend/http.html) explains how to
|
||
do that.
|
||
|
||
- If the plugin or the backend doesn't require parameters, or if you want to keep the default values for the parameters,
|
||
then its configuration can simply contain `enabled: True`.
|
||
|
||
You can now add your hooks and procedures either directly inside the `config.yaml` (if they are in YAML format) or in
|
||
`~/.config/platypush/scripts` (if they are in Python format). Also, the `config.yaml` can easily get messy, especially
|
||
if you add many integrations, hooks and procedures, so you can split it on multiple files and use the `include`
|
||
directive to include those external files (if relative paths are used then their reference base directory will be
|
||
`~/.config/platypush`):
|
||
|
||
```yaml
|
||
include:
|
||
- integrations/lights.yaml
|
||
- integrations/music.yaml
|
||
- integrations/media.yaml
|
||
- hooks/home.yaml
|
||
- hooks/media.yaml
|
||
# - ...
|
||
```
|
||
|
||
Finally, launch the service from the command line:
|
||
|
||
```shell
|
||
$ platypush # If the executable is installed in your PATH
|
||
$ python -m platypush # If you want to run it from the sources folder
|
||
```
|
||
|
||
You can also create a systemd service for it and have it to automatically start. Copy something like this
|
||
to `~/.config/systemd/user/platypush.service`:
|
||
|
||
```
|
||
[Unit]
|
||
Description=Universal command executor and automation platform
|
||
After=network.target redis.service
|
||
|
||
[Service]
|
||
ExecStart=/usr/bin/platypush
|
||
Restart=always
|
||
RestartSec=5
|
||
|
||
[Install]
|
||
WantedBy=default.target
|
||
```
|
||
|
||
Then:
|
||
|
||
```shell
|
||
systemctl --user start platypush # Will start the service
|
||
systemctl --user enable platypush # Will spawn it at startup
|
||
```
|
||
|
||
If everything went smooth and you have e.g. enabled the Hue plugin, you will see a message like this on your
|
||
logs/stdout:
|
||
|
||
```
|
||
Bridge registration error: The link button has not been pressed in the last 30 seconds.
|
||
```
|
||
|
||
As you may have guessed, you’ll need to press the link button on your Hue bridge to complete the pairing. After that,
|
||
you’re ready to go, and you should see traces with information about your bridge popping up in your log file.
|
||
|
||
If you enabled the HTTP backend then you may want to point your browser to `http://localhost:8008` to create a new user.
|
||
Then you can test the HTTP backend by sending e.g. a `get_lights` command:
|
||
|
||
```shell
|
||
curl -XPOST \
|
||
-H 'Content-Type: application/json' \
|
||
-H "Authorization: Bearer $PP_TOKEN" \
|
||
-d '{"type":"request", "action":"light.hue.get_lights"}' \
|
||
http://localhost:8008/execute
|
||
```
|
||
|
||
You may also notice that a panel is accessible on `http://localhost:8008` upon login that contains the UI for each
|
||
integration that provides it. Example for the `light.hue` plugin:
|
||
|
||
![Example image of a Platypush integration UI](../img/light-hue-ui-1.png)
|
||
|
||
Now you’ve got all the basic notions to start playing with Platypush on your own! Remember that all you need to know to
|
||
initialize a specific plugin, interact with it and which dependencies it requires is in the official documentation. Stay
|
||
tuned for the next articles that will show you how to do more with platypush — configuring your voice assistant, control
|
||
your media, manage your music collection, read data from sensors or NFC tags, control motors, enable multi-room music
|
||
control, implement a security system for your house with real-time video and audio stream, control your switches and
|
||
smart devices, turn your old TV remote into a universal remote to control anything attached to platypush, and much more.
|