GPIO Edge Trigger

This example demonstrates defining a custom Lambda Trigger that subscribes to GPIO edge events via the Linux kernel. It then demonstrates various Lambdas that respond to it and perform various functions.

Initial Provisioning

graph LR;

cli --> control_plane;
console --> control_plane;
control_plane <-- tunnel --> agent;

subgraph user_edge[User Edge]
cli[CLI];
console[Console];
end

subgraph cloud[Cloud <small>api.on-prem.net</small>]
control_plane[Control Plane];
end

subgraph device_edge[Device Edge]
agent[Agent];
end

Subsequent Autonomous Edge Operation

graph TB;

agent --> trigger[Lambda Trigger];
pin -- edge trigger --> trigger;
trigger --> lambda1;
trigger --> lambda2;
trigger --> lambda3;

subgraph device_edge[Device Edge]
agent;
pin[GPIO Pin];
end

subgraph agent[Agent]
trigger;
lambda1[Lambda 1];
lambda2[Lambda 2];
lambda3[Lambda 3];
end

Define the lambda trigger

$ onprem generate xid
cj7ca3berad6gieb3rbg
# my_gpio_trigger_type.yaml
id: cj7ca3berad6gieb3rbg
kind: LambdaTriggerType
name: gpio_trigger
description: >
  Trigger lambdas when a GPIO edge event occurs.
runsAtControlPlane: false
runsAtDevices: true
scriptContentType: text/x-lua
script: >
  local GPIO = require('periphery.GPIO')
  local socket = require('socket')

  local M = {}

  function M.init(context)
    local params = {
      path      = '/dev/gpiochip0',
      line      = 23,
      direction = 'in',
      edge      = 'both',
    }
    local gpio = GPIO(params)
    context['gpio'] = gpio
  end

  function M.run(context)
    local gpio = context.gpio
    while true do
      local event = gpio:read_event()
      coroutine.yield(event)
      socket.sleep(0.005)
    end
  end

  return M

The sleep used above is precautionary but unnecessary when performing a blocking call such as read_event(). Each Lambda Trigger Type loop runs in a dedicated thread, and run loops are free to peg the CPU of a single core if they want.

Upload it to the control plane

$ onprem apply ./my_gpio_trigger_type.yaml

It will now show up in the cloud console.

Cloud Console

And it will also now show up as one of the trigger choices when editing a Lambda.

Editing a Lambda

Lambda Example 1: Configure an LED to follow the GPIO pin

$ onprem generate xid
cj7co6jerad78frcc100
# follow_gpio23_with_led0.yaml
id: cj7co6jerad78frcc100
kind: Lambda
name: follow_gpio_with_led0
description: >
  Follow GPIO pin 23 and display with led0.
runAt:
  deviceId: ci2fabp32ckvhk1g9qe0
triggerTypeId: cj7ca3berad6gieb3rbg
scriptContentType: text/x-lua
script: >
  local LED = require('periphery.LED')
  local led = LED('led0')
  
  local M={}
  function M.handler(event, context)
    local newValue = false
    if event.edge == 'rising' then
      newValue = true
    end
    led:write(newValue)
    return {edge=event.edge, timestamp=event.timestamp}
  end
  return M
$ onprem apply ./follow_gpio23_with_led0.yaml

Lambda Example 2: Aggregate the GPIO events in Redis

$ onprem generate xid
clv0p4u56a1fjkem7h9g
# follow_gpio23_and_aggregate_in_redis.yaml
id: clv0p4u56a1fjkem7h9g
kind: Lambda
name: follow_gpio23_and_aggregate_in_redis
description: >
  Follow GPIO pin 23 and aggregate events in Redis.
runAt:
  deviceId: ci2fabp32ckvhk1g9qe0
triggerTypeId: cj7ca3berad6gieb3rbg
scriptContentType: text/x-lua
script: >
  local redis = require('redis')
  local redisClient = redis.connect('my-redis', 6379)
  assert(redisClient:ping())
  
  local M={}
  
  function M.handler(event, context)
    redisClient:pipeline(function(pipeline)
      -- Count total events for all time
      pipeline:incrby('event_count', 1)

      -- Also count events per day
      pipeline:incrby('event_count_' .. os.date('%Y-%m-%d'), 1)

      -- Also count events per hour
      pipeline:incrby('event_count_' .. os.date('%Y-%m-%dT%H'), 1)
    end)
    return {edge=event.edge, timestamp=event.timestamp}
  end
  
  return M
$ onprem apply follow_gpio23_and_aggregate_in_redis.yaml