Alerts & Webhook

flowchart TD
    A[Sensor Read] --> B[EventBus publish]
    B --> C[rules.lua callback]
    C --> D{Threshold crossed?}
    D -->|No| E[Clear sustain counter]
    D -->|Yes| F[Increment sustain]
    F --> G{N readings OR T minutes?}
    G -->|Not yet| H[Wait for next read]
    G -->|Yes| I{Cooldown expired?}
    I -->|No| H
    I -->|Yes| J[notify]
    J --> K[Log.warn]
    J --> L[Telegram.broadcast]
    J --> M[MQTT publish alert]

Lua Alert Engine

Alert logic lives in /scripts/rules.lua on LittleFS - hot-reloadable without recompiling. The firmware provides the bindings; the script defines the rules.

Available Lua bindings for alerts:

Function Description
EventBus.subscribe(event, fn) Subscribe to sensor events (temperature, current, battery)
Telegram.send(chat_id, msg) Send to a specific Telegram chat
Telegram.broadcast(msg) Send to all configured chat_ids
MQTT.publish(topic, payload) Publish alert to MQTT
Config.get(key) Read config values (supports dot notation and array indices)
Node.uptime() Current uptime in ms (for cooldown/sustain timing)
Node.setTimeout(ms, fn) Delayed execution (e.g. boot alerts after WiFi ready)
Log.warn(msg) Log alert to serial/WebSocket terminal

Telegram config (config.json):

"telegram": {
  "bot_token": "your-bot-token",
  "chat_ids": ["12345678910", "-10987654321"]
}

Both array and object format supported for chat_ids:

"chat_ids": {"user1": "12345678910", "user2": "-10987654321"}

Object format enables per-recipient routing in Lua:

local user1 = Config.get("telegram.chat_ids.user1")
Telegram.send(user1, "critical alert")

Example rules.lua

local prefix = Config.get("mqtt.topic_prefix")
local unit   = Config.get("temperature.unit") or "C"

local sustain = {}
local cooldown = {}
local COOLDOWN_MS = 900000   -- 15 minutes
local TEMP_SUSTAIN = 5       -- 5 readings or 5 minutes

local function can_alert(key)
  local now = Node.uptime()
  if cooldown[key] and (now - cooldown[key]) < COOLDOWN_MS then
    return false
  end
  cooldown[key] = now
  return true
end

local function notify(msg)
  Log.warn(msg)
  Telegram.broadcast(msg)
  MQTT.publish(prefix .. "/alert", msg)
end

-- Temperature: sustained low temp alert
EventBus.subscribe("temperature", function(data)
  for _, s in ipairs(data.sensors) do
    local key = "low_temp:" .. s.name
    if s.name == "House Supply" and s.temp_c <= 55 then
      sustain[key] = (sustain[key] or 0) + 1
      if sustain[key] >= TEMP_SUSTAIN and can_alert(key) then
        notify(s.name .. ": " .. s.temp .. unit .. " - Low temp")
        sustain[key] = 0
      end
    else
      sustain[key] = 0
    end
  end
end)

-- Battery: immediate alert
EventBus.subscribe("battery", function(data)
  if data.present and data.percent <= 20 and not data.charging then
    if can_alert("battery_low") then
      notify("Battery low: " .. data.percent .. "%")
    end
  end
end)

EventBus data format (temperature):

{
  "sensors": [
    { "name": "House Supply", "temp_c": 54.4, "temp": 129.9, "address": "28..." }
  ]
}
  • temp_c - always Celsius (use for thresholds)
  • temp - display unit (C or F depending on temperature.unit config)

Sustain pattern: alert only fires after N consecutive readings cross the threshold. Prevents false alerts from single sensor glitches or brief temperature dips when loading fuel.

Cooldown pattern: after an alert fires, the same key won’t fire again for COOLDOWN_MS milliseconds. Prevents alert spam when a condition persists.


Webhook

Optional HTTP POST fired from Lua or TelegramModule on every alert:

"webhook": {
  "url": "http://homeassistant.local:8123/api/webhook/module-alert",
  "message_template": "{{value}}"
}

{{value}} is replaced with the full alert message. Supports http:// and https://. Leave url empty to disable.

HTTPS webhook endpoints are unverified by default (the URL is arbitrary, so there is no CA to pin ahead of time). To verify TLS, upload the endpoint’s root certificate - self-signed works - as /webhook-ca.crt via the file API, then restart:

curl -u admin:<password> -X POST \
  'http://[ip]/api/file?path=/webhook-ca.crt&source=littlefs' \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @webhook-ca.crt

Same override pattern as /telegram-ca.crt and /ca.crt; remove the file (and restart) to fall back to unverified. The boot log states which mode is active.


Boot alert (main.lua)

Send a Telegram message when the node starts (delayed 10s for WiFi/firewall):

Node.setTimeout(10000, function()
  local name = Config.get("device.name") or "unknown"
  local ip = Node.ip() or "no IP"
  Telegram.broadcast(name .. " booted - " .. ip)
end)

Home Assistant automation for MQTT alerts

automation:
  - alias: "Thesada Node Alert - Telegram"
    triggers:
      - trigger: mqtt
        topic: thesada/node/alert
    actions:
      - action: notify.send_message
        data:
          entity_id: notify.telegram_notify
          message: "{{ trigger.payload }}"

Thesada - AGPL-3.0-only (app) / GPL-3.0-only (firmware) / CC BY-NC-SA 4.0 (docs) - License

This site uses Just the Docs, a documentation theme for Jekyll.