Skip to content

Latest commit

 

History

History
267 lines (190 loc) · 7.28 KB

File metadata and controls

267 lines (190 loc) · 7.28 KB

Chipi-API

Chipi-API adds an optional REST/JSON layer on top of the Chipi home-automation framework. It lets you read and update item values via HTTP; every request is authorised with scope-based API keys.

Licence: Apache 2.0 (see root‐level LICENSE).


Prerequisites

  • SBCL ≥ 2.x (or any other supported Common Lisp implementation)
  • Quicklisp (or OCICL)
  • ASDF systems chipi and chipi-api
;; Quicklisp
(ql:quickload :chipi-api)
;; OCICL
(asdf:load-system :chipi-api)

Quick start (REPL)

(hab:defconfig "chipi"
  ;; 1 – initialises runtime, actor system, timers …
  ;; 2 – Chipi-API specific environment
  (api-env:init
    :apikey-store (apikey-store:make-simple-file-backend) ; or *memory-backend* for testing
    :apikey-lifetime (ltd:duration :day 100))

  ;; 3 – HTTP server on port 8765
  (api:start))

For a complete, runnable example have a look at example-web.lisp in the project root.

Example items

(defitem 'lamp  "Living-room lamp" 'boolean :initial-value 'item:false)
(defitem 'temp  "Temperature"      'float   :initial-value 21.5)

Read-only items

Items can be marked as read-only for external systems by setting the :ext-readonly tag:

(defitem 'sensor "Temperature sensor" 'float 
  :initial-value 20.0
  :tags '((:ext-readonly . t)))

Read-only items can still be read via the REST API but cannot be updated through external calls. The tag value should be the Lisp boolean t.

Note: This is a convention that should be respected by the UI and external systems. The server does not enforce read-only restrictions - it's up to client implementations to check for the :ext-readonly tag and prevent updates accordingly.


Creating an API key

(defparameter *my-key*
  (apikey-store:create-apikey :access-rights '(:read :update)))

API key persistence

(api-env:init
  :apikey-store (apikey-store:make-simple-file-backend))

The file backend stores keys in runtime/apikeys.


REST interface

A full OpenAPI 3.0 specification lives in chipi-api.yaml.

Besides individual items the API also exposes itemgroups, logical containers that hold multiple items.

Endpoint Method Required scope
/items GET read
/items/{itemName} GET read
/items/{itemName} POST update
/itemgroups GET read
/itemgroups/{groupName} GET read
/events/items GET read

Add header X-Api-Key: <your-key> to every call.

Examples (curl)

# List all items
curl -H "X-Api-Key: $MY_KEY" http://localhost:8765/items

# List all itemgroups
curl -H "X-Api-Key: $MY_KEY" http://localhost:8765/itemgroups

# Get single item
curl -H "X-Api-Key: $MY_KEY" http://localhost:8765/items/lamp

# Get single itemgroup
curl -H "X-Api-Key: $MY_KEY" http://localhost:8765/itemgroups/living

# Update item
curl -X POST -H "X-Api-Key: $MY_KEY" -H "Content-Type: application/json" \
     -d '{"value": true}' http://localhost:8765/items/lamp

Server-Sent Events (SSE)

The API provides real-time item updates via Server-Sent Events:

# Connect to item events stream
curl -H "Accept: text/event-stream" \
     "http://localhost:8765/events/items?apikey=$MY_KEY"

The SSE endpoint sends:

  • Connection confirmation messages
  • Real-time item change notifications with full item data
  • Periodic heartbeat messages to keep the connection alive

Event format follows the SSE standard with data: fields containing JSON payloads.

Event Types

Connection Event:

{"event":{"type":"connection","message":"Connected to item events"}}

Item Change Event:

{"event":{"type":"item-change","item":{"name":"lamp","label":"Living-room lamp","type-hint":"boolean","tags":{},"item-state":{"value":true,"timestamp":1703123456}}}}

Heartbeat Event:

{"event":{"type":"heartbeat","timestamp":1703123456}}

Item Change Payload Structure

Field Type Description
event.type string Always "item-change"
event.item.name string Item identifier
event.item.label string Human-readable item name
event.item.type-hint string Data type (boolean, float, integer, string)
event.item.tags object Item metadata (including :ext-readonly flag)
event.item.item-state.value any Current item value
event.item.item-state.timestamp number Common Lisp universal-time timestamp (seconds since 1900-01-01)

Timestamp Format

Timestamps are provided as Common Lisp universal-time values (seconds since January 1st, 1900, 00:00:00 GMT). To convert to Unix timestamp (seconds since 1970-01-01), subtract 2208988800:

// Convert CL universal-time to Unix timestamp
const unixTimestamp = universalTime - 2208988800;
const date = new Date(unixTimestamp * 1000);
# Convert in shell (example with timestamp 3913123456)
echo $((3913123456 - 2208988800))  # Result: 1704134656 (Unix timestamp)

Scope model

Scope Meaning
read read-only
update push new values
delete reserved
admin full access

The highest requested scope must not exceed the highest scope granted to the API key.


Shutting down

(api:stop)     ; stop only the web API
(hab:shutdown) ; stop the entire Chipi instance

Chipi UI

Chipi UI is a responsive UI, based on CLOG and Bootstrap 5 with realtime updates of values.

Example of setup:

(hab:defconfig "chipi"
  ;; 1 – initialises runtime, actor system, timers …
  ;; 2 – Chipi-API specific environment
  (api-env:init
    :apikey-store (apikey-store:make-simple-file-backend) ; or *memory-backend* for testing
    :apikey-lifetime (ltd:duration :day 100))

  ;; 3 – HTTP server on port 8765
  ;;(api:start)
  
  ;; 4 - UI
  ;; API server is not needed for UI, but api-env is (eventually, because we want API key security)
  ;; ATM it's WIP and doesn't need that either
  (ui:start :host "your-host" :port <your-port>)
  )

UI Tags

Items and itemgroups support special tags that control how they are rendered in the UI.

Item tags

Tag Value Description
:ui-type string Overrides the default type badge label (e.g. "Light" instead of "Switch")
:ui-readonly t Boolean items render as plain "ON"/"OFF" text instead of an interactive toggle
;; Custom type label
(defitem 'switch1 "Switch1" 'boolean :initial-value 'item:false
  :tags '((:ui-type . "Light")))

;; Read-only boolean displayed as ON/OFF text
(defitem 'motion-sensor "Motion Sensor" 'boolean :initial-value 'item:true
  :tags '((:ui-readonly . t)))

Itemgroup tags

Tag Value Description
:ui-link Renders the itemgroup as a clickable navigation link instead of an inline card
;; Renders as a navigation link that opens a detail page
(defitemgroup 'lights "Lights" :tags '((:ui-link)))

Screenshot

Screenshot

This is mostly WIP. Still under investigation if this is a practical or not.