Plugins¶
Pulls¶
Pulls can be divided into pulls that react on events and pulls that regularly poll for data.
So called Polling
components are special pulls
that - as stated earlier - regularly poll data or just execute
in regular intervals.
Besides the arguments stated in the component description polls
always have the following
arguments to control their polling behavior.
name | type | opt. | default | description |
---|---|---|---|---|
interval | str/float | yes | 60s | You may specify duration literals such as 60 (60 secs), 1m , 1h (…) to realize a periodic polling or cron expressions e.g. */1 * * * * (every minute) to realize cron like behavior. |
instant_run | bool | yes | False | If set to True the component will run as soon as pnp starts; otherwise it will run the next configured interval. |
fitbit.Current¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.fitbit.Current | poll | fitbit | 0.13.0 |
Description
Requests various current metrics (steps, calories, distance, …) from the fitbit api for a specific account.
Please see Fitbit Authentication to configure to prepare your account accordingly.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
config | str | no | n/a | The configuration file that keeps your initial and refreshed authentication tokens (see Fitbit Authentication for detailed information) |
resources | List[str] | no | n/a | The resources to request (see below for possible options) |
system | str | yes | None | The metric system to use based on your localisation (de_DE, en_US, …). Default is your configured metric system in your fitbit account |
Note
You can query the following resources:
- activities/calories
- activities/caloriesBMR
- activities/steps
- activities/distance
- activities/floors
- activities/elevation
- activities/minutesSedentary
- activities/minutesLightlyActive
- activities/minutesFairlyActive
- activities/minutesVeryActive
- activities/activityCalories
- body/bmi
- body/fat
- body/weight
- foods/log/caloriesIn
- foods/log/water
- sleep/awakeningsCount
- sleep/efficiency
- sleep/minutesAfterWakeup
- sleep/minutesAsleep
- sleep/minutesAwake
- sleep/minutesToFallAsleep
- sleep/startTime
- sleep/timeInBed
Result
Emits a map that contains the requested resources and their associated values:
{
"activities/calories": 1216,
"activities/caloriesBMR": 781,
"activities/steps": 4048,
"activities/distance": 3.02385,
"activities/floors": 4,
"activities/elevation": 12,
"activities/minutes_sedentary": 127,
"activities/minutes_lightly_active": 61,
"activities/minutes_fairly_active": 8,
"activities/minutes_very_active": 24,
"activities/activity_calories": 484,
"body/bmi": 23.086421966552734,
"body/fat": 0.0,
"body/weight": 74.8,
"foods/log/calories_in": 0,
"foods/log/water": 0.0,
"sleep/awakenings_count": 0,
"sleep/efficiency": 84,
"sleep/minutes_after_wakeup": 0,
"sleep/minutes_asleep": 369,
"sleep/minutes_awake": 69,
"sleep/minutes_to_fall_asleep": 0,
"sleep/start_time": "21:50",
"sleep/time_in_bed": 438
}
Example
# Please point your environment variable `FITBIT_AUTH` to your authentication
# configuration
- name: fitbit_current
pull:
plugin: pnp.plugins.pull.fitbit.Current
args:
config: !env FITBIT_AUTH
instant_run: true
interval: 5m
resources:
- 'activities/calories'
- 'activities/caloriesBMR'
- 'activities/steps'
- 'activities/distance'
- 'activities/floors'
- 'activities/elevation'
- 'activities/minutesSedentary'
- 'activities/minutesLightlyActive'
- 'activities/minutesFairlyActive'
- 'activities/minutesVeryActive'
- 'activities/activityCalories'
- 'body/bmi'
- 'body/fat'
- 'body/weight'
- 'foods/log/caloriesIn'
- 'foods/log/water'
- 'sleep/awakeningsCount'
- 'sleep/efficiency'
- 'sleep/minutesAfterWakeup'
- 'sleep/minutesAsleep'
- 'sleep/minutesAwake'
- 'sleep/minutesToFallAsleep'
- 'sleep/startTime'
- 'sleep/timeInBed'
push:
- plugin: pnp.plugins.push.simple.Echo
fitbit.Devices¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.fitbit.Devices | poll | fitbit | 0.13.0 |
Description
Requests details about your fitbit devices / trackers (battery, model, …) associated to your account.
Please see Fitbit Authentication to configure to prepare your account accordingly.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
config | str | no | n/a | The configuration file that keeps your initial and refreshed authentication tokens (see Fitbit Authentication for detailed information) |
system | str | yes | None | The metric system to use based on your localisation (de_DE, en_US, …). Default is your configured metric system in your fitbit account |
Result
Emits a list that contains your available trackers and/or devices and their associated details:
[{
"battery": "Empty",
"battery_level": 10,
"device_version": "Charge 2",
"features": [],
"id": "abc",
"last_sync_time": "2018-12-23T10:47:40.000",
"mac": "AAAAAAAAAAAA",
"type": "TRACKER"
}, {
"battery": "High",
"battery_level": 95,
"device_version": "Blaze",
"features": [],
"id": "xyz",
"last_sync_time": "2019-01-02T10:48:39.000",
"mac": "FFFFFFFFFFFF",
"type": "TRACKER"
}]
Example
# Please point your environment variable `FITBIT_AUTH` to your authentication
# configuration
tasks:
- name: fitbit_devices
pull:
plugin: pnp.plugins.pull.fitbit.Devices
args:
config: !env FITBIT_AUTH
instant_run: true
interval: 15m
push:
- plugin: pnp.plugins.push.simple.Echo
fitbit.Goal¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.fitbit.Goal | poll | fitbit | 0.13.0 |
Description
Requests your goals (water, steps, …) from the fitbit api.
Please see Fitbit Authentication to configure to prepare your account accordingly.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
config | str | no | n/a | The configuration file that keeps your initial and refreshed authentication tokens (see Fitbit Authentication for detailed information) |
resources | List[str] | no | n/a | The resources to request (see below for possible options) |
system | str | yes | None | The goals to request (see below for detailed information) |
Note
You can query the following resources:
- body/fat
- body/weight
- activities/daily/activeMinutes
- activities/daily/caloriesOut
- activities/daily/distance
- activities/daily/floors
- activities/daily/steps
- activities/weekly/distance
- activities/weekly/floors
- activities/weekly/steps
- foods/calories
- foods/water
Result
Emits a map structure that consists of the requested goals:
{
"body/fat": 15.0,
"body/weight": 70.0,
"activities/daily/active_minutes": 30,
"activities/daily/calories_out": 2100,
"activities/daily/distance": 5.0,
"activities/daily/floors": 10,
"activities/daily/steps": 6000,
"activities/weekly/distance": 5.0,
"activities/weekly/floors": 10.0,
"activities/weekly/steps": 6000.0,
"foods/calories": 2220,
"foods/water": 1893
}
Example
# Please point your environment variable `FITBIT_AUTH` to your authentication
# configuration
tasks:
- name: fitbit_goal
pull:
plugin: pnp.plugins.pull.fitbit.Goal
args:
config: !env FITBIT_AUTH
instant_run: true
interval: 5m
goals:
- body/fat
- body/weight
- activities/daily/activeMinutes
- activities/daily/caloriesOut
- activities/daily/distance
- activities/daily/floors
- activities/daily/steps
- activities/weekly/distance
- activities/weekly/floors
- activities/weekly/steps
- foods/calories
- foods/water
push:
- plugin: pnp.plugins.push.simple.Echo
fs.FileSystemWatcher¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.fs.FileSystemWatcher | pull | fswatcher | < 0.10.0 |
Description
Watches the given directory for changes like created, moved, modified and deleted files.
Per default will recursively report any file that is touched, changed or deleted in the given path. The
directory itself or subdirectories will be object to reporting too, if ignore_directories
is set to False
.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
path | str | no | n/a | The path to track for file / directory changes |
recursive | bool | yes | True | If set to True, any subfolders of the given path will be tracked too. |
patterns | List[str] | yes | None | Any file pattern (e.g. *.txt or *.txt, *.md. If set to None no filter is applied. |
ignore_patterns | List[str] | yes | None | Any patterns to ignore (specify like argument patterns ). If set to None, nothing will be ignored. |
ignore_directories | bool | yes | False | If set to True will send events for directories when file change. |
case_sensitive | bool | yes | False | If set to True, any pattern is case_sensitive, otherwise it is case insensitive. |
events | List[str] | yes | None | The events to track. One or multiple of ‘moved’, ‘deleted’, ‘created’ and/or ‘modified’. If set to None all events will be reported. |
load_file | bool | yes | False | If set to True the file contents will be loaded into the result. |
mode | str | yes | auto | Open mode of the file (only necessary when load_file is True). Can be text, binary or auto (guessing). |
base64 | bool | yes | False | If set to True the loaded file contents will be converted to base64 (only applicable when load_file is True). Argument mode will be automatically set to ‘binary’ |
defer_modified | float | yes | 0.5 | There might be multiple flushes of a file before it is written completely to disk. Without defer_modified each flush will raise a modified event. |
Result
Example of an emitted message:
{
"operation": "modified",
"source": "/tmp/abc.txt",
"is_directory": False,
"destination": None, # Only non-None when operation = "moved"
"file": { # Only present when load_file is True
"file_name": "abc.txt",
"content": "foo and bar",
"read_mode": "text",
"base64": False
}
}
Example
tasks:
- name: file_watcher
pull:
plugin: pnp.plugins.pull.fs.FileSystemWatcher
args:
path: "/tmp"
ignore_directories: true
events: [created, deleted, modified]
load_file: false
push:
plugin: pnp.plugins.push.simple.Echo
fs.Size¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.fs.Size | poll | none | 0.17.0 |
Description
Periodically determines the size of the specified files or directories in bytes.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
paths | List[str] | no | n/a | List of files and/or directories to monitor their sizes in bytes. |
fail_on_error | bool | yes | True | If set to true, the plugin will raise an error when a file/directory does not exists or any other file system related error occurs. Otherwise the plugin will proceed and simply report None as size. |
Note
Be careful when adding directories with a large amount of files. This will be prettly slow cause the plugin will iterate over each file and determine it’s individual size.
Result
Example of an emitted message. Size is in bytes.
{
"logs": 32899586,
"copy": 28912
}
Example
tasks:
- name: file_size
pull:
plugin: pnp.plugins.pull.fs.Size
args:
instant_run: true
interval: 5s
fail_on_error: false
paths:
logs: /var/log # directory - recursively determines size
copy: /bin/cp # file
push:
plugin: pnp.plugins.push.simple.Echo
ftp.Server¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.ftp.Server | pull | ftp | 0.17.0 |
Description
Runs a ftp server on the specified port to receive and send files by ftp protocol.
Optionally sets up a simple user/password authentication mechanism.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
directory | str | yes | None | The directory to serve via ftp protocol. If not given a directory is created is created temporarily to accept incoming uploads. |
port | int | yes | 2121 | The port to listen on. |
user_pwd | str / Tuple[str] | yes | None | User/password combination (as a tuple/list; see example). You may specify the user only - password will be empty OR you can enable anonymous access by not providing the argument. |
events | List[str] | yes | None | A list of events to subscribe to. Available events are: connect, disconnect, login, logout, file_received, file_sent, file_received_incomplete, file_sent_incomplete. By default all events are subscribed. |
max_cons | int | yes | 256 | The maximum number of simultaneous connections the ftpserver will permit. |
max_cons_ip | int | yes | 5 | The maximum number of simultaneous connections from the same ip. Default is 5. |
Result
All emitted messages will have an event field to identify the type of the event and an - optional - data field.
The data field will contain the user for login/logout events and the file_path for file-related events.
{
"event": "file_received",
"data": {
"file_path": "/private/tmp/ftp/test.txt"
}
}
Example
tasks:
- name: ftp_server
pull:
plugin: pnp.plugins.pull.ftp.Server
args:
directory: !env FTP_DIR
user_pwd: [admin, root] # user: admin, pw: root
events:
- file_received
- file_sent
push:
- plugin: pnp.plugins.push.simple.Echo
gpio.Watcher¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.gpio.Watcher | poll | gpio | 0.12.0 |
Description
Listens for low/high state changes on the configured gpio pins.
In more detail the plugin can raise events when one of the following situations occur:
- rising (high) of a gpio pin - multiple events may occur in a short period of time
- falling (low) of a gpio pin - multiple events may occur in a short period of time
- switch of gpio pin - will suppress multiple events a defined period of time (bounce time)
- motion of gpio pin - will raise the event motion_on if the pin rises and set a timer with a configurable amount of time. Any other gpio rising events will reset the timer. When the timer expires the motion_off event is raised.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
pins | List[int] | no | n/a | The gpio pins to observe for state changes. Please see the examples section on how to configure it. |
default | str | no | n/a | The default edge that is applied when not configured. Please see the examples section for further details. One of rising , falling , switch , motion |
Result
Emits a dictionary that contains an entry for every sensor of the plant sensor device:
{
"gpio_pin": 17 # The gpio pin which state has changed
"event": rising # One of [rising, falling, switch, motion_on, motion_off]
}
Example
tasks:
- name: gpio
pull:
plugin: pnp.plugins.pull.gpio.Watcher
args:
default: rising
pins:
- 2 # No mode specified: Default mode (in this case 'rising')
- 2 # Duplicates will get ignored
- 3:rising # Equal to '3' (without explicit mode)
- 3:falling # Get the falling event for gpio pin 3 as well
- 4:switch # Uses some debouncing magic and emits only one rising event
- 5:switch(1000) # Specify debounce in millseconds (default is 500ms)
- 5:switch(500) # Duplicates - even when they have other arguments - will get ignored
- 7:motion # Uses some delay magic to emit only one motion on and one motion off event
- 9:motion(1m) # Specify delay (default is 30 seconds)
push:
- plugin: pnp.plugins.push.simple.Echo
hass.State¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.hass.State | pull | none | 0.14.0 |
Description
Connects to the home assistant
websocket api and listens for state changes. If no include
or exclude
is defined
it will report all state changes. If include
is defined only entities that match one of the specified patterns will
be emitted. If exclude
if defined entities that match at least one of the specified patterns will be ignored. exclude
patterns overrides include
patterns.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
host | str | no | n/a | Url to your home assistant instance (e.g. http://my-hass:8123 ) |
token | str | no | n/a | Your long lived access token to access the websocket api. See below for further instructions |
include | List[str] | yes | n/a | Patterns of entity state changes to include. All state changes that do not match the defined patterns will be ignored |
exclude | List[str] | yes | n/a | Patterns of entity state changes to exclude. All state changes that do match the defined patterns will be ignored |
Note
include
andexclude
support wildcards (e.g*
and?
)exclude
overridesinclude
. So you can include everything from a domain (sensor.*
) but exclude individual entities.- Create a long lived access token: Home Assistant documentation
Result
The emitted result always contains the entity_id, new_state and old_state:
{
"entity_id": "light.bedroom_lamp",
"old_state": {
"state": "off",
"attributes": {},
"last_changed": "2019-01-08T18:24:42.087195+00:00",
"last_updated": "2019-01-08T18:40:40.011459+00:00"
},
"new_state": {
"state": "on",
"attributes": {},
"last_changed": "2019-01-08T18:41:06.329699+00:00",
"last_updated": "2019-01-08T18:41:06.329699+00:00"
}
}
Example
tasks:
- name: hass_state
pull:
plugin: pnp.plugins.pull.hass.State
args:
url: http://localhost:8123
token: !env HA_TOKEN
exclude:
- light.lamp
include:
- light.*
push:
- plugin: pnp.plugins.push.simple.Echo
http.Server¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.http.Server | pull | none | < 0.10.0 |
Description
Creates a specific route on the builtin api server and listens to any call to that route.
Any data passed to the endpoint will be tried to be parsed to a dictionary (json).
If this is not possible the data will be passed as is. See sections Result
for specific payload and examples.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
prefix_path | str | no | n/a | The route to create for incoming traffic on the builtin api server. See the Example section for reference. |
allowed_methods | List[str] | yes | GET | List of http methods that are allowed. Default is ‘GET’. |
Result
Assumes that you configured your pull with prefix_path = callme
curl -X GET "http://localhost:9999/callme/telephone/now?number=12345&priority=high" --data '{"magic": 42}'
{
"endpoint": "telephone/now",
"data": {"magic": 42},
"levels": ["telephone", "now"],
"method": "GET",
"query": {"number": "12345", "priority": "high"},
"is_json": True,
"url": "http://localhost:9999/callme/telephone/now?number=12345&priority=high",
"full_path": "/callme/telephone/now?number=12345&priority=high",
"path": "/callme/telephone/now"
}
Example
#
# Registers the endpoint /callme to the builtin api server.
# Use curl to try it out:
# curl -X GET "http://localhost:9999/callme/telephone/now?number=12345&priority=high" --data '{"magic": 42}'
#
api: # You need to enable the api
port: 9999 # Mandatory
tasks:
- name: server
pull:
plugin: pnp.plugins.pull.http.Server
args:
prefix_path: callme # Results into http://localhost:9999/callme
allowed_methods: # Specify which methods are allowed
- GET
- POST
push:
plugin: pnp.plugins.push.simple.Echo
monitor.Stats¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.monitor.Stats | poll | none | 0.12.0 |
Description
Emits every interval
various metrics / statistics about the host system.
Please see the Result
section for available metrics.
Result
Emits a dictionary that contains an entry for every sensor of the plant sensor device:
{
"cpu_count": 4,
"cpu_freq": 2700,
"cpu_temp": 0.0,
"cpu_use": 80.0,
"disk_use": 75.1,
"load_1m": 2.01171875,
"load_5m": 1.89501953125,
"load_15m": 1.94189453125,
"memory_use": 67.0,
"rpi_cpu_freq_capped": 0,
"rpi_temp_limit_throttle": 0,
"rpi_throttle": 0,
"rpi_under_voltage": 0,
"swap_use": 36.1
}
Example
tasks:
- name: stats
pull:
plugin: pnp.plugins.pull.monitor.Stats
args:
interval: 10s
instant_run: true
push:
plugin: pnp.plugins.push.simple.Echo
mqtt.Subscribe¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.mqtt.Subscribe | pull | none | < 0.10.0 |
Description
Pulls messages from the specified topic from the given mosquitto mqtt broker (identified by host and port).
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
host | str | no | n/a | Host where the mosquitto broker is running. |
port | int | no | n/a | Port where the mosquitto broker is listening. |
topic | str | no | n/a | Topic to listen for new messages. You can listen to multiple topics by using the #-wildcard (e.g. test/# will listen to all topics underneath test). |
Result
The emitted message will look like this:
{
"topic": "test/device/device1",
"levels": ["test", "device", "device1"]
"payload": "The actual event message"
}
Example
tasks:
- name: mqtt
pull:
plugin: pnp.plugins.pull.mqtt.Subscribe
args:
host: localhost
port: 1883
topic: test/#
push:
plugin: pnp.plugins.push.simple.Echo
net.PortProbe¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.net.PortProbe | poll | none | 0.19.0 |
Description
Periodically establishes socket connection to check if anybody is listening on a given server on a specific port.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
port | int | no | n/a | The port to probe if a service is listening |
server | str | yes | localhost | Server name or ip address |
timeout | float | yes | 1.0 | Timeout for remote operations |
Result
Emits a dictionary that contains an entry for every sensor of the plant sensor device:
{
"server": "www.google.de",
"port": 80,
"reachable": True
}
Example
tasks:
- name: port_probe
pull:
plugin: pnp.plugins.pull.net.PortProbe
args:
server: localhost # Server name or ip address, default is localhost
port: 9999 # The port to probe if somebody is listening
interval: 5s # Probe the port every five seconds ...
instant_run: true # ... and run as soon as pnp starts
push:
- plugin: pnp.plugins.push.simple.Echo
net.Speedtest¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.net.Speedtest | poll | speedtest | 0.25.0 |
Description
Performs a speedtest of your internet connection using speedtest.net
Arguments
No arguments
Result
{
"download_speed_bps": 13815345.098080076,
"download_speed_mbps": 13.82,
"upload_speed_bps": 1633087.176468341,
"upload_speed_mbps": 1.63,
"ping_latency": 19.933,
"result_image": "http://www.speedtest.net/result/10049630297.png",
"server": {
"name": "Deutsche Telekom",
"host": "ham.wsqm.telekom-dienste.de:8080",
"location": {
"city": "Hamburg",
"country": "Germany",
"lat": "53.5653",
"lon": "10.0014"
}
},
"client": {
"isp": "Vodafone DSL",
"rating": "3.7"
}
}
Example
tasks:
- name: speedtest
pull:
plugin: pnp.plugins.pull.net.Speedtest
args:
num_parallel_requests: 2 # Number of parallel requests
interval: 1h # Run every hour
instant_run: true # Run as soon as pnp starts
push:
- plugin: pnp.plugins.push.simple.Echo
net.SSLVerify¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.net.SSLVerify | poll | none | 0.22.0 |
Description
Periodically checks if the ssl certificate of a given host is valid and how many days are remaining before the certificate will expire.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
host | str | no | n/a | The host to check for it’s SSL certificate. |
timeout | float | yes | 3.0 | Timeout for remote operation. |
Result
{
# Envelope
"host": "www.google.com",
"payload": {
"expires_days": 50, # Remaining days before expiration
"expires_at": datetime.datetime(2020, 5, 26, 9, 45, 52), # Python datetime of expiration
"expired": False # True of the certificate is expired; otherwise False.
}
}
Example
tasks:
- name: ssl_verify
pull:
plugin: pnp.plugins.pull.net.SSLVerify
args:
host: www.google.com # Check the ssl certificate for this host
interval: 1m # Check the ssl certificate every minute
instant_run: true # ... and run as soon as pnp starts
push:
- plugin: pnp.plugins.push.simple.Echo
presence.FritzBoxTracker¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.presence.FritzBoxTracker | poll | fritz | 0.22.0 |
Description
Periodically asks a Fritz!Box router for the devices that were connected in the past or right now.
Note
Extra fritz
is only compatible with python 3.6
or higher
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
host | str | yes | 169.254.1.1 | The IP address of your Fritz!Box. |
user | str | yes | admin | The user to use. |
password | str | yes | <empty> | The password to use. |
offline_delay | int | yes | 0 | Defines how many intervals to wait before marking a device as not connected after the Fritz!Box reported the device as not connected anymore. This is useful for mobile devices that go temporarily to sleep and drop connection. Default is 0 -> Disconnected devices will be instantly reported as disconnected. |
whitelist | List[str] | yes | None | A specific list of devices to track (identified by mac address). If not passed all devices will be fetched. |
Note
By using the default values you should be able to connect to your Fritz!Box, because the necessary operation can be performed anonymously.
Result
{
"ip": "192.168.178.2",
"mac": "00:0a:95:9d:68:16",
"status": True, # True or False
"name": "pc1"
}
Example
tasks:
- name: fritzbox_tracker
pull:
plugin: pnp.plugins.pull.presence.FritzBoxTracker
args:
host: 169.254.1.1 # IP of your Fritz!Box. Default is 169.254.1.1
user: admin # User name. Default is admin
password: '' # Password. Default is an empty string
offline_delay: 0 # How many intervals to wait before marking a device as not connected after the fritzbox reported so
instant_run: true # ... and run as soon as pnp starts
push:
- plugin: pnp.plugins.push.simple.Echo
tasks:
- name: fritzbox_tracker_whitelist
pull:
plugin: pnp.plugins.pull.presence.FritzBoxTracker
args:
host: 169.254.1.1 # IP of your Fritz!Box. Default is 169.254.1.1
user: admin # User name. Default is admin
password: '' # Password. Default is an empty string
offline_delay: 0 # How many intervals to wait before marking a device as not connected after the fritzbox reported so
whitelist: # A specific list of devices to track (identified by mac address)
- B0:05:94:77:B8:3B
- 90:CD:B6:DC:8D:61
instant_run: true # ... and run as soon as pnp starts
push:
- plugin: pnp.plugins.push.simple.Echo
sensor.DHT¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.sensor.DHT | poll | dht | < 0.10.0 |
Description
Periodically polls a dht11 or dht22 (aka am2302) for temperature and humidity readings.
Polling interval is controlled by interval
.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
device | str | yes | dht22 | The device to poll (one of dht22, dht11, am2302). |
data_gpio | int | yes | 17 | The data gpio port where the device operates on. |
humidity_offset | float | yes | 0.0 | Positive/Negative offset for humidity. |
temp_offset | float | yes | 0.0 | Positive/Negative offset for temperature. |
Result
{
"humidity": 65.4 # in %
"temperature": 23.7 # in celsius
}
Example
tasks:
- name: dht
pull:
plugin: pnp.plugins.pull.sensor.DHT
args:
device: dht22 # Connect to a dht22
data_gpio: 17 # DHT is connected to gpio port 17
interval: 5m # Polls the readings every 5 minutes
humidity_offset: -5.0 # Subtracts 5% from the humidity reading
temp_offset: 1.0 # Adds 1 °C to the temperature reading
instant_run: true
push:
- plugin: pnp.plugins.push.simple.Echo
selector: payload.temperature # Temperature reading
- plugin: pnp.plugins.push.simple.Echo
selector: payload.humidity # Humidity reading
sensor.MiFlora¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.sensor.MiFlora | poll | miflora | 0.16.0 |
Description
Periodically polls a Xiaomi MiFlora plant sensor
for sensor readings
(temperature, conductivity, light, …) via btle
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
mac | str | no | n/a | The device to poll identified by mac address. See below for further instructions |
adapter | str | yes | hci0 | The bluetooth adapter to use (if you have more than one). Default is |
Note
Start a bluetooth scan to determine the MAC addresses of the sensor (look for Flower care or Flower mate entries) using this command:
$ sudo hcitool lescan
LE Scan ...
F8:04:33:AF:AB:A2 [TV] UE48JU6580
C4:D3:8C:12:4C:57 Flower mate
[...]
Result
Emits a dictionary that contains an entry for every sensor of the plant sensor device:
{
"conductivity": 800,
"light": 2000,
"moisture": 42,
"battery": 72,
"temperature": 24.2,
"firmaware": "3.1.9"
}
Example
- name: miflora
pull:
plugin: pnp.plugins.pull.sensor.MiFlora
args:
mac: 'C4:7C:8D:67:50:AB' # The mac of your miflora device
instant_run: true
push:
- plugin: pnp.plugins.push.simple.Echo
sensor.Sound¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.sensor.Sound | pull | sound | 0.15.0 |
Description
Listens to the microphone in realtime and searches the stream for specific sound patterns.
Practical example: I use this plugin to recognize my doorbell without tampering with the electrical device ;-)
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
wav_files | List[Dict] | no | n/a | See below for a detailed description |
device_index | int | yes | None | The index of the microphone device. Run pnp_record_sound --list to get the index.
If not specified pyAudio will try to find a capable device |
ignore_overflow | bool | yes | true | If set to True any buffer overflows due to slow realtime processing will be ignored. Otherwise an exception will be thrown and the plugin will abort. |
wav_files
A list of dictionaries containing the configuration for each file that contains an original sound pattern to listen for. Possible keys:
name type opt. default description path str no n/a The path to the original sound file. Absolute or relative to the pnp configuration file mode str yes pearson Correlation/similarity method. Default is pearson. Try out which one is best for you offset float yes 0.0 Adjusts sensitivity for similarity. Positive means less sensitive; negative is more sensitive. You should try out 0.1 steps cooldown Dict yes special See below for a detailed description cooldown
Contains the cooldown configuration. Default is a cooldown period of 10 seconds and no emit of a cooldown event. Possible keys:
name type opt. default description period str yes 10s Prevents the pull to emit more than one sound detection event per cool down period. emit_event bool yes false If set to true the end of the cooldown period will an emit as well.
Note
- You can list your available input devices:
pnp_record_sound --list
- You can record a wav file from an input device:
pnp_record_sound <out.wav> --seconds=<seconds_to_record> --index=<idx>
- This one is _not_ pre-installed when using the docker image. Would be grateful if anyone can integrate it
Result
Will emit the event below when the correlation coefficient is above or equal the threshold. In this case the component has detected a sound that is similar to one of the given sound patterns
{
"type": "sound" # Type 'sound' means we detected a sound pattern
"sound": ding, # Name of the wav_file without path and extension. To differentiate if you have multiple patterns you listen to
"corrcoef": 0.82, # Correlation coefficient probably between [-1;+1] for pearson
"threshold": 0.6 # Threshold influenced by sensitivity_offset
}
Will emit the event below when you have configured the component to send cooldown events as well.
{
"type": "cooldown" # Type 'cooldown' means that we previously identified a sound pattern and the cooldown has happened
"sound": ding, # Name of the wav_file without path and extension. To differentiate if you have multiple patterns you listen to
}
Example
tasks:
- name: sound_detector
pull:
plugin: pnp.plugins.pull.sensor.Sound
args:
wav_files: # The files to compare for similarity
- path: ding.wav # Absolute or relative (from the config) path to the wav file
mode: std # Use std correlation coefficient [pearson, std]; optional default is pearson
offset: -0.5 # Adjust sensitivity. Positive means less sensitive; negative is more sensitive. Default is 0.0
- path: doorbell.wav # This will use default values for mode and offset (pearson, 0.0)
cooldown:
period: 10s # Prevents the pull to emit more than one sound detection event every 10 seconds
emit_event: true # Fire an event after the actual cool down - Useful for binary_sensors to return to their 'off' state
device_index: # The index of the microphone devices. If not specified pyAudio will try to find a capable device
ignore_overflow: true # Some devices might be too slow to process the stream in realtime. Ignore any buffer overflow errors.
push:
- plugin: pnp.plugins.push.simple.Echo
simple.Count¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.simple.Count | poll | none | < 0.10.0 |
Description
Emits every interval
seconds a counting value which runs from from_cnt
to to_cnt
.
If to_cnt
is None the counter will count to infinity (or more precise to sys.maxsize
).
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
from_cnt | int | yes | 0 | Starting value of the counter. |
to_cnt | int | yes | sys.maxsize | End value of the counter. If not passed set to “infinity” (precise: sys.maxsize ) |
Result
Counter value (int).
Example
tasks:
- name: count
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 1s
from_cnt: 1
to_cnt: 10
push:
plugin: pnp.plugins.push.simple.Echo
simple.Cron¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.simple.Cron | pull | none | 0.16.0 |
Description
Execute push-components based on time constraints configured by cron-like expressions.
This plugin basically wraps cronex to parse cron expressions and to check if
any job is pending. See the documentation of cronex
for a guide on featured/supported cron expressions.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
exppresions | List[str] | no | n/a | Cron like expressions to configure the scheduler. |
Result
Imagine your cron expressions looks like this: */1 * * * * every minute
.
The pull will emit the text every minute
every minute.
Example
tasks:
- name: cron
pull:
plugin: pnp.plugins.pull.simple.Cron
args:
expressions:
- "*/1 * * * * every minute"
- "0 15 * * * 3pm"
- "0 0 * * * midnight every day"
- "0 16 * * 1-5 every weekday @ 4pm"
push:
plugin: pnp.plugins.push.simple.Echo
simple.Repeat¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.simple.Repeat | poll | none | < 0.10.0 |
Description
Emits every interval
seconds the same repeat
.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
repeat | Any | no | n/a | The object to emit. |
Result
Emits the repeat
-object as it is.
Example
tasks:
- name: repeat
pull:
plugin: pnp.plugins.pull.simple.Repeat
args:
repeat: "Hello World" # Repeats 'Hello World'
interval: 1s # Every second
push:
plugin: pnp.plugins.push.simple.Echo
simple.RunOnce¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.simple.RunOnce | pull | none | 0.23.0 |
Description
Takes a valid plugins.pull.Polling
component and immediately executes it and ventures
down the given plugins.push
components. If no component to wrap is given it will simple execute the
push chain.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
poll | plugin | yes | None | The polling component you want to run once. If not passed the push chain will be executed. |
Result
Emits the payload of the polling component if given. Otherwise an empty dictionary will be returned.
Example
tasks:
- name: run_once
pull:
plugin: pnp.plugins.pull.simple.RunOnce
push:
plugin: pnp.plugins.push.simple.Echo
tasks:
- name: run_once_wrapped
pull:
plugin: pnp.plugins.pull.simple.RunOnce
args:
poll:
plugin: pnp.plugins.pull.monitor.Stats
push:
plugin: pnp.plugins.push.simple.Echo
zway.ZwayPoll¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.pull.zway.ZwayPoll | poll | none | < 0.10.0 |
Description
Pulls the specified json content from the zway rest api. The content is specified by the url, e.g.
http://<host>:8083/ZWaveAPI/Run/devices
will pull all devices and serve the result as a json.
Specify the polling interval by setting the argument interval
. User / password combination is required when
your api is protected against guest access (by default it is).
Use multiple pushes and the related selectors to extract the required content like temperature readings (see the examples section for guidance).
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
url | str | no | n/a | The url to poll periodically. |
user | str | no | n/a | Authentication user name. |
password | str | no | n/a | Authentication password. |
Note
Below are some common selector examples to fetch various metrics from various devices
Fibaro Motion Sensor
- Temperature
payload[deviceid].instances[0].commandClasses[49].data[1].val.value
- Luminescence
payload[deviceid].instances[0].commandClasses[49].data[3].val.value
Fibaro Wallplug
- Meter
payload[deviceid].instances[0].commandClasses[50].data[0].val.value
Thermostat (Danfoss / other should work as well)
- Setpoint
payload[deviceid].instances[0].commandClasses[67].data[1].val.value
Battery operated devices
- Battery level
payload[deviceid].instances[0].commandClasses[128].data.last.value
Result
Emits the content of the fetched url as it is.
Example
# Please make sure to adjust url and device ids
# Username and Password are injected from environment variables:
# export ZWAY_USER=admin
# export ZWAY_PASSWORD=secret_one
tasks:
- name: zway
pull:
plugin: pnp.plugins.pull.zway.ZwayPoll
args:
url: "http://smarthome:8083/ZWaveAPI/Run/devices"
interval: 5s
user: !env ZWAY_USER
password: !env ZWAY_PASSWORD
push:
- plugin: pnp.plugins.push.simple.Echo
# Temperature of fibaro motion sensor
# You can access the returned json like you would inquire the zway-api
selector: payload[19].instances[0].commandClasses[49].data[1].val.value
- plugin: pnp.plugins.push.simple.Echo
# Luminiscence of fibaro motion sensor
selector: payload[19].instances[0].commandClasses[49].data[3].val.value
Pushes¶
Like a pull
a push
does support args
to initialize the instance of a push
.
Besides that you can optionally pass a selector
to transform the incoming payload
and set the unwrap
option to invoke a push
for each element of an iterable
.
See also
Some pushes
do support the envelope
feature to alter the arguments for a push
during
runtime: Envelope
fs.FileDump¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.fs.FileDump | push | none | < 0.10.0 |
Description
This push dumps the given payload
to a file to the specified directory
.
If argument file_name
is None
, a name will be generated based on the current datetime (%Y%m%d-%H%M%S
).
If file_name
is not passed (or None
) you should pass extension
to specify the extension of the generated
file name.
Argument binary_mode
controls whether the dump is binary (mode=wb
) or text (mode=w
).
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
directory | str | yes | . (cwd) | no | The target directory to store the dumps. |
file_name | str | yes | None | yes | The name of the file to dump. If not passed a file name will be automatically generated. |
extension | str | yes | .dump | yes | The extension to use when the file name is automatically generated. |
binary_mode | bool | yes | False | no | If set to True the file will be written in binary mode (wb ); otherwise in text mode (w ). |
Result
Will return an absolute path to the file created.
Example
tasks:
- name: file_dump
pull:
plugin: pnp.plugins.pull.simple.Repeat
args:
repeat: "Hello World"
push:
plugin: pnp.plugins.push.fs.FileDump
args:
directory: !env WATCH_DIR
file_name: null # Auto-generated file (timestamp)
extension: ".txt" # Extension of auto-generated file
binary_mode: false # text mode
deps:
- plugin: pnp.plugins.push.simple.Echo
tasks:
- name: file_dump
pull:
plugin: pnp.plugins.pull.simple.Repeat
args:
repeat: "Hello World"
push:
plugin: pnp.plugins.push.fs.FileDump
# Override `file_name` and `extension` via envelope.
# Instead of an auto generated file, the file '/tmp/hello-world.hello' will be dumped.
selector:
data: "lambda data: data"
file_name: hello-world
extension: .hello
args:
directory: !env WATCH_DIR
file_name: null # Auto-generated file (timestamp)
extension: ".txt" # Extension of auto-generated file
binary_mode: false # text mode
deps:
- plugin: pnp.plugins.push.simple.Echo
fs.Zipper¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.fs.Zipper | push | none | 0.21.0 |
Description
The push expects a directory or a file path to be passed as the payload. As long it’s a valid path it will zip the directory or the single file and return the absolute path to the created zip file.
Note
You can use a so called .zipignore
file to exclude files and directories from zipping.
It works - mostly - like a .gitignore
file.
To use a .zipignore
file you have to put it in the root the folder you want to zip.
An example .zipignore
looks like this:
__pycache__/
*.log
This example will ignore all folder called __pycache__
and all files with the extension .log
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
source | str | yes | n/a | yes | Specifies the source directory or file to zip. If not passed the source can be specified by the envelope at runtime. |
out_path | str | yes | tmp | no | Specifies the path to the general output path where all target zip files should be generated. If not passed the systems temp directory is used. |
archive_name | str | yes | below | yes | Explicitly specifies the name of the resulting archive. |
The default of archive_name
will be either the original file name (if you zip a single file)
resp. the name of the zipped directory (if you zip a directory).
In both cases the extension .zip
will be added.
If you do not want an extension, you have to provide the archive_name
.
Result
Will return an absolute path to the zip file created.
Example
tasks:
- name: zipper
pull:
plugin: pnp.plugins.pull.simple.Cron
args:
expressions:
- "*/1 * * * * /path/to/backup"
push:
plugin: pnp.plugins.push.fs.Zipper
args:
out_path: !env BACKUP_DIR
deps:
plugin: pnp.plugins.push.simple.Echo
The next example is useful for dynamically adjusting the archive name to generate unique names for storing multiple backups:
tasks:
- name: zipper
pull:
plugin: pnp.plugins.pull.simple.Cron
args:
expressions:
- "*/1 * * * * /tmp/backup_folder"
push:
plugin: pnp.plugins.push.fs.Zipper
args:
out_path: !env BACKUP_DIR
selector:
archive_name: "lambda payload: '{}_{}'.format(now().isoformat(), 'backup.zip')"
data: "lambda payload: payload"
deps:
plugin: pnp.plugins.push.simple.Echo
hass.Service¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.hass.Service | push | none | 0.16.0 |
Description
Calls a home assistant service providing the payload as service-data.
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
url | str | no | n/a | no | The url to your home assistant instance (e.g. http://hass:8123 ) |
token | str | no | n/a | no | The long live access token to get access to home assistant. |
domain | str | no | n/a | no | The domain of the service to call. |
service | str | no | n/a | no | The name of the service to call. |
timeout | int|float | yes | 5.0 | no | Tell the request to stop waiting for a response after given number of seconds. |
Note
Create a long lived access token: Home Assistant documentation
Result
Returns the payload as-is for better chaining (this plugin can’t add any useful information).
Example
# Calls the frontend.set_theme service to oscillate between a "light" and a "dark" theme
tasks:
- name: hass_service
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 10s
push:
plugin: pnp.plugins.push.hass.Service
selector:
name: "lambda i: 'clear' if i % 2 == 0 else 'dark'"
args:
url: http://localhost:8123
token: !env HA_TOKEN
domain: frontend
service: set_theme
# Calls the notify.notify service to send a message with the actual counter
tasks:
- name: hass_service
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 10s
push:
plugin: pnp.plugins.push.hass.Service
selector:
message: "lambda i: 'Counter: ' + str(i)"
args:
url: http://localhost:8123
token: !env HA_TOKEN
domain: notify
service: notify
http.Call¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.http.Call | push | none | 0.21.0 |
Description
Makes a request to a http resource.
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
url | str | no | n/a | yes | Request url. |
method | str | yes | GET | yes | The http method to use for the request. Must be a valid http method (GET, POST, …). |
fail_on_error | bool | yes | False | yes | If True the push will fail on a http status code <> 2xx. This leads to an error message recorded into the logs and no further execution of any dependencies. |
provide_response | bool | yes | False | no | If True the push will not return the payload as it is, but instead provide the response status_code, fetched url content and a flag if the url content is a json response. This is useful for other push instances in the dependency chain. |
Result
Will return the payload as it is for easy chaining of dependencies.
If provide_response
is True the push will return a dictionary that looks like this:
{
"status_code": 200,
"data": "fetched url content",
"is_json": False
}
Example
# Simple example calling the built-in rest server
# Oscillates between http method GET and POST. Depending on the fact if the counter is even or not.
api:
port: 9999
tasks:
- name: http_call
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 5s
push:
plugin: pnp.plugins.push.http.Call
selector:
data:
counter: "lambda data: data"
method: "lambda data: 'POST' if int(data) % 2 == 0 else 'GET'"
args:
url: http://localhost:9999/counter
- name: rest_server
pull:
plugin: pnp.plugins.pull.http.Server
args:
prefix_path: counter
allowed_methods:
- GET
- POST
push:
plugin: pnp.plugins.push.simple.Echo
# Demonstrates the use of `provide_response` set to True.
# Call will return a response object to dependent push instances.
api:
port: 9999
tasks:
- name: http_call
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 5s
push:
plugin: pnp.plugins.push.http.Call
args:
url: http://localhost:9999/counter
provide_response: true
deps:
plugin: pnp.plugins.push.simple.Echo
- name: rest_server
pull:
plugin: pnp.plugins.pull.http.Server
args:
prefix_path: counter
allowed_methods:
- GET
push:
plugin: pnp.plugins.push.simple.Nop
ml.FaceR¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.ml.FaceR | push | faceR | < 0.10.0 |
Description
FaceR (short one for face recognition) tags known faces in images. Output is the image with all faces tagged whether
with the known name or an unknown_label
. Default for unknown ones is Unknown
.
Known faces can be ingested either by a directory of known faces (known_faces_dir
) or by mapping of known_faces
(dictionary: name -> [list of face files]).
The payload
passed to the push
method is expected to be a valid byte array that represents an image in memory.
Please see the example section for loading physical files into memory.
Note
This one is not pre-installed when using the docker image. Would be grateful if anyone can integrate it
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
known_faces | Dict[str, str] | yes | None | no | Mapping of a person’s name to a list of images that contain the person’s face. |
known_faces_dir | str | yes | None | no | A directory containing images with known persons (file_name -> person’s name). |
unknown_label | str | yes | Unknown | no | Tag label of unknown faces. |
lazy | bool | yes | False | no | If set to True the face encodings will be loaded when the first push is executed (lazy); otherwise the encodings are loaded when the plugin is initialized (during __init__ ). |
Note
You need to specify either known_faces
or known_faces_dir
Result
Will return a dictionary that contains the bytes of the tagged image (key tagged_image
) and metadata (no_of_faces
,
known_faces
)
{
'tagged_image': <bytes of tagged image>
'no_of_faces': 2
'known_faces': ['obama']
}
Example
tasks:
- name: faceR
pull:
plugin: pnp.plugins.pull.fs.FileSystemWatcher
args:
path: "/tmp/camera"
recursive: true
patterns: "*.jpg"
ignore_directories: true
case_sensitive: false
events: [created]
load_file: true
mode: binary
base64: false
push:
plugin: pnp.plugins.push.ml.FaceR
args:
known_faces_dir: "/tmp/faces"
unknown_label: "don't know him"
lazy: true
mqtt.Discovery¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.mqtt.Discovery | push | none | 0.13.0 |
Description
Pushes an entity to home assistant by publishing it to an mqtt broker. The entity will be enabled to be auto discovered by home assistant.
Please see the home assistant docs about mqtt auto discovery.
The mqtt topic is structured like this:
<discovery_prefix>/<component>/[<node_id>/]<object_id>/[config|state]
You may also publish attributes
besides your state. attributes
can be passed by the envelope
via runtime. Please see the examples section for further reference.
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
discovery_prefix | str | no | n/a | no | The prefix for the topic. |
component | str | no | n/a | no | The component / type of the entity, e.g. sensor , light , … |
config | Dict | no | n/a | no | A dictionary of configuration items to configure the entity (e.g. icon -> mdi:soccer ) |
object_id | str | yes | None | yes | The ID of the device. This is only to allow for separate topics for each device and is not used for the entity_id. |
node_id | str | yes | None | yes | A non-interpreted structuring entity to structure the MQTT topic. |
Note
Inside the config
section you can reference some variables to make the configuration easier.
The following variables can be referenced via the dictmentor
syntax "{{var::<variable>}}"
:
- discovery_prefix
- component
- object_id
- node_id
- base_topic
- config_topic
- state_topic
- json_attributes_topic
Please see the examples section on how to use that.
Result
Returns the payload as-is for better chaining (this plugin can’t add any useful information).
Example
tasks:
- name: fitbit_steps
pull:
plugin: pnp.plugins.pull.fitbit.Current
args:
config: !env FITBIT_AUTH
instant_run: true
interval: 5m
resources:
- activities/steps
push:
- plugin: pnp.plugins.push.mqtt.Discovery
selector: "data.get('activities/steps')"
args:
host: localhost
discovery_prefix: homeassistant
component: sensor
object_id: fitbit_steps
config:
name: "{{var::object_id}}"
icon: "mdi:soccer"
name: service_probing
pull:
plugin: pnp.plugins.pull.net.PortProbe
args:
server: server # Server name or ip address, default is localhost
port: 3000 # The port to probe if somebody is listening
timeout: 5
interval: 2m # Probe the port every five seconds ...
instant_run: true # ... and run as soon as pnp starts
push:
- plugin: pnp.plugins.push.mqtt.Discovery
selector:
data: "lambda data: 'OFF' if data.get('reachable') else 'ON'"
object_id: "service"
attributes:
friendly_name: My Service
icon: mdi:monitor-dashboard
args:
host: !env MQTT_HOST
discovery_prefix: !env MQTT_BASE_TOPIC
component: binary_sensor
config:
name: "{{var::object_id}}"
device_class: problem
mqtt.Publish¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.mqtt.Publish | push | none | < 0.10.0 |
Description
Will push the given payload
to a mqtt broker e.g. mosquitto
.
The broker is specified by host
and port
. In addition a topic
needs to be specified were the payload
will be pushed to (e.g. home/living/thermostat
).
The payload
will be pushed as it is. No transformation is applied. If you need to some transformations, use the
selector.
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
host | str | no | n/a | no | The host where the mqtt broker is running. |
port | int | yes | 1883 | no | The port where the mqtt broker is listening |
topic | Dict | yes | None | yes | The topic to subscribe to. If set to None the envelope of the payload has to contain a topic key or the push will fail. If both exists the topic from the envelope will overrule the __init__ one. |
retain | bool | yes | False | no | If set to True will mark the message as retained.
See the mosquitto man page for further guidance: https://mosquitto.org/man/mqtt-7.html. |
multi | bool | yes | False | no | If set to True the payload is expected to be a dictionary. Each item of that dictionary will be send individually to the broker. The key of the item will be appended to the configured topic. |
Result
For chaining of pushes the payload is simply returned as it is.
Example
# Demonstrates the basic mqtt.Publish
tasks:
- name: mqtt
pull:
plugin: pnp.plugins.pull.simple.Count
push:
# Will push the counter to the 'home/counter/state' topic
plugin: pnp.plugins.push.mqtt.Publish
args:
host: localhost
topic: home/counter/state
port: 1883
retain: true
# Demonstrates the topic override via envelope
tasks:
- name: mqtt
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 1s
push:
plugin: pnp.plugins.push.mqtt.Publish
# Lets override the topic via envelope mechanism
# Will publish even counts on topic 'even' and uneven counts on 'uneven'
selector:
data: "lambda data: data"
topic: "lambda data: 'test/even' if int(data) % 2 == 0 else 'test/uneven'"
args:
host: localhost
port: 1883
# Demonstrates the use of multi push
tasks:
- name: mqtt
pull:
# Periodically gets metrics about your system
plugin: pnp.plugins.pull.monitor.Stats
args:
instant_run: true
interval: 10s
push:
# Push them to the mqtt
plugin: pnp.plugins.push.mqtt.Publish
args:
host: localhost
topic: devices/localhost/
port: 1883
retain: true
# Each item of the payload-dict (cpu_count, cpu_usage, ...) will be pushed to the broker as multiple items.
# The key of the item will be appended to the topic, e.g. `devices/localhost/cpu_count`.
# The value of the item is the actual payload.
multi: true
notify.Slack¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.notify.Slack | push | none | 0.20.0 |
Description
Sends a message to a given Slack channel.
You can specify the channel, the name of the poster, the icon of the poster and a list of users to ping.
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
api_key | str | no | n/a | no | The api key of your slack oauth token. |
channel | str | no | n/a | yes | The channel to post the message to. |
username | str | yes | PnP | yes | The username of the message poster. |
emoji | str | yes | :robot: | yes | The emoji of the message poster. |
ping_users | List[str] | yes | None | yes | A list of users to ping when the message is posted. By default no one is ping’d. |
Result
Will return the payload as it is for easy chaining of dependencies.
Example
tasks:
- name: slack
pull:
plugin: pnp.plugins.pull.simple.Count # Let's count
args:
wait: 10
push:
- plugin: pnp.plugins.push.notify.Slack
selector:
data: "lambda data: 'This is the counter: {}'.format(data)"
# You can override the channel if necessary
# channel: "lambda data: 'test_even' if int(data) % 2 == 0 else 'test_odd'"
# You can override the username if necessary
# username: the_new_user
# You can override the emoji if necessary
# emoji: ':see_no_evil:'
# You can override the ping users if necessary
# ping_users:
# - clone_dede
args:
api_key: !env SLACK_API_KEY # Your slack api key.
channel: test # The channel to post to. Mandatory. Overridable by envelope.
username: slack_tester # The username to show. Default is PnP. Overridable by envelope
emoji: ':pig:' # The emoji to use. Default is :robot: . Overridable by envelope
ping_users: # The users you want to ping when the message is send. Overridable by envelope
- dede
simple.Echo¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.simple.Echo | push | none | < 0.10.0 |
Description
Simply log the passed payload to the default logging instance.
Result
Will return the payload as it is for easy chaining of dependencies.
Example
tasks:
- name: echo
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 1s
from_cnt: 1
to_cnt: 10
push:
plugin: pnp.plugins.push.simple.Echo
simple.Execute¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.simple.Execute | push | none | 0.12.0 |
Description
Executes a command with given arguments in a shell of the operating system.
Both command
and args
may include placeholders (e.g. {{placeholder}}
) which are injected at runtime
by passing the specified payload after selector transformation. Please see the examples section for further details.
Will return the exit code of the command and optionally the output from stdout and stderr.
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
command | str | no | n/a | no | The command to execute. May contain placeholders. |
args | List[str] | yes | [] | no | The arguments to pass to the command. Default is no arguments. May contain placeholders. |
cwd | str | yes | special | no | Specifies where to execute the command (working directory). Default is the folder where the invoked pnp configuration file is located. |
timeout | str|float | yes | 5s | no | Specifies how long the worker should wait for the command to finish. |
capture | bool | yes | False | no | If True stdout and stderr output is captured, otherwise not. |
Result
Returns a dictionary that contains the return_code
and optionally the output from stdout
and stderr
whether
capture
is set or not. The output is a list of lines.
{
"return_code": 0
"stdout": ["hello", "dude!"]
"stderr": []
}
Example
tasks:
- name: execute
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 1s
from_cnt: 1
push:
plugin: pnp.plugins.push.simple.Execute
selector:
command: echo
count: "lambda data: str(data)"
labels:
prefix: "The actual count is"
iter: iterations
args:
command: "{{command}}" # The command to execute (passed by selector)
args:
- "{{labels.prefix}}"
- "{{count}}" # The named argument passed at runtime by selector
- "{{labels.iter}}"
timeout: 2s
cwd: # None -> pnp-configuration directory
capture: true # Capture stdout and stderr
deps:
- plugin: pnp.plugins.push.simple.Echo
# How to escape " correctly
tasks:
- name: execute
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 1s
from_cnt: 1
push:
plugin: pnp.plugins.push.simple.Execute
selector:
command: echo
salutation: "\"hello you\""
args:
command: "{{command}}" # The command to execute (passed by selector)
args:
- "{{salutation}}"
timeout: 2s
cwd: # None -> pnp-configuration directory
capture: true # Capture stdout and stderr
deps:
- plugin: pnp.plugins.push.simple.Echo
# Capturing multiline output
tasks:
- name: execute
pull:
plugin: pnp.plugins.pull.simple.Count
args:
interval: 1s
from_cnt: 1
push:
plugin: pnp.plugins.push.simple.Execute
selector:
command: cat multiline.txt
args:
command: "{{command}}" # The command to execute (passed by selector)
cwd: # None -> pnp-configuration directory
capture: true # Capture stdout and stderr
deps:
- plugin: pnp.plugins.push.simple.Echo
simple.Nop¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.simple.Nop | push | none | < 0.10.0 |
Description
Executes no operation at all. A call to push(…) just returns the payload. This push is useful when you only need the power of the selector for dependent pushes.
See the example section for an example.
Nop = No operation OR No push ;-)
Result
Will return the payload as it is for easy chaining of dependencies.
Example
tasks:
- name: nop
pull:
plugin: pnp.plugins.pull.simple.Repeat
args:
interval: 1s
repeat:
- 1
- 2
- 3
push:
plugin: pnp.plugins.push.simple.Nop
selector: "data + [4]"
deps:
plugin: pnp.plugins.push.simple.Echo
unwrap: true
simple.Wait¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.simple.Wait | push | none | 0.19.0 |
Description
Performs a sleep operation and waits for some time to pass by.
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
wait_for | float|str | no | n/a | no | The time to wait for before proceeding.
You can pass literals such as 5s , 1m ; ints such as 1 , 2 , 3 or floats such as 0.5 .
In the end everything will be converted to it’s float representation (1 => 1.0; 5s => 5.0; 1m => 60.0; 0.5 => 0.5 ) |
Result
Will return the payload as it is for easy chaining of dependencies.
Example
tasks:
- name: wait
pull:
plugin: pnp.plugins.pull.simple.Count # Let's count
args:
interval: 1s
push:
- plugin: pnp.plugins.push.simple.Echo
selector: "'START WAITING: {}'.format(payload)"
deps:
- plugin: pnp.plugins.push.simple.Wait
args:
wait_for: 3s
deps:
- plugin: pnp.plugins.push.simple.Echo
selector: "'END WAITING: {}'.format(payload)"
storage.Dropbox¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.storage.Dropbox | push | dropbox | 0.12.0 |
Description
Uploads a provided file to the configured dropbox account.
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
api_key | str | no | n/a | no | The api key to your dropbox account/app. |
target_file_name | str | yes | None | yes | The file path on the server where to upload the file to. If not specified you have to specify this argument during runtime by setting it in the envelope. |
created_shared_link | bool | yes | True | no | If set to True , the push will create a publicly available link to your uploaded file. |
Result
Returns a dictionary that contains metadata information about your uploaded file. If you uploaded a file named 42.txt
,
your result will be similar to the one below:
{
"name": "42.txt",
"id": "HkdashdasdOOOOOadss",
"content_hash": "aljdhfjdahfafuhu489",
"size": 42,
"path": "/42.txt",
"shared_link": "http://someserver/tosomestuff/asdasd?dl=1",
"raw_link": "http://someserver/tosomestuff/asdasd?raw=1"
}
shared_link
is the one that is publicly available (but only if you know the link).
Same for raw_link
, but this link will return the raw file (without the dropbox overhead).
Both are None
if create_shared_link
is set to False
.
Example
tasks:
- name: dropbox
pull:
plugin: pnp.plugins.pull.fs.FileSystemWatcher
args:
path: "/tmp"
ignore_directories: true
events:
- created
- modified
load_file: false
push:
- plugin: pnp.plugins.push.storage.Dropbox
args:
api_key: !env DROPBOX_API_KEY
create_shared_link: true # Create a publicly available link
selector:
data: "lambda data: data.source" # Absolute path to file
target_file_name: "lambda data: basename(data.source)" # File name only
timedb.InfluxPush¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.push.timedb.InfluxPush | push | none | < 0.10.0 |
Description
Pushes the given payload
to an influx database using the line protocol
.
You have to specify host
, port
, user
, password
and the database
.
The protocol
is basically a string that will be augmented at push-time with data from the payload.
E.g. {payload.metric},room={payload.location} value={payload.value}
assumes that payload contains metric
, location
and value
.
See also
Arguments
name | type | opt. | default | env | description |
---|---|---|---|---|---|
host | str | no | n/a | no | The host where influx service is running. |
port | int | no | n/a | no | The port where the influx service is listening on. |
user | str | no | n/a | no | Username to use for authentication. |
password | str | no | n/a | no | Related password. |
database | str | no | n/a | no | The database to store the measurement. |
protocol | str | no | n/a | no | Line protocol template (augmented with payload-data). |
Result
For the ability to chain multiple pushes together the payload is simply returned as is.
Example
tasks:
- name: influx_push
pull:
plugin: pnp.plugins.pull.mqtt.Subscribe
args:
host: mqtt
topic: home/#
push:
plugin: pnp.plugins.push.timedb.InfluxPush
selector:
data: "lambda data: data"
args:
host: influxdb
port: 8086
user: root
password: secret
database: home
# This assumes that your topics are structured like this:
# home/<room e.g. living>/<sensor e.g. humidity>
protocol: "{payload.levels[2]},room={payload.levels[1]} value={payload.payload}"
UDFs¶
New in version 0.14.0.
All udfs do share the following base arguments:
name | type | opt. | default | description |
---|---|---|---|---|
throttle | str/float | yes | None | If set to a valid duration literal (e.g. 5m ) the return value of the called functions will be cached for the given amount of time. |
Note
Please note that even when an udf does not require arguments, you anyway have to specify the args:
section.
Otherwise it will be interpreted as a regular function and not as a UDF.
...
udfs:
- name: fsize
args:
tasks:
...
hass.State¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.udf.hass.State | udf | none | 0.14.0 |
Description
Fetches the state of an entity from home assistant by a rest-api call.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
url | str | no | n/a | The url to your home assistant instance (e.g. http://hass:8123 ) |
token | str | no | n/a | The long lived access token to get access to home assistant |
timeout | float | yes | 5.0 | Tell the request to abort the waiting for a response after given number of seconds |
Note
Create a long lived access token: Home Assistant documentation
Call Arguments
name | type | opt. | default | description |
---|---|---|---|---|
entity_id | str | no | n/a | The entity to fetch the state |
attribute | str | yes | None | Optionally you can fetch the state of one of the entity attributes. Not passed will fetch the state of the entity |
Result
Returns the current state of the entity or one of it’s attributes. If the entity is not known to home assistant an exception is raised.
In case of an attribute does not exists, None
will be returned instead to signal it’s absence.
Example
udfs:
# Defines the udf. name is the actual alias you can call in selector expressions.
- name: hass_state
plugin: pnp.plugins.udf.hass.State
args:
url: http://localhost:8123
token: !env HA_TOKEN
tasks:
- name: hass_state
pull:
plugin: pnp.plugins.pull.simple.Repeat
args:
repeat: "Hello World" # Repeats 'Hello World'
interval: 1s # Every second
push:
- plugin: pnp.plugins.push.simple.Echo
# Will only print the data when attribute azimuth of the sun component is above 200
selector: "'azimuth is greater than 200' if hass_state('sun.sun', attribute='azimuth') > 200.0 else SUPPRESS"
- plugin: pnp.plugins.push.simple.Echo
# Will only print the data when the state of the sun component is above 'above_horizon'
selector: "'above_horizon' if hass_state('sun.sun') == 'above_horizon' else SUPPRESS"
simple.Counter¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.udf.simple.Counter | udf | none | 0.14.0 |
Description
Memories a counter value which is increased everytime you call the udf.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
init | int | yes | 0 | The initialization value of the counter. |
Result
Returns the current counter.
Example
udfs:
# Defines the udf. name is the actual alias you can call in selector expressions.
- name: counter
plugin: pnp.plugins.udf.simple.Counter
args:
tasks:
- name: countme
pull:
plugin: pnp.plugins.pull.simple.Repeat
args:
repeat: "Hello World" # Repeats 'Hello World'
interval: 1s # Every second
push:
- plugin: pnp.plugins.push.simple.Echo
selector:
data: "lambda data: data"
count: "lambda data: counter()" # Calls the udf
simple.FormatSize¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.udf.simple.FormatSize | udf | none | 0.14.0 |
Description
Returns the size of a file (or whatever) as a human readable size (e.g. bytes, KB, MB, GB, TB, PB). The input is expected to be at byte scale.
Call Arguments
name | type | opt. | default | description |
---|---|---|---|---|
size_in_bytes | int|float | no | n/a | The size in bytes to format to a human readable format. |
Result
Returns the argument in a human readable size format.
Example
udfs:
# Defines the udf. name is the actual alias you can call in selector expressions.
- name: fsize
plugin: pnp.plugins.udf.simple.FormatSize
args:
tasks:
- name: format_size
pull:
plugin: pnp.plugins.pull.fs.Size
args:
instant_run: true
interval: 5s
fail_on_error: false
paths:
logs: /var/log # directory - recursively determines size
copy: /bin/cp # file
push:
plugin: pnp.plugins.push.simple.Nop
selector: "[(k ,v) for k, v in data.items()]" # Transform the dictionary into a list
deps:
plugin: pnp.plugins.push.simple.Echo
unwrap: true # Call the push for each individual item in the list
selector:
object: "lambda d: d[0]"
data: "lambda d: fsize(d[1])"
simple.Memory¶
plugin | type | extra | version |
---|---|---|---|
pnp.plugins.udf.simple.Memory | udf | none | 0.14.0 |
Description
Returns a previously memorized value when called.
Arguments
name | type | opt. | default | description |
---|---|---|---|---|
init | Any | yes | None | The initial memory of the plugin. When not set initially the first call will return the value of new_memory , if specified; otherwise None . |
Call Arguments
name | type | opt. | default | description |
---|---|---|---|---|
new_memory | Any | no | None | After emitting the current memorized value the current memory is overwritten by this value. Will only be overwritten if the parameter is specified. |
Result
Returns the memorized value.
Example
udfs:
- name: mem
plugin: pnp.plugins.udf.simple.Memory
args:
tasks:
- name: countme
pull:
plugin: pnp.plugins.pull.simple.Count
args:
from_cnt: 1
interval: 1s # Every second
push:
- plugin: pnp.plugins.push.simple.Echo
# Will memorize every uneven count
selector: "mem() if data % 2 == 0 else mem(new_memory=data)"
udfs:
- name: mem
plugin: pnp.plugins.udf.simple.Memory
args:
tasks:
- name: countme
pull:
plugin: pnp.plugins.pull.simple.Count
args:
from_cnt: 1
interval: 1s # Every second
push:
- plugin: pnp.plugins.push.simple.Echo
# Will memorize every uneven count
selector: "mem() if data % 2 == 0 else mem(new_memory=data)"
Appendix¶
Fitbit Authentication¶
To request data from the fitbit account it is necessary to create an app. Go to dev.fitbit.com.
Under Manage
go to Register an App
.
For the application website and organization website, name it anything starting with http://
or https://
.
Secondly, make sure the OAuth 2.0 Application Type is Personal
.
Lastly, make sure the Callback URL is http://127.0.0.1:8080/
in order to get our Fitbit API to connect properly.
After that, click on the agreement box and submit. You will be redirected to a page that contains your Client ID
and
your Client Secret
.
Next we need to acquire your initial access
- and refresh
-token.
git clone https://github.com/orcasgit/python-fitbit.git
cd python-fitbit
python3 -m venv venv
source venv/bin/activate
pip install -r dev.txt
./gather_keys_oauth2.py <client_id> <client_secret>
You will be redirected to your browser and asked to login to your fitbit account. Next you can restrict the app to
certain data. If everything is fine, your console window should print your access
- and refresh
-token and also
expires_at
.
Put your client_id
, client_secret
, access_token
, refresh_token
and expires_at
to a yaml file and use this
file-path as the config
argument of this plugin. Please see the example below:
access_token: <access_token>
client_id: <client_id>
client_secret: <client_secret>
expires_at: <expires_at>
refresh_token: <refresh_token>
That’s it. If your token expires it will be refreshed automatically by the plugin.