8 Quickstart
Fabio Manganiello edited this page 2021-03-28 16:55:24 +02:00

Quick start

In this first example, let's suppose that you want to control your smart lights (we'll use the Philips Hue plugin in this example, but you can easily use any other plugin) whenever you say "turn on the lights" to your embedded smart assistant (Platypush comes with support for Google Assistant through the Google SDK, all you need is a microphone plugged into your device).

  1. Start by creating an empty configuration file. It will be /etc/platypush/config.yaml if you want to share the configuration with all the user on the machine, or ~/.config/platypush/config.yaml if you want a user-specific configuration.

  2. Download the phue Python package (pip install phue), required by the Hue plugin

  3. Add the Hue configuration to your file:

light.hue:
    bridge: 192.168.1.100  # IP address or host name of your bridge
    groups:
        - Living Room      # Default groups to control if not specified in the request
  1. Also, enable the web server backend, we'll use it to test our plugin through curl:
backend.http:
    port: 8008

Note that flask is a required dependency to run the web server (pip install flask). websockets (pip install websockets) and redis (pip install redis) are optional dependency but I'd recommend to install them as well. Redis in particular is used by many components to communicate with each other, especially when they don't run in the same process, while websockets allows you to get live events on the web client.

  1. Now start Platypush:
$ platypush

If you want to use the web API you will first have to create a user through the web interface - just head to http://your-host:8008/ and create the user. You will then have to generate a token for your new user that can be used for API calls. You can either do it from the web panel (Settings -> Generate token) or command line:

curl -XPOST -H 'Content-Type: application/json' -d '
  {
    "username": "your-user",
    "password": "your-pass"
  }
' http://localhost:8008/auth

You can then store this token in an environment variable (e.g. $PP_TOKEN) and include it in your API calls over the Authorization: Bearer header.

Note: if you have authentication issues with the Hue bridge, press the physical connect button on the bridge and restart the application.

  1. Try to send commands to your lights:
curl -XPOST \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $PP_TOKEN" \
    -d '{"type":"request", "action":"light.hue.on"}' \
    http://localhost:8008/execute

Your lights should have turned on - congratulations!

Note that each request has:

  • The type=request field set
  • The action name. It will be the plugin package name followed by the name of the method in its main class, in this case light.hue.on
  • An optional key-valued list of method arguments named args

A Platypush response will always have a structure like this:

{
  "id": "response_id",
  "type": "response",
  "target": "target",
  "origin": "origin",
  "response": {
    "output": "The response output",
    "errors": ["The response errors"]
  }
}
  1. Another way to set an authentication token is by creating a global token in your config.yaml:
token: your_authentication_token

If configured, the calls to the service will require this bearer token to be provided either:

  • As a query string parameter (?token=your_authentication_token)
  • As an HTTP header (X-Token: your_authentication_token)
  • At the root of your JSON request (attribute name: token)
  • On the Authorization: Bearer HTTP header.

The web interface will also require basic HTTP authentication through this token.

  1. You can also point your browser to http://localhost:8008/, and you should now see the web interface where you can control your lights - change colors, state, and animate them. You'll only see one tab for now as you've only configured one plugin, but once you configure more plugins your web interface may look as something like this.

Note that if you configured a token you'll be promped with a basic HTTP authentication. The password will be your token, any username works for now.

  1. Check out the plugin docs to know more about the configuration variables or the supported methods for this plugin. The configuration values for any plugin are simply the parameters passed to its constructor. Same for the methods - just pass them on args:
curl -XPOST -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $PP_TOKEN" \
    -d '{"type":"request", "target":"your_device_id", "action":"light.hue.on", "args": {"groups":["Bedroom"]}}' \
    http://localhost:8008/execute
  1. Connect the action to your smart assistant now. Follow the steps on the Google website to get the assistant SDK installed and the credential file ready. Then configure your assistant backend:
backend.assistant.google:
    device_model_id: your_model_id   # From your Google project configuration
    credentials_file: /path/to/your/credentials.json
  1. Connect a microphone, restart the application and try to say "Ok Google" and try some basic interaction with the assistant. If everything went well, you should now be able to use the Google Assistant on your device.

  2. Configure an event hook to run the "lights on" action whenever you say "turn on the lights":

event.hook.LightsOnAssistantCommand:
    if:
        type: platypush.message.event.assistant.SpeechRecognizedEvent
        phrase: "turn on the lights"
    then:
        action: light.hue.on

Event hooks:

  • Start with the event.hook. string followed by a unique name
  • Have an if and then clause. In the if clause you specify the type of the event that will trigger the hook (event inheritance works) and, optionally, the arguments in the event that must match to fire the action. In this case, platypush.message.event.assistant.SpeechRecognizedEvent has a phrase argument that contains the phrase recognized by the assistant. If the confition matches, then the list of requests in the then clause will be triggered (you don't need to specify type, as they will always be requests, nor target, as it will be the local host).

You can also pass (partial) regular expressions as phrase. Something like "turn on (the)? lights?" would fire the hook even if the recognized phrase is "turn on lights" or "turn on the light". You can also pass named arguments, that will be used to match parts of the phrase and can be re-used in the action:

event.hook.ChangeSceneAssistantCommand:
    if:
        type: platypush.message.event.assistant.SpeechRecognizedEvent
        phrase: "set the scene on ${scene}"
    then:
        -
            action: light.hue.scene
            args:
                name: ${scene}

In the example above we store whatever is detected after "set the scene on" in a context variable named scene, and reuse it in the command. Note that we used args to specify the arguments for the invoked method.

You can also run multiple actions in the same hook:

event.hook.LightsOnAssistantCommand:
    if:
        type: platypush.message.event.assistant.SpeechRecognizedEvent
        phrase: "turn on the lights"
    then:
        -
            action: light.hue.on
        -
            action: shell.exec
            args:
                cmd: echo "Lights turned on" >> /your/log/file

Note how the example above uses another plugin - shell.exec to run a shell command. Since this plugin doesn't require any configuration, you don't have to explicitly configure it in order to use it.

Also, please note that the actions executed in an event hook will be executed in parallel. If you want to execute action synchronously, consider wrapping them into a synchronous procedure.

Congratulation, you've set up your first rule! Check out the plugins documentation, the backends documentation and the events documentation for the list of supported plugins, backends and events.

Procedures

As I mentioned above, event hooks allow you to run multiple actions in parallel. Sometimes however you want to run the commands in a series, especially if you want to reuse parts of the output from the previous commands. Let's suppose for instance that we want our assistant to tell us the weather in our city using the embedded weather forecast plugin, if we're not that happy with the accuracy of the Google weather forecast. That would require to run an action to get the forecast, and another action to say out the weather conditions.

Start by configuring the weather forecast plugin:

weather.forecast:
    lat: your_latitude
    long: your_longitude
    darksky_token: token  # Get it from https://darksky.net/dev/register
    units: si

Restart Platypush and test out the plugin with a curl call:

curl -XPOST -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $PP_TOKEN" \
    -d '{"type":"request", "target":"your_device_id", "action":"weather.forecast.get_current_weather"}' \
    http://localhost:8008/execute

The response will look like this:

{
  "id": 123,
  "type": "response",
  "target": "device_id",
  "origin": "device_id",
  "response": {
    "output": {
      "...":"...",
      "summary": "Clear",
      "...":"..."
    },
    "errors": []
  }
}

Now configure a synchronous procedure that gets the forecast and uses the text-to-speech plugin to tell you about the current conditions:

procedure.sync.SayWeatherForecast:
    -
        action: shell.exec
        args:
            cmd: echo "Phrase recognized: ${event['args']['phrase']}" >> /your/log/file
    -
        action: weather.forecast.get_current_weather
    -
        action: tts.say
        args:
            phrase: ${summary}

We declared the procedure as synchronous (use the async keyword instead of sync if you want to run the actions in parallel). Note how we used the summary from the get_current_weather action in tts.say. The variables returned by the previous actions become part of the context for the next action. In particular, what is contained in the output is expanded into local variables that you can reuse through the ${} notation.

You can also access the original event from the context. The context is just a key-value map that contains all the variables relevant to the executed procedure, and you can use the ${} notation to wrap any Python snippet to manipulate it and access it

Now configure an event hook to trigger the procedure through an assistant command:

event.hook.WeatherAssistantCommand:
    if:
        type: platypush.message.event.assistant.SpeechRecognizedEvent
        phrase: "how is the weather"
    then:
        action: procedure.SayWeatherForecast

You can also pass arguments to a procedure:

procedure.sync.LogEvent:
    -
        action: shell.exec
        args:
            cmd: echo "Event received on ${hostname} ${event}" >> /your/log/file

event.hook.OnEvent:
    if:
        type: platypush.message.event.Event
    then:
        action: procedure.LogEvent
        args:
            hostname: your_host

For loops

You can declare for loops in a procedure to iterate over values. Let's still use the weather forecast plugin to get an hourly forecast. That call will return a list of values under data in the response. We want to log those values to a file:

procedure.sync.LogHourlyForecast:
    -
        action: weather.forecast.get_hourly_forecast
    - for hour_data in ${data}:
        -
            action: shell.exec
            args:
                cmd: echo "${hour_data['time']}: ${hour_data['summary']}" >> /your/log/file

for will execute the nested actions synchronously - use the fork keyword instead if you want to run the nested action in parallel.

If conditions

You can also set up if conditions in your procedure, to only execute some actions if some conditions are met (you can use snippets of Python code for expressing the conditions):

procedure.sync.LogHourlyForecast:
    - action: weather.forecast.get_current_weather
    - if ${summary == "Clear"}:
        -
            action: tts.say
            args:
                phrase: The weather is good outside

The syntax also supports an else statement that will be executed if the condition is not matched:

procedure.sync.LogHourlyForecast:
    - action: weather.forecast.get_current_weather
    - if ${summary == "Clear"}:
        -
            action: tts.say
            args:
                phrase: The weather is clear outside
    - else:
        -
            action: tts.say
            args:
                phrase: The weather is not clear outside

And you can also build a more complex logic with nested if-else statements like in a real programming language.

You've now got all the basic ingredients to configure your own plugins and write you own rules. You can proceed looking at some examples with the available:

Backends

Backend configurations start with backend. in the config.yaml.

HTTP service configuration

Already covered in quick start.

PushBullet configuration

You will need:

  • Your PushBullet access token (create one here);
  • The name of the (virtual) PushBullet device used to listen for events (create one here).
backend.pushbullet:
    token: PUSHBULLET_TOKEN
    device: platypush

Apache Kafka configuration

This would be a sample snippet for an Apache Kafka configuration:

backend.kafka:
    server: server:9092  # Kafka server and port
    topic: platypush  # Topic prefix. Note: platypush will create a topic for each device named <topic>.<device_id>

Google Assistant configuration

Follow the steps on the Google Assistant SDK page and get the assistant sample running on your machine.

Afterwards, you can enable custom speech-triggered actions on Platypush by just enabling the assistant backend:

backend.assistant.google:
    disabled: False

Flic buttons configuration

Flic buttons are a quite cool and useful accessory. You can pair them with your phone over Bluetooth and they can trigger anything on your device - play music on Spotify, start a timer, trigger a Tasker task, snooze alarms, trigger fake phone calls...

A beta SDK is available as well that allows you to pair the buttons to any bluetooth device, not necessarily running the Flic app.

Install the SDK and run the flicd server on your machine. You can then enable the Flic plugin:

backend.button.flic:
    server: localhost

By the way, the Flic app only supports a maximum of three events per button - short press, long press and double press. With the Platypush plugin you can trigger countless actions by configuring multiple combinations of short and long presses - provided that you can remember them.

Mobile notification mirroring

If you enable the PushBullet backend and notification mirroring on your Android or iOS device, the PushBullet backend will be able to post events to Platypush if you receive notifications on your mobile, and you can react on those. You can for instance configure Platypush to log specific Whatsapp messages on a local db, read out the headlines of your favourite news app when you receive a push, connect custom Tasker events through AutoNotification, mirror the notifications on a display connected to a RaspberryPi, and so on.

Example configuration that logs all the notifications mirrored from your device on a MySQL database:

event.hook.OnMobileNotification:
    if:
        type: platypush.message.event.pushbullet.PushbulletEvent
        push_type: mirror
    then:
        -
            action: db.insert
            args:
                engine: mysql+mysqlconnector://user:password@localhost/notifications
                encoding: utf-8
                table: notifications
                records:
                    -
                        source_device_id: ${source_device_iden}
                        source_user_id: ${source_user_iden}
                        package_name: ${package_name}
                        application_name: ${application_name}
                        title: ${title}
                        body: ${body}
                        icon: ${icon}

Include directive

Your config.yaml file can easily become quite large if you have customized to automate all of your voice assistant, buttons, lights, database and media control events.

To keep the configuration neat and manageable, and/or keep some configuration parts shared among multiple nodes, you may want to use the built-in include directive to import some pieces of configuration from other files.

include:
    - include/backends.yaml
    - include/assistant_common.yaml

Now you can take a more in-depth to look to the available: