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})
end

Dodaj 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)
ParametrTypOpis
pathstringUnikalny identyfikator. Konwencja: źródło:metryka (np. temp:salon, salda:outsideAir)
getterfunctionFunkcja zwracająca aktualną wartość. Wywoływana co interval sekund
optstableOpcje (patrz niżej)

Opcje:

OpcjaTypDomyślnieOpis
unitstring""Jednostka ("°C", "%", "RPM")
intervalnumber60Sekundy między próbkami. Minimum: 1
decimalsnumbernilZaokrą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.

ParametrWymaganyOpis
pathtakŚcieżka metryki (np. temp:salon)
rangenieZakres 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.ts

Format pliku (jedna linia na próbkę):

1710001000 23.9
1710001030 23.8

Pliki 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#

ParametrWartośćOpis
Max metryk500Ogranicza zużycie RAM
Ring buffer720 próbek/metrykę~5.5 MB przy 500 metrykach
Flushco 5 minMax 5 min utraty danych przy restarcie
Retencja7 dniStarsze pliki automatycznie usuwane
Min interwał1sMinimum czasu między próbkami

Konwencje nazewnictwa#

PrefiksPrzykładŹródło
temp:temp:salonTermometry pokojowe (Grenton AnalogIN)
salda:salda:outsideAirRekuperator Salda (plugin)
sys:sys:memoryMetryki systemowe (planowane)
mqtt:mqtt:publishedLiczniki 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)