Arete Control Plane for Smart Buildings

This example demonstrates use of two Raspberry Pis to simulate two devices within a smart building that make use of the Arete Control Plane to coordinate desired state and actual state.

It reproduces the Arete SDK example called "the switch and the light", by restructuring its flow as Controller control loops and Lambdas.

The Switch and The Light

On a 1st node, the On Prem Agent runs "the switch", which consists of:

  • a Controller that subscribes to GPIO 04 edge events, and
  • a Lambda that translates those events into a new desired state, and publishes it to the Arete control plane.

On a 2nd node, the On Prem Agent runs "the light", which consists of:

  • a Controller that subscribes to Arete control plane events containing desired state changes, and
  • a Lambda that responds to those desired state change events, and realizes them by setting a GPIO 23 pin, and finally communicating the new actual state back to the Arete Control Plane.

Initial Provisioning

graph LR;

cli --> control_plane;
console --> control_plane;
control_plane <-- tunnel --> agent_1;
control_plane <-- tunnel --> agent_2;

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_1[Device Edge]
agent_1[Agent 1];
agent_2[Agent 2];
end

Subsequent Autonomous Edge Operation

graph TB;

switch --> arete_control_plane;
arete_control_plane --> light;

subgraph device_edge[Device Edge]
agent_1;
agent_2;
arete_control_plane;
end

subgraph agent_1[Agent 1]
gpio_04_pin[GPIO 04 pin];
switch[Switch];
gpio_04_pin -- edge trigger --> switch;
end

subgraph agent_2[Agent 2]
gpio_23_pin[GPIO 23 pin];
light[Light];
light --> gpio_23_pin;
end

arete_control_plane[(Arete Control Plane)];

Part 1: The Switch

Define the Controller to detect the GPIO edge triggers

$ onprem generate xid
cj7ca3berad6gieb3rbg
# arete_switch_gpio_controller.yaml
id: cj7ca3berad6gieb3rbg
kind: Controller
name: arete_switch_gpio_controller
description: >
  Detect GPIO edge events, and emit them to a downstream lambda.
scriptContentType: Lua
script: >
  local GPIO = require('periphery.GPIO')

  local M = {}

  function M.init(context)
    -- Configure GPIO 04 pin for input
    local params = {
      path      = '/dev/gpiochip0',
      line      = 4,
      direction = 'in',
      edge      = 'both',
    }
    local pin = GPIO(params)
    context['pin'] = pin
  end

  function M.run(context)
    local pin = context.pin
    while true do
      local event = pin:read_event()
      coroutine.yield(event)
    end
  end

  return M
$ onprem apply ./arete_switch_gpio_controller.yaml

Define the Lambda that will receive the GPIO events, and transmit to Arete

$ onprem generate xid
d322bqpcaq0mr0n3a2bg
# arete_switch.yaml
id: d322bqpcaq0mr0n3a2bg
kind: Lambda
name: arete_switch
description: >
  Respond to GPIO edge events, and transmit as new desired state to the Arete control plane.
triggerId: cj7ca3berad6gieb3rbg
runAt:
  device:
    deviceId: d6mclbipqhcfg04mv0l0 # Raspberry Pi #1
scriptContentType: Lua
script: >
  local NODE_ID = 'ozr9fZbU8i7hMdjEjuTS2o'
  local NODE_NAME = 'On Prem Arete Switch'

  local CONTEXT_ID = 'uRLoYsXEY7nsbs9fRdjM8A'
  local CONTEXT_NAME = 'Building 23, Office 41-B'

  local PADI_LIGHT_PROFILE = 'padi.light'

  local M = {}

  function M.handler(event, context)
    -- Configure Arete context, the first time
    if context.areteContext == nil then
      context['areteContext'] = M.newAreteContext()
    end
    local areteContext = context.areteContext
  
    -- Transmit new desired state to Arete control plane
    local desiredState = event.edge == 'falling' and '1' or '0'
    local provider = areteContext:provider(PADI_LIGHT_PROFILE)
    provider:put('sOut', desiredState)
  end
  
  function M.newAreteContext()
    local arete = require('arete-sdk')
    local areteClient = arete.Client:new('wss://dashboard.test.cns.dev:443')
    areteClient:waitForOpen()
    local system = areteClient:system()
    local node = system:node(NODE_ID, NODE_NAME, false)
    local context = node:context(CONTEXT_ID, CONTEXT_NAME)
    return context
  end
  
  return M
$ onprem apply ./arete_switch.yaml

Part 2: The Light

Define the Controller that will detect changes to desired state

$ onprem generate xid
cj7ca3berad6gieb3rbg
# arete_light_controller.yaml
id: cj7ca3berad6gieb3rbg
kind: Controller
name: arete_light_controller
description: >
  Subscribe to the Arete control plane for the desired state of a light.
scriptContentType: Lua
script: >
  local PADI_LIGHT_PROFILE = 'padi.light'

  local M = {}

  function M.init(context)
    context['areteContext'] = M.newAreteContext()
  end

  function M.run(context)
    local areteContext = context.areteContext
    local consumer = areteContext:consumer(PADI_LIGHT_PROFILE)
    for event, abort in consumer:watch() do
      coroutine.yield(event)
    end
  end

  function M.newAreteContext()
    local arete = require('arete-sdk')

    local NODE_ID = 'onqXVczGoymQkFc3UN6qcM'
    local NODE_NAME = 'On Prem Arete Light'

    local CONTEXT_ID = 'uRLoYsXEY7nsbs9fRdjM8A'
    local CONTEXT_NAME = 'Building 23, Office 41-B'

    local areteClient = arete.Client:new('wss://dashboard.test.cns.dev:443')
    areteClient:waitForOpen()
    local system = areteClient:system()
    local node = system:node(NODE_ID, NODE_NAME)
    local context = node:context(CONTEXT_ID, CONTEXT_NAME)
    return context
  end

  return M
$ onprem apply ./arete_light_controller.yaml

Define the Lambda that will realize the desired state by turning on a light

$ onprem generate xid
d322gs1caq0mvt65gb4g
# arete_light.yaml
id: d322gs1caq0mvt65gb4g
kind: Lambda
name: arete_light
description: >
  Turn on a light.
triggerId: cj7ca3berad6gieb3rbg
runAt:
  device:
    deviceId: d6mclhapqhcfg5ammj1g # Raspberry Pi #2
scriptContentType: Lua
script: >
  local GPIO = require('periphery.GPIO')

  local M = {}

  function M.handler(event, context)
    -- Configure GPIO 23 pin for output, the first time
    if context.pin == nil then
      local params = {
        path      = '/dev/gpiochip0',
        line      = 23,
        direction = 'out',
      }
      local pin = GPIO(params)
      context['pin'] = pin
    end
    local pin = context.pin
  
    -- Realize the desired state
    local desiredState = event.sState == '1'
    pin:write(desiredState)
  end

  return M
$ onprem apply ./arete_light.yaml

©2026 Megalithic LLC | Website | GitLab | GitLab (Megalithic)