Monitoring¶
OpenLatch exposes two monitoring endpoints for external monitoring systems. Both are unauthenticated so monitoring agents can scrape them without credentials.
Endpoints¶
Nagios check: GET /api/v1/monitoring/nuki-battery¶
Returns plain text in Nagios plugin format with performance data.
Query parameters (all optional):
warn— WARNING threshold in percent (default: 30)crit— CRITICAL threshold in percent (default: 15)stale_hours— hours before heartbeat data is considered stale (default: 24)
Response examples:
OK - NUKI battery 87% (3min ago) | battery=87%;30;15;0;100
WARNING - NUKI battery 25% (8min ago) | battery=25%;30;15;0;100
CRITICAL - NUKI battery 10% (2min ago) | battery=10%;30;15;0;100
CRITICAL - NUKI not paired with ESP32
UNKNOWN - No heartbeat data received
UNKNOWN - Last heartbeat 1500min ago (stale > 24h)
The NUKI's own battery_critical flag also triggers CRITICAL, regardless of the percent threshold.
Prometheus metrics: GET /api/v1/monitoring/metrics¶
Returns metrics in Prometheus exposition format (text/plain; version=0.0.4).
Example response:
# HELP openlatch_heartbeat_up Whether a heartbeat has been received (1=yes, 0=no)
# TYPE openlatch_heartbeat_up gauge
openlatch_heartbeat_up 1
# HELP openlatch_heartbeat_age_seconds Seconds since last device heartbeat
# TYPE openlatch_heartbeat_age_seconds gauge
openlatch_heartbeat_age_seconds 42
# HELP openlatch_device_uptime_seconds ESP32 uptime in seconds
# TYPE openlatch_device_uptime_seconds gauge
openlatch_device_uptime_seconds 86400
# HELP openlatch_device_free_heap_bytes ESP32 free heap memory in bytes
# TYPE openlatch_device_free_heap_bytes gauge
openlatch_device_free_heap_bytes 120000
# HELP openlatch_device_wifi_rssi_dbm WiFi signal strength in dBm
# TYPE openlatch_device_wifi_rssi_dbm gauge
openlatch_device_wifi_rssi_dbm -55
# HELP openlatch_nuki_battery_percent NUKI battery level (0-100)
# TYPE openlatch_nuki_battery_percent gauge
openlatch_nuki_battery_percent 87
# HELP openlatch_nuki_battery_critical NUKI critical battery flag (1=critical, 0=ok)
# TYPE openlatch_nuki_battery_critical gauge
openlatch_nuki_battery_critical 0
# HELP openlatch_nuki_paired NUKI pairing status (1=paired, 0=not paired)
# TYPE openlatch_nuki_paired gauge
openlatch_nuki_paired 1
Additionally, if the device sends nuki_daily_report events (3am daily summary), the following metrics are included:
# HELP openlatch_nuki_daily_unlatch_count NUKI unlatch actions in last daily report
# TYPE openlatch_nuki_daily_unlatch_count gauge
openlatch_nuki_daily_unlatch_count 12
# HELP openlatch_nuki_daily_lock_count NUKI lock actions in last daily report
# TYPE openlatch_nuki_daily_lock_count gauge
openlatch_nuki_daily_lock_count 8
# HELP openlatch_nuki_daily_total_actions Total NUKI actions in last daily report
# TYPE openlatch_nuki_daily_total_actions gauge
openlatch_nuki_daily_total_actions 20
Metrics are only included when the device reports them. If no NUKI is configured, NUKI metrics are absent. If no heartbeat has been received yet, only openlatch_heartbeat_up 0 is returned.
Check plugin script¶
All Nagios-compatible systems (Nagios, Icinga2, Checkmk, Zabbix external checks) use this plugin. Save as /usr/lib/nagios/plugins/check_openlatch_nuki (or your PluginDir):
#!/bin/bash
# check_openlatch_nuki — Nagios/Icinga plugin for OpenLatch NUKI battery
# Usage: check_openlatch_nuki -H <host> [-w <warn>] [-c <crit>] [-s <stale_hours>]
HOST=""
WARN=30
CRIT=15
STALE=24
while getopts "H:w:c:s:" opt; do
case $opt in
H) HOST="$OPTARG" ;;
w) WARN="$OPTARG" ;;
c) CRIT="$OPTARG" ;;
s) STALE="$OPTARG" ;;
*) echo "UNKNOWN - Invalid arguments"; exit 3 ;;
esac
done
if [ -z "$HOST" ]; then
echo "UNKNOWN - Missing -H <host>"
exit 3
fi
URL="$HOST/api/v1/monitoring/nuki-battery?warn=$WARN&crit=$CRIT&stale_hours=$STALE"
OUTPUT=$(curl -sf --max-time 10 "$URL" 2>/dev/null)
if [ $? -ne 0 ]; then
echo "UNKNOWN - Failed to reach $HOST"
exit 3
fi
echo "$OUTPUT"
case "$OUTPUT" in
OK*) exit 0 ;;
WARNING*) exit 1 ;;
CRITICAL*) exit 2 ;;
*) exit 3 ;;
esac
Icinga2¶
// /etc/icinga2/conf.d/openlatch.conf
object CheckCommand "openlatch_nuki" {
command = [ PluginDir + "/check_openlatch_nuki" ]
arguments = {
"-H" = "$openlatch_address$"
"-w" = "$openlatch_nuki_warn$"
"-c" = "$openlatch_nuki_crit$"
"-s" = "$openlatch_nuki_stale$"
}
vars.openlatch_nuki_warn = 30
vars.openlatch_nuki_crit = 15
vars.openlatch_nuki_stale = 24
}
object Host "openlatch" {
import "generic-host"
address = "openlatch.example.com"
vars.openlatch_address = "https://openlatch.example.com"
}
object Service "nuki-battery" {
import "generic-service"
host_name = "openlatch"
check_command = "openlatch_nuki"
check_interval = 15m
retry_interval = 5m
// Override thresholds per-service if needed:
// vars.openlatch_nuki_warn = 25
// vars.openlatch_nuki_crit = 10
}
Restart Icinga2:
Nagios¶
# /etc/nagios/objects/openlatch.cfg
define command {
command_name check_openlatch_nuki
command_line $USER1$/check_openlatch_nuki -H $ARG1$ -w $ARG2$ -c $ARG3$
}
define host {
use linux-server
host_name openlatch
alias OpenLatch Door Controller
address openlatch.example.com
}
define service {
use generic-service
host_name openlatch
service_description NUKI Battery
check_command check_openlatch_nuki!https://openlatch.example.com!30!15
check_interval 15
retry_interval 5
}
Add to your nagios.cfg:
Restart Nagios:
Checkmk¶
Checkmk supports Nagios-compatible plugins via "local checks" or MRPE.
Option A: Local check (recommended)¶
Copy the check plugin to the Checkmk agent's local checks directory on the OpenLatch server:
cp check_openlatch_nuki /usr/lib/check_mk_agent/local/
chmod +x /usr/lib/check_mk_agent/local/check_openlatch_nuki
Create a wrapper script that Checkmk's agent will execute:
cat > /usr/lib/check_mk_agent/local/openlatch_nuki_battery <<'EOF'
#!/bin/bash
# Checkmk local check — outputs Checkmk local check format
URL="http://localhost:8000/api/v1/monitoring/nuki-battery?warn=30&crit=15"
OUTPUT=$(curl -sf --max-time 10 "$URL" 2>/dev/null)
if [ $? -ne 0 ]; then
echo "3 openlatch_nuki_battery - UNKNOWN - Failed to reach OpenLatch"
exit 0
fi
# Extract perfdata (after |) and status
PERFDATA=$(echo "$OUTPUT" | sed 's/.*| //')
STATUS_TEXT=$(echo "$OUTPUT" | sed 's/ |.*//')
case "$OUTPUT" in
OK*) STATE=0 ;;
WARNING*) STATE=1 ;;
CRITICAL*) STATE=2 ;;
*) STATE=3 ;;
esac
echo "$STATE openlatch_nuki_battery $PERFDATA $STATUS_TEXT"
EOF
chmod +x /usr/lib/check_mk_agent/local/openlatch_nuki_battery
In Checkmk WATO, run a service discovery on the host. The openlatch_nuki_battery service will appear automatically.
Option B: MRPE (if agent runs on a different host)¶
Add to /etc/check_mk/mrpe.cfg:
OpenLatch_NUKI_Battery /usr/lib/nagios/plugins/check_openlatch_nuki -H https://openlatch.example.com -w 30 -c 15
Zabbix¶
Option A: HTTP agent (agentless, recommended)¶
In Zabbix web UI:
- Go to Configuration → Hosts → (your host) → Items → Create item
-
Set:
Field Value Name OpenLatch NUKI Battery Type HTTP agent URL https://openlatch.example.com/api/v1/monitoring/nuki-batteryRequest type GET Type of information Text Update interval 5m -
Create a trigger:
Field Value Name NUKI battery warning Severity Warning Expression find(/host/openlatch.nuki.battery,,"regexp","^WARNING")=1Field Value Name NUKI battery critical Severity High Expression find(/host/openlatch.nuki.battery,,"regexp","^CRITICAL")=1
Option B: External check (uses the plugin script)¶
Copy the check plugin to /usr/lib/zabbix/externalscripts/:
cp check_openlatch_nuki /usr/lib/zabbix/externalscripts/
chmod +x /usr/lib/zabbix/externalscripts/check_openlatch_nuki
Create an item with type External check:
Option C: Prometheus integration¶
If you already have Zabbix 6.0+ with Prometheus preprocessing, create an HTTP agent item pointing at /api/v1/monitoring/metrics and use Prometheus pattern preprocessing to extract individual metrics. See the Prometheus section below for the available metric names.
Prometheus + Grafana¶
This is the recommended setup for battery and usage graphs.
Prometheus configuration¶
Add to prometheus.yml:
scrape_configs:
- job_name: openlatch
scrape_interval: 60s
metrics_path: /api/v1/monitoring/metrics
static_configs:
- targets:
- openlatch.example.com
scheme: https
Reload Prometheus:
Verify scraping works:
Available metrics¶
| Metric | Type | Description |
|---|---|---|
openlatch_heartbeat_up |
gauge | 1 if heartbeat received, 0 if not |
openlatch_heartbeat_age_seconds |
gauge | Seconds since last heartbeat |
openlatch_device_uptime_seconds |
gauge | ESP32 uptime |
openlatch_device_free_heap_bytes |
gauge | ESP32 free heap memory |
openlatch_device_wifi_rssi_dbm |
gauge | WiFi signal strength |
openlatch_nuki_battery_percent |
gauge | Battery level 0-100 |
openlatch_nuki_battery_critical |
gauge | 1 if NUKI reports critical battery |
openlatch_nuki_paired |
gauge | 1 if paired, 0 if not |
openlatch_nuki_daily_unlatch_count |
gauge | Unlatch actions in last daily report |
openlatch_nuki_daily_lock_count |
gauge | Lock actions in last daily report |
openlatch_nuki_daily_total_actions |
gauge | Total actions in last daily report |
Prometheus alerting rules¶
Save as /etc/prometheus/rules/openlatch.yml:
groups:
- name: openlatch
rules:
- alert: NukiBatteryLow
expr: openlatch_nuki_battery_percent < 30
for: 1h
labels:
severity: warning
annotations:
summary: "NUKI battery low ({{ $value }}%)"
description: "Battery has been below 30% for over 1 hour."
- alert: NukiBatteryCritical
expr: openlatch_nuki_battery_percent < 15 or openlatch_nuki_battery_critical == 1
for: 10m
labels:
severity: critical
annotations:
summary: "NUKI battery critical ({{ $value }}%)"
description: "Replace batteries immediately."
- alert: OpenLatchHeartbeatMissing
expr: openlatch_heartbeat_up == 0 or openlatch_heartbeat_age_seconds > 900
for: 15m
labels:
severity: warning
annotations:
summary: "No heartbeat from OpenLatch for >15 minutes"
description: "ESP32 may be offline or network connectivity lost."
- alert: NukiNotPaired
expr: openlatch_nuki_paired == 0
for: 5m
labels:
severity: critical
annotations:
summary: "NUKI not paired with ESP32"
description: "Lock cannot be controlled. Re-pair required."
Grafana dashboard¶
Import the following dashboard JSON or create panels manually.
Panel 1: NUKI Battery Level¶
- Type: Time series (line chart)
- Query:
openlatch_nuki_battery_percent - Unit: Percent (0-100)
- Thresholds: 30 (yellow), 15 (red)
PromQL:
Panel 2: Device Health¶
- Type: Stat panel (4 values)
- Queries:
openlatch_nuki_battery_percent{job="openlatch"}
openlatch_device_uptime_seconds{job="openlatch"} / 3600
openlatch_device_wifi_rssi_dbm{job="openlatch"}
openlatch_device_free_heap_bytes{job="openlatch"} / 1024
Panel 3: Heartbeat Freshness¶
- Type: Gauge
- Query:
openlatch_heartbeat_age_seconds{job="openlatch"} - Unit: Seconds
- Thresholds: 300 (green), 600 (yellow), 900 (red)
Panel 4: Battery + Daily Usage over Time¶
- Type: Time series with dual Y-axes
- Left Y-axis: Battery % (line)
- Right Y-axis: Daily action count (bar)
- X-axis: Time
PromQL queries:
# Battery level over time
openlatch_nuki_battery_percent{job="openlatch"}
# Daily usage (total actions from 3am daily report)
openlatch_nuki_daily_total_actions{job="openlatch"}
For more detail, split by action type:
openlatch_nuki_daily_unlatch_count{job="openlatch"}
openlatch_nuki_daily_lock_count{job="openlatch"}
Alternatively, query the audit log directly via Grafana's PostgreSQL data source for historical daily reports:
SELECT
details->>'battery_percent' AS battery,
details->>'total_actions' AS actions,
created_at
FROM audit_log
WHERE event_type = 'nuki_daily_report'
ORDER BY created_at;
Uptime Kuma¶
- Add a new monitor: HTTP(s) - Keyword
-
Set:
Field Value URL https://openlatch.example.com/api/v1/monitoring/nuki-batteryKeyword OKHeartbeat interval 300 (5 min)
This marks the service as DOWN when the response does not contain "OK" (i.e., WARNING, CRITICAL, or UNKNOWN).
For more granularity, create separate monitors:
- NUKI Battery Critical: keyword
CRITICAL, invert match, expected status UP when keyword is absent - NUKI Heartbeat: URL
https://openlatch.example.com/api/v1/health, keywordok