Examples

Compiles a set of example configs to inspire you what you can achieve by using pnp.

Backup to dropbox

# Every sunday at 1am a backup of the specified directory is created
# and stored at the dropbox service.

tasks:
  - name: cron_backup
    pull:
      plugin: pnp.plugins.pull.simple.Cron
      args:
        expressions:
          - "0 1 * * SUN /tmp"
    push:
      plugin: pnp.plugins.push.fs.Zipper
      deps:
        - plugin: pnp.plugins.push.storage.Dropbox
          args:
            api_key: !env DROPBOX_API_KEY
            create_shared_link: false
          selector:
            data: "lambda payload: payload"
            target_file_name: "lambda payload: '{}_{}'.format(str(now()), basename(payload))"

Expose internet speed to home assistant

# We use the api to test our speed when we want
#   curl -X POST "http://localhost:80/trigger?task=speedtest" -H "accept: application/json"
api:
  port: 80
tasks:
  - name: speedtest
    pull:
      plugin: pnp.plugins.pull.net.Speedtest
      args:
        interval: "0 6 * * *"  # Run every morning at 6 am
    push:
      - plugin: pnp.plugins.push.mqtt.Discovery
        selector: "data.get('download_speed_mbps')"
        args:
          host: localhost
          discovery_prefix: homeassistant
          component: sensor
          object_id: speedtest_download
          config:
            name: "{{var::object_id}}"
            icon: "mdi:cloud-download-outline"
            state_topic: "{{var::state_topic}}"
            unit_of_measurement: "Mbps"
      - plugin: pnp.plugins.push.mqtt.Discovery
        selector: "data.get('upload_speed_mbps')"
        args:
          host: localhost
          discovery_prefix: homeassistant
          component: sensor
          object_id: speedtest_upload
          config:
            name: "{{var::object_id}}"
            icon: "mdi:cloud-upload-outline"
            state_topic: "{{var::state_topic}}"
            unit_of_measurement: "Mbps"
      - plugin: pnp.plugins.push.mqtt.Discovery
        selector: "data.get('ping_latency')"
        args:
          host: localhost
          discovery_prefix: homeassistant
          component: sensor
          object_id: speedtest_ping
          config:
            name: "{{var::object_id}}"
            icon: "mdi:lan-pending"
            state_topic: "{{var::state_topic}}"
            unit_of_measurement: "ms"
      - plugin: pnp.plugins.push.mqtt.Discovery
        selector:
          data: "lambda data: data.get('server').get('name')"
          attributes:
            isp: "lambda data: data.get('client').get('isp')"
            rating: "lambda data: data.get('client').get('rating')"
            host: "lambda data: data.get('server').get('host')"
            result_image: "lambda data: data.get('result_image')"
        args:
          host: localhost
          discovery_prefix: homeassistant
          component: sensor
          object_id: speedtest_host
          config:
            name: "{{var::object_id}}"
            icon: "mdi:cloud-sync-outline"
            state_topic: "{{var::state_topic}}"
            json_attributes_topic: "{{var::json_attributes_topic}}"

Fitbit to home assistant

# Polls your fitbit account for the step count and devices every 5 minutes
# and publishes those metrics to home assistant via mqtt discovery.
#    https://www.home-assistant.io/docs/mqtt/discovery/
#
# Please point your environment variable `FITBIT_AUTH` to your authentication
# configuration file.

- name: fitbit_steps
  pull:
    plugin: pnp.plugins.pull.fitbit.Current
    args:
      config: !env FITBIT_AUTH
      instant_run: true  # Run as soon as pnp starts
      interval: 5m
      resources:
        - activities/steps
  push:
    # Adds a sensor.fitbit_steps to home assistant
    - 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: fitbit_devices_battery
  pull:
    plugin: pnp.plugins.pull.fitbit.Devices
    args:
      config: !env FITBIT_AUTH
      instant_run: true
      interval: 5m
  push:
    # Adds sensor.fb_<device>_battery for each device attached to your fitbit account
    - plugin: pnp.plugins.push.mqtt.Discovery
      selector:
        data: "lambda data: data.get('battery_level')"
        object_id: "lambda data: 'fb_{}_battery'.format(data.get('device_version', '').replace(' ', '_').lower())"
      unwrap: true
      args:
        host: localhost
        discovery_prefix: homeassistant
        component: sensor
        config:
          name: "{{var::object_id}}"
          device_class: "battery"
          unit_of_measurement: "%"
    # Adds sensor.fb_<device>_lastsync for each device attached to your fitbit account
    - plugin: pnp.plugins.push.mqtt.Discovery
      selector:
        data: "lambda data: data.get('last_sync_time')"
        object_id: "lambda data: 'fb_{}_lastsync'.format(data.get('device_version', '').replace(' ', '_').lower())"
      unwrap: true
      args:
        host: localhost
        discovery_prefix: homeassistant
        component: sensor
        config:
          name: "{{var::object_id}}"

Image face recognition

# Watches the directory '/tmp/camera' for file changes on image files and
# publishes them to a message queue (base64 encoded).
#
# Then the image data is pulled from the queue and a face recognition
# is executed. The result is a tagged file (known and unknown persons) which
# will be dumped to the specified directory.
#

- name: image_watcher
  pull:
    plugin: pnp.plugins.pull.fs.FileSystemWatcher
    args:
      path: "/tmp/camera"
      events: [created]
      ignore_directories: true
      patterns: ['*.jpeg', '*.jpg', '*.png']
      load_file: true
      base64: true

  push:
    plugin: pnp.plugins.push.mqtt.Publish
    selector: payload.file.content
    args:
      host: localhost
      topic: camera/images

- name: image_processor
  pull:
    plugin: pnp.plugins.pull.mqtt.Subscribe
    args:
      host: localhost
      topic: camera/images
  push:
    plugin: pnp.plugins.push.ml.FaceR
    selector: b64decode(payload.payload)
    args:
      known_faces_dir: '/tmp/faces'
      lazy: true
    deps:
      - plugin: pnp.plugins.push.simple.Echo
        selector: (payload['known_faces'], payload['no_of_faces'])
      - plugin: pnp.plugins.push.fs.FileDump
        selector: payload['tagged_image']
        args:
          directory: '.'
          extension: '.png'

Miflora to home assistant

# Polls a miflora device at :20 and publishes its reading
# to home assistant via mqtt discovery.
#    https://www.home-assistant.io/docs/mqtt/discovery/

- 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: false
      interval: '20 * * * *'
  push:
    - plugin: pnp.plugins.push.simple.Nop
      selector: "data if data.get('conductivity') else SUPPRESS"
      deps:
        - plugin: pnp.plugins.push.mqtt.Discovery
          selector:
            data: "lambda data: data.get('conductivity')"
            object_id: "miflora_conductivity"
          args:
            host: localhost
            discovery_prefix: homeassistant
            component: sensor
            config:
              name: "{{var::object_id}}"
              unit_of_measurement: "µS/cm"
              icon: mdi:flash-circle
              friendly_name: Conductivity
    - plugin: pnp.plugins.push.simple.Nop
      selector: "data if data.get('light') else SUPPRESS"
      deps:
        - plugin: pnp.plugins.push.mqtt.Discovery
          selector:
            data: "lambda data: data.get('light')"
            object_id: "miflora_light_intensity"
          args:
            host: localhost
            discovery_prefix: homeassistant
            component: sensor
            config:
              name: "{{var::object_id}}"
              unit_of_measurement: "lx"
              icon: mdi:white-balance-sunny
              friendly_name: Light intensity
    - plugin: pnp.plugins.push.simple.Nop
      selector: "data if data.get('temperature') else SUPPRESS"
      deps:
        - plugin: pnp.plugins.push.mqtt.Discovery
          selector:
            data: "lambda data: data.get('temperature')"
            object_id: "miflora_temperature"
          args:
            host: localhost
            discovery_prefix: homeassistant
            component: sensor
            config:
              name: "{{var::object_id}}"
              unit_of_measurement: "°C"
              icon: mdi:thermometer
              friendly_name: Temperature
    - plugin: pnp.plugins.push.simple.Nop
      selector: "data if data.get('battery') else SUPPRESS"
      deps:
        - plugin: pnp.plugins.push.mqtt.Discovery
          selector:
            data: "lambda data: data.get('battery')"
            object_id: "miflora_battery"
          args:
            host: localhost
            discovery_prefix: homeassistant
            component: sensor
            config:
              name: "{{var::object_id}}"
              unit_of_measurement: "%"
              device_class: battery
              friendly_name: Battery
    - plugin: pnp.plugins.push.simple.Nop
      selector: "data if data.get('moisture') else SUPPRESS"
      deps:
        - plugin: pnp.plugins.push.mqtt.Discovery
          selector:
            data: "lambda data: data.get('moisture')"
            object_id: "miflora_moisture"
          args:
            host: localhost
            discovery_prefix: homeassistant
            component: sensor
            config:
              name: "{{var::object_id}}"
              unit_of_measurement: "%"
              icon: mdi:water-percent
              friendly_name: Moisture

Monitoring

# Client component
# Sends statistics about the host system (like cpu usage, ram usage, ...)
# to a mqtt broker every 30 seconds.

tasks:
  - name: stats
    pull:
      plugin: pnp.plugins.pull.monitor.Stats
      args:
        interval: 30s
        instant_run: true
    push:
      plugin: pnp.plugins.push.mqtt.Publish
      args:
        host: !env MQTT_HOST
        topic: devices/my_name/stats
        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
# Server component
# Listens to the mqtt topic where the readings from each client are stored.
# If a new reading arrives it will be send to an influx database
# to save it for later evaluation.

tasks:
  - name: stats_mqtt_pull
    pull:
      plugin: pnp.plugins.pull.mqtt.MQTTPull
      args:
        host: !env MQTT_HOST
        topic: devices/+/stats/#
    push:
      plugin: pnp.plugins.push.timedb.InfluxPush
      selector: "{'data': payload}"
      args:
        host: "localhost"
        port: 8086
        user: "the_user"
        password: "the_password"
        database: "my_db"
        protocol: "{payload.levels[3]},device={payload.levels[1]},domain=stats value={payload.payload}"

Naive dropbox sync

# Every time a file is created or modified in /tmp
# the file is uploaded to dropbox and you are notified
# about it via pushbullet.
#
# You need to set your environment variables properly:
# - DROPBOX_API_KEY
# - SLACK_API_KEY

- name: dropbox_sync_notify
  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
      selector:
        data: "lambda data: data.source"  # Absolute path to file
        target_file_name: "lambda data: basename(data.source)"  # File name only
      deps:
        - plugin: pnp.plugins.push.notify.Slack
          args:
            api_key: !env SLACK_API_KEY  # Your slack api key.
            channel: test  # The channel to post to. Mandatory. Overridable by envelope.
          selector: data.raw_link