Přeskočit obsah

Karta zařízení

Toto je klíčová kapitola oddílu. Zde zařízení získá rozhraní v portálu a mobilní aplikaci — bez jediného řádku kódu na jejich straně.

Jak to funguje

Zařízení zveřejní manifest karty — strojově čitelný popis „co zobrazit a čím ovládat". Portál a aplikace si podle manifestu postaví kartu: senzory se stanou buňkami s živými hodnotami, ovládací prvky — tlačítky, vstupními poli a seznamy. Rozvržení si taky můžete zadat z programu.

Nemusíte nic publikovat ručně: deklarujete entity přes link.card() a jádro samo sestaví manifest a odešle jej při připojení.

1. Deklarujeme entity

Všechny deklarace se provádějí v setup(), za s_link.begin(). Náš filtr má tři entity: čtení VOC, seznam režimů a pole prahu. Rozebereme každou zvlášť a na konci celek složíme dohromady.

Obecný princip: id a label

Každá entita má dvě jména, nepleťte si je:

  • id — vnitřní, strojové jméno ("voc", "mode"). Latina, číslice, podtržítko, bez mezer. Podle id se entita pozná v rozvržení, příkazech a portálu navzájem. Vymysleli jednou — neměníte;
  • label — nápis pro člověka ("VOC index", "Mode"). Co napíšete, to si uživatel na kartě přečte. Měnit se dá libovolně.

Senzor: čtení VOC

s_link.card().sensor(
    "voc",              // id: vnitřní jméno entity
    "VOC index",        // label: nápis na kartě
    "",                 // unit: jednotka měření vpravo od čísla ("°C", "%", "g");
                        //   VOC index nemá jednotky — prázdný řetězec
    "units[0].vocIndex" // path: odkud vzít hodnotu — cesta v JSON telemetrických datech.
                        //   To je TEN SAMÝ prvek, který jsme doplnili v kapitole 5:
                        //   doc["units"][0]["vocIndex"]. Jména se musí shodovat
                        //   písmeno za písmenem, jinak bude na kartě pomlčka.
);

Senzor je dlaždice „pouze pro čtení": portál vezme hodnotu z telemetrie podle path a zobrazí ji. Senzor nemá žádný příkaz.

Seznam výběru: režim práce

// Varianty seznamu. Uživatel je uvidí v rozbalovacím menu takové jak jsou.
static const char* kModes[] = { "auto", "on", "off" };

s_link.card().select(
    "mode",                  // id: vnitřní jméno entity
    "Mode",                  // label: nápis na kartě
    kModes,                  // options: pole variant (deklarováno výše)
    3,                       // počet variant v poli — auto, on, off = tři.
                             //   C++ sám nezná délku pole, řekneme ji my
    [](const char* opt) {    // callback: funkce, kterou jádro volá, když
                             //   uživatel vybere variantu v portálu.
                             //   opt — vybraný řetězec, např. "on"
        onModeSelected(opt); //   předáme ji do naší logiky (napíšeme v kapitole 7)
    }
);

Tím se aktivuje druhá polovina mechanismu: ovládání. Když uživatel vybere možnost v portálu, zařízení obdrží příkaz, jádro jej samo přijme, ověří (cizí řetězce, které nejsou v options, se k vám nedostanou) a zavolá váš callback s vybranou hodnotou. MQTT zprávy rozebírat ručně nemusíte — vaše zodpovědnost začíná až uvnitř onModeSelected.

Číselné pole: práh spuštění

s_link.card().number(
    "threshold",       // id: vnitřní jméno entity
    "VOC threshold",   // label: nápis na kartě
    100,               // min: méně než toto portál nezadá
    400,               // max: více než toto — taky; jádro navíc
                       //   ořízne hodnotu v těchto mezích na své straně
    10,                // step: krok změny hodnoty šipkami pole
    "",                // unit: jednotka měření; index ji nemá
    [](float v) {              // callback: volá se, když uživatel poslal
                               //   novou hodnotu; v — číslo v rozmezí min..max
        onThresholdChanged(v); //   předáme do naší logiky (napíšeme v kapitole 7)
    }
);

Skládáme dohromady

Finální podoba bloku v setup() — to, co má v kódu zůstat. Funkce onModeSelected a onThresholdChanged napíšeme v kapitole 7; aby se kód přeložil již teď, deklarujte je jako prázdné zástupce před setup():

// Zástupci: skutečná těla napíšeme v kapitole 7 (logika automatiky).
static void onModeSelected(const char* opt) {}
static void onThresholdChanged(float v) {}

void setup() {
    Serial.begin(115200);
    s_link.begin();
    initVocSensor();

    // Telemetrické údaje: vlastní pole vocIndex (kapitola 5).
    s_link.onTelemetryPublish([](JsonObject doc) {
        if (g_vocIndex >= 0) {
            doc["units"][0]["vocIndex"] = g_vocIndex;
        }
    });

    // Karta: senzor + dva ovládací prvky.
    s_link.card().sensor("voc", "VOC index", "", "units[0].vocIndex");

    static const char* kModes[] = { "auto", "on", "off" };
    s_link.card().select("mode", "Mode", kModes, 3, [](const char* opt) {
        onModeSelected(opt);
    });

    s_link.card().number("threshold", "VOC threshold", 100, 400, 10, "", [](float v) {
        onThresholdChanged(v);
    });
}

A ventilátor? Deklarovat jej není třeba: příznak hasFan = true v Config již automaticky přidal dlaždici „Ventilátor" do manifestu — to je slovníková schopnost, jádro ji zná samo.

Hranaté závorky v callbackech — vždy prázdné

[](const char* opt) { ... } — lambda, bezejmenná funkce; podrobně jsme ji rozebrali v poznámce kapitoly 5. Připomínka pravidla jádra: hranaté závorky jsou vždy prázdné ([]), do lambdy nic „s sebou" neberte, vše potřebné uchovávejte v globálních proměnných — jako g_mode a g_threshold z následující kapitoly.

2. Automatické rozvržení karty

Rozvržení vůbec nastavovat nemusíte. Portál kartu z deklarovaných entit sestaví sám — a sestaví ji pěkně: dlaždice s hodnotami se seskupí do řádků (nejvýše tři v řadě, pak zalomení), ovládací prvky jdou níže, každý na vlastním řádku, vše ve firemním designu portálu. Pro většinu zařízení to plně stačí — rozhraní vypadá úhledně bez jediné starosti o rozložení.

Pořadí entit na kartě odpovídá pořadí jejich deklarace v setup().

3. Vlastní rozvržení karty (volitelné)

Nejprve — jak je karta strukturována. Karta je svislý sloupec řádků. Řádek je vodorovný pás s jednou až čtyřmi entitami; šířku karty si dělí rovnoměrně: jedna entita zabere celou šířku, dvě po polovině, tři po třetině.

Automatické rozvržení z předchozí sekce entity po těchto řádcích rozloží samo. Chcete-li sami rozhodovat, co s čím stojí vedle sebe, nastavte řádky ručně voláními layoutRow. Jedno volání = jeden řádek, pořadí volání = pořadí řádků shora dolů:

// Řádek 1: dvě buňky — VOC index a ventilátor, každá po polovině šířky.
s_link.card().layoutRow("voc", "fan");

// Řádek 2: dva ovládací prvky — režim a práh, taky po polovině.
s_link.card().layoutRow("mode", "threshold");

Do layoutRow se předávají id entit — interní jména přiřazená při deklaraci (právě proto bylo id potřebné). "fan" je id slovníkové entity ventilátoru, vytvořené příznakem hasFan.

Na kartě to vytvoří toto rozvržení:

┌─ DIY Air Filter ────────────────┐
│  VOC index        │  Ventilátor │   ← řádek 1: voc, fan
│  103              │  Vyp        │
├───────────────────┼─────────────┤
│  Mode      [auto ▾] │ Threshold [150] │   ← řádek 2: mode, threshold
└─────────────────────────────────┘

Entity, které jste nezmínili v žádném řádku, se neztratí — portál je doplní pod ním automaticky. Takže stačí rozvrhnout jen „to nejdůležitější" a zbytek přenechat automatice.

4. Co se publikuje

Jádro zveřejní do topiku idryer/{serial}/card (retained):

{
  "v": 1,
  "entities": [
    { "id": "fan",  "type": "binary_sensor", "device_class": "fan",
      "source": "telemetry", "path": "units[0].fanStatus" },
    { "id": "voc",  "type": "sensor", "label": "VOC index",
      "source": "telemetry", "path": "units[0].vocIndex" },
    { "id": "mode", "type": "select", "label": "Mode",
      "options": ["auto", "on", "off"], "action": "card.mode", "arg": "value" },
    { "id": "threshold", "type": "number", "label": "VOC threshold",
      "min": 100, "max": 400, "step": 10, "action": "card.threshold", "arg": "value" }
  ],
  "layout": [ ["voc", "fan"], ["mode", "threshold"] ]
}

Tento JSON rozebírat nemusíte — jádro jej generuje z vašich volání. Je ale dobré o něm vědět: pokud píšete firmware ne na idryer-core (Rust, MicroPython, cokoliv jiného), stačí takový JSON publikovat přímo — portál přijme cokoliv, pokud formát souhlasí.

5. Ověření

Nahrajte firmware a otevřete zařízení v portálu:

  • dlaždice VOC index zobrazuje živý index (vydechněte na senzor — při příštím obnovení číslo vzroste);
  • dlaždice Ventilátor — Zap/Vyp;
  • Mode — rozbalovací seznam, VOC threshold — pole s tlačítkem odeslání.

Výběr režimu a prahu zatím nic nedělá — to jsou prázdné zástupce callbacků. Oživíme je v následující kapitole.

To je právě ta koncepce

Všimněte si, co se stalo: popsali jste rozhraní pěti řádky v programu — a objevilo se v portálu i v aplikaci. Stejný postup funguje pro jakékoli vaše zařízení: mění se jen id, popisky a callbacky.