Migrated first blog article
This commit is contained in:
parent
070c1cdd85
commit
a5e52e4e18
3 changed files with 623 additions and 0 deletions
|
@ -0,0 +1,3 @@
|
||||||
|
# Blog articles
|
||||||
|
|
||||||
|
- [Introduction to Platypush](pages/Self-hosted-ultimate-automation-with-Platypush.md)
|
BIN
img/light-hue-ui-1.png
Normal file
BIN
img/light-hue-ui-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
620
pages/Self-hosted-ultimate-automation-with-Platypush.md
Normal file
620
pages/Self-hosted-ultimate-automation-with-Platypush.md
Normal file
|
@ -0,0 +1,620 @@
|
||||||
|
Ultimate self-hosted automation with Platypush
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
In the last 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://platypush.readthedocs.io/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 (after logging at least once at the control panel at `http://localhost:8008` and creating a
|
||||||
|
user):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# cURL example
|
||||||
|
curl -XPOST -H 'Content-Type: application/json' -u 'username:password' \
|
||||||
|
-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 -a 'username:password' http://localhost:8008/execute
|
||||||
|
```
|
||||||
|
|
||||||
|
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' -u 'username:password' \
|
||||||
|
-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://platypush.readthedocs.io/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://platypush.readthedocs.io/en/latest/platypush/backend/light.hue.html) can
|
||||||
|
trigger [`platypush.message.event.light.LightStatusChangeEvent`](https://platypush.readthedocs.io/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://platypush.readthedocs.io/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 -u 'username:password' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-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.
|
Loading…
Reference in a new issue