From 1955423d4eaa18e5f06ad60e7ebb42a363361d8e Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Mon, 9 Jul 2018 02:13:40 +0200 Subject: [PATCH] Included documentation on procedures --- Home.md | 2 +- Configuration.md => Quickstart.md | 255 ++++++++++++++---------------- 2 files changed, 117 insertions(+), 140 deletions(-) rename Configuration.md => Quickstart.md (78%) diff --git a/Home.md b/Home.md index 622c222..169d6e8 100644 --- a/Home.md +++ b/Home.md @@ -46,5 +46,5 @@ After configuring the server, start it by simply running `platypush`, `python -m **NOTE**: Platypush is only compatible with Python 3 and higher. -After installing the application, you're ready to [start configuring it](configuration) according to your needs. +After installing the application, you're ready to [start configuring it](quickstart) according to your needs. diff --git a/Configuration.md b/Quickstart.md similarity index 78% rename from Configuration.md rename to Quickstart.md index f894208..e8a66a2 100644 --- a/Configuration.md +++ b/Quickstart.md @@ -1,4 +1,5 @@ * [Quick start](#quick-start) +* [Procedures](#procedures) * [Backends](#backends) * [HTTP service configuration](#http-service-configuration) * [PushBullet](#pushbullet-configuration) @@ -15,12 +16,11 @@ * [Shell plugin](#shell-plugin) * [HTTP requests plugin](#http-requests-plugin) * [Database plugin](#database-plugin) -* [Procedures](#procedures) * [Event Hooks](#event-hooks) * [Mobile notification mirroring](#mobile-notification-mirroring) * [Include directive](#include-directive) -## Quick start +# Quick start In this first example, let's suppose that you want to control your smart lights (we'll use the [Philips Hue](https://www2.meethue.com/en-us) 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](https://platypush.readthedocs.io/en/latest/platypush/backend/assistant.google.html) through the [Google SDK](https://developers.google.com/assistant/sdk/), all you need is a microphone plugged into your device). @@ -144,21 +144,22 @@ 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). -If you wanted to pass extra arguments to the action: +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: ```yaml -event.hook.LightsOnAssistantCommand: +event.hook.ChangeSceneAssistantCommand: if: type: platypush.message.event.assistant.SpeechRecognizedEvent - phrase: "turn on the lights" + phrase: "set the scene on ${scene}" then: - action: light.hue.on - args: - groups: - - Bedroom - - Bathroom + - + 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: ```yaml @@ -177,10 +178,114 @@ event.hook.LightsOnAssistantCommand: 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** (we'll tackle them soon). +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](https://platypush.readthedocs.io/en/latest/plugins.html) and the [backends documentation](https://platypush.readthedocs.io/en/latest/backends.html) for the list of supported plugins and backends (_documentation for events coming soon_). +# 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: + +```yaml +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: + +```shell +curl -XPOST -H 'Content-Type: application/json' \ + -d '{"type":"request", "target":"your_device_id", "action":"weather.forecast.get_current_weather"}' \ + http://localhost:8008/execute +``` + +The response will look like this: + +```json +{ + "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: + +```yaml +procedure.sync.SayWeatherForecast: + - + action: shell.exec + args: + cmd: echo "Phrase recognized: ${context['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: + +```yaml +event.hook.WeatherAssistantCommand: + if: + type: platypush.message.event.assistant.SpeechRecognizedEvent + phrase: "how is the weather" + then: + action: procedure.SayWeatherForecast +``` + +## 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: + +```yaml +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): + +```yaml +procedure.sync.LogHourlyForecast: + - + action: weather.forecast.get_current_weather + - if ${summary == "Clear"}: + - + action: tts.say + args: + phrase: The weather is good outside +``` + # Backends Backend configurations start with `backend.` in the `config.yaml`. @@ -385,134 +490,6 @@ curl -XPOST -d 'msg={"type":"request","target":"hostname","action":"db.select", {"id": null, "type": "response", "target": null, "origin": null, "response": {"output": [{"id": 1, "temperature": "21.0","created_at":""}], "errors": []}} ``` -# Procedures - -Procedures are lists of actions that will be executed by Platypush. - -A procedure can be run: - -## Synchronous - -Configuration name starting with `procedure.sync.`. - -The actions will be executed sequentially. The return values from the previous actions can be used in the following actions as well. `output` and `errors` are special variables that will contain the output and the errors of the previous action, and the attributes of the previous responses (also JSON strings) can be accessible directly by name. Just include the context expression in `${...}`. You can also inspect the context in case of nested JSON values or transform the values through Python string functions - the expression will be eval'd by the interpreter. - -```yaml -procedure.sync.say_temperature: - - - action: http.request.get - args: - # Example JSON response: {"temperature":12.0, "location":{"city":"Amsterdam", "country":"nl"}} - url: https://your-weather-channel.com/api/temperature - headers: - X-Auth-Token: TOKEN - - - action: tts.say - args: - phrase: The temperature in ${location['city']} is ${temperature} -``` - -`for var in list` syntax loops are also supported if you want to iterate over some returned list and perform actions on each item individually. Two syntaxes are supported: `for` (synchronous execution of the commands, context from the previous responses can be used), and `fork` (asynchronous execution, the actions will be executed in parallel and are assumed to be independent): - -```yaml -procedure.sync.turn_all_lights_on: - - - action: http.request.get - args: - # Example JSON response: {"lights": [{"name":"Kitchen"}, {"name":"Living Room"}]} - url: https://your-bridge/api/lights - headers: - X-Auth-Token: TOKEN - - - fork light in ${lights}: - - - action: your.lights.plugin.on - args: - light: ${light['name']} -``` - -## Asynchronous - -Configuration name starting with `procedure.async.`. - -The actions will be executed in parallel, and the context from the response of the previous action cannot be used. Useful if you need to run a set of independent task and you want to optimize the execution time. - -```yaml -procedure.async.at_home: - - - action: tts.say - args: - phrase: Welcome home, YOUR_NAME - - - action: light.hue.on - - - action: switch.wemo.on - args: - device: Heater - - - action: music.mpd.play - args: - resource: spotify:user:you:playlist:your_favourite_playlist - - - action: shell.exec - args: - cmd: './bin/sensors_report.sh' -``` - -# Event Hooks - -Event hooks are one of the most powerful features of Platypush. They are the equivalent of a profile in Tasker, or a recipe in IFTTT. They link events received on the main bus to actions that can be triggered on the local or on a remote node. Events will usually be ignored unless you configured a hook for handling them. Their configuration names start with `event.hook.`, the condition expressed in the `if` section and the actions to execute in the `then` section. `then` can include either a single action or a list of actions. Note that the actions will be executed in parallel and therefore the context of the previously run action is not available - run a synchronous procedure in the `then` section if you want to execute multiple actions in sequence. - -Some examples: - -```yaml -event.hook.AssistantConversationStarted: - if: - type: platypush.message.event.assistant.ConversationStartEvent - then: - - - action: shell.exec - args: - cmd: 'aplay /usr/share/sounds/start.wav' - - -event.hook.LightsSceneAssistantCommand: - if: - type: platypush.message.event.assistant.SpeechRecognizedEvent - # Note: regex support (still quite experimental though) and support for - # parsing context variables out of the trigger event, that can be used - # in the executed actions. Context variables names are used as ${...} - # (escape it with \ if you want to use the literal symbol instead). - # The context variables already include the variables of the source event, - # meaning that you can use ${phrase} as well in your action for this example. - phrase: "set (the)? scene on ${name}" - then: - - - action: light.hue.scene - args: - # If you said "set the scene on sunset", name="sunset" - name: ${name} - - -event.hook.NextSongFlicButton: - if: - type: platypush.message.event.button.flic.FlicButtonEvent - btn_addr: 00:11:22:33:44:55 - sequence: - # Lists are supported too - - ShortPressEvent - - LongPressEvent - then: - - - action: music.mpd.next - # You can also specify multiple actions - - - action: media.ctrl.forward - # Or procedures - - - action: procedure.your_proc -``` - # Mobile notification mirroring If you enable the PushBullet backend and [notification mirroring](https://docs.pushbullet.com/#mirrored-notifications) 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](https://play.google.com/store/apps/details?id=com.joaomgcd.autonotification&hl=en), mirror the notifications on a display connected to a RaspberryPi, and so on.