Metryki - dane historyczne#
System metryk zbiera wartości liczbowe w czasie (temperatura, wilgotność, prędkość wentylatora) i przechowuje je w ring bufferach (RAM) oraz plikach dziennych (dysk). Dane dostępne są przez HTTP API i mogą być wyświetlane na dashboardach jako wykresy.
Szybki start#
Utwórz plik modules/metrics.lua:
local r = _registry
-- Rejestracja termometru pokojowego
metric("temp:salon", function()
return r:get("CLU221000290.ANA4201"):getValue()
end, {unit = "°C", interval = 30, decimals = 1})
-- Rejestracja wilgotności z pluginu Salda
local salda = Plugin.getPlugin("@vclu/salda-recuperator")
if salda then
local hum = salda:get("humidity")
metric("salda:humidity", function()
return hum:get()
end, {unit = "%", interval = 30, decimals = 1})
endDodaj w modules/init.lua:
require("metrics")Po reloadzie metryki zaczną się zbierać. Dane widoczne pod /api/metrics/list.
Lua API#
metric(path, getter, opts)#
Rejestruje metrykę do cyklicznego zbierania.
metric(path, getter, opts)| Parametr | Typ | Opis |
|---|---|---|
path | string | Unikalny identyfikator. Konwencja: źródło:metryka (np. temp:salon, salda:outsideAir) |
getter | function | Funkcja zwracająca aktualną wartość. Wywoływana co interval sekund |
opts | table | Opcje (patrz niżej) |
Opcje:
| Opcja | Typ | Domyślnie | Opis |
|---|---|---|---|
unit | string | "" | Jednostka ("°C", "%", "RPM") |
interval | number | 60 | Sekundy między próbkami. Minimum: 1 |
decimals | number | nil | Zaokrąglij do N miejsc po przecinku |
Przykłady:
-- Termometr pokojowy - co 30s, zaokrąglony do 0.1°C
metric("temp:salon", function()
return r:get("CLU.ANA4201"):getValue()
end, {unit = "°C", interval = 30, decimals = 1})
-- Wilgotność z pluginu
metric("salda:humidity", function()
return salda:get("humidity"):get()
end, {unit = "%", interval = 30, decimals = 1})
-- Prędkość wentylatora - co 10s
metric("salda:fanSpeed", function()
return salda:get("fanSpeed"):get()
end, {unit = "RPM", interval = 10})Getter musi być szybki (< 1ms) - działa na głównym wątku Lua. Unikaj HTTP requestów w getterze.
metric()wywołuj podczas inicjalizacji modułu, nie w pętlach ani timerach. Rejestracja jest idempotentna - ponowne wywołanie z tą samą ścieżką nadpisuje poprzednią.
HTTP API#
GET /api/metrics/list#
Lista wszystkich zarejestrowanych metryk z metadanymi.
[
{"path": "temp:salon", "unit": "°C", "interval": 30, "samples": 720, "latest": 23.9},
{"path": "salda:humidity", "unit": "%", "interval": 30, "samples": 240, "latest": 45.2}
]GET /api/metrics/data?path=...&range=...#
Dane historyczne dla jednej metryki.
| Parametr | Wymagany | Opis |
|---|---|---|
path | tak | Ścieżka metryki (np. temp:salon) |
range | nie | Zakres czasu. Domyślnie 2h. Obsługiwane: 30m, 2h, 24h, 7d |
Odpowiedź: tablica par [timestamp, value]:
[[1710001000, 23.9], [1710001030, 23.8], [1710001060, 23.9]]Timestamp w sekundach Unix. Wartości zaokrąglone wg decimals.
Wyświetlanie na dashboardach#
Wykres (chart widget)#
Pełny wykres liniowy z tooltipami, osiami i auto-refresh:
section:chart('salon_temp', 'Temperatura salon', 'temp:salon',
{unit = '°C', range = '2h', span = 2, color = '#FF6B6B'})Szczegóły: Dashboard → chart
Sparkline na statusie#
Miniaturowy wykres trendu pod wartością:
section:status('salon', 'Salon', 'thermometer-half',
function() return r:get("CLU.ANA4201"):getValue() end, '°C',
{spark = 'temp:salon', sparkRange = '2h'})Szczegóły: Dashboard → sparkline
Przechowywanie danych#
Ring buffer (RAM)#
Każda metryka ma cykliczny bufor w pamięci (domyślnie 720 próbek). Przy interwale 10s to 2 godziny danych. Dane w RAM są natychmiast dostępne - zapytania range=30m i range=2h są obsługiwane z bufora.
Pliki dzienne (dysk)#
Co 5 minut dane z buforów zapisu są dopisywane do plików dziennych:
data/clu/metrics/
temp.salon/
2026-03-05.ts
2026-03-06.ts
salda.humidity/
2026-03-05.tsFormat pliku (jedna linia na próbkę):
1710001000 23.9
1710001030 23.8Pliki starsze niż 7 dni są automatycznie usuwane.
Zapytania o starsze dane#
Zapytania range=24h i range=7d automatycznie sięgają do plików na dysku gdy ring buffer nie obejmuje żądanego zakresu.
Limity#
| Parametr | Wartość | Opis |
|---|---|---|
| Max metryk | 500 | Ogranicza zużycie RAM |
| Ring buffer | 720 próbek/metrykę | ~5.5 MB przy 500 metrykach |
| Flush | co 5 min | Max 5 min utraty danych przy restarcie |
| Retencja | 7 dni | Starsze pliki automatycznie usuwane |
| Min interwał | 1s | Minimum czasu między próbkami |
Konwencje nazewnictwa#
| Prefiks | Przykład | Źródło |
|---|---|---|
temp: | temp:salon | Termometry pokojowe (Grenton AnalogIN) |
salda: | salda:outsideAir | Rekuperator Salda (plugin) |
sys: | sys:memory | Metryki systemowe (planowane) |
mqtt: | mqtt:published | Liczniki MQTT (planowane) |
Przykład: pełny moduł metryk#
--- modules/metrics.lua
local r = _registry
-- Termometry pokojowe
local thermometers = {
{path = "temp:salon", reg = "CLU221000290.ANA4201", name = "Salon"},
{path = "temp:kuchnia", reg = "CLU221000290.ANA5151", name = "Kuchnia"},
{path = "temp:sypialnie", reg = "CLU221000290.ANA9920", name = "Sypialnie"},
}
for _, t in ipairs(thermometers) do
local obj = _registry[t.reg]
if obj then
metric(t.path, function() return obj:getValue() end, {
unit = "°C", interval = 30, decimals = 1,
})
end
end
-- Salda (opóźnione - plugin ładuje się z opóźnieniem)
EventBus:getShared():setTimeout(function()
local salda = Plugin.getPlugin("@vclu/salda-recuperator")
if not salda then return end
local sensors = {
{path = "salda:outsideAir", key = "outsideAir", unit = "°C"},
{path = "salda:supplyAir", key = "supplyAir", unit = "°C"},
{path = "salda:humidity", key = "humidity", unit = "%"},
{path = "salda:fanSpeed", key = "fanSpeed", unit = ""},
}
for _, s in ipairs(sensors) do
local obj = salda:get(s.key)
if obj then
metric(s.path, function() return obj:get() end, {
unit = s.unit, interval = 30, decimals = 1,
})
end
end
end, 6000)