Bakaláři v Home Assistantu: známky a zprávy ze školy na dashboardu
Bakaláři používá většina českých základek a středních škol. Mají web i mobilní aplikaci – a obojí funguje tak, že se o nové zprávě od učitele dozvíte, až když se sami podíváte. Napojil jsem Bakaláře přes jejich API na Home Assistant. Nové známky teď svítí na dashboardu v kuchyni barevně podle toho, jak dopadly, a při nové zprávě přijde notifikace oběma rodičům.
Obtížnost: Středně pokročilý
Co je potřeba: Home Assistant + Node-RED, rodičovský nebo žákovský účet do Bakaláří, adresa školního serveru
Co to nakonec umí
Flow se každých dvacet minut zeptá školního serveru na čtyři věci a výsledek uloží do helperů v Home Assistantu:
- Zprávy z Komens – celkový počet a kolik z nich je nepřečtených
- Nástěnka – stejné dvě čísla zvlášť, protože nástěnka je jiný endpoint
- Domácí úkoly – počet aktuálně zadaných
- Známky – jen ty nové, s předmětem, hodnotou, vahou a popisem
Na dashboardu z toho vznikne karta, kde je jednička zeleně, trojka oranžově a pětka červeně, a nad ní odznáček, který se objeví jen když je co číst. Při nové zprávě přijde notifikace na oba telefony. A protože je to všechno v Home Assistantu jako obyčejné entity, dá se na to navěsit cokoli dalšího – hlášení na chytrý reproduktor, řádek na LED panelu, cokoli.
Dvě cesty, jak na to
Než začnete stavět vlastní flow, stojí za to vědět, že v HACS existuje hotová integrace Bakaláři HA a k ní sada karet Bakaláři Cards. Instaluje se běžnou cestou přes HACS a je to výrazně rychlejší start.
Já jsem nakonec zůstal u vlastního flow v Node-RED, protože chci mít pod kontrolou přesně to, kdy a komu se pošle notifikace, a chci si sám určit, jak se známky formátují. Pokud vám stačí data v Home Assistantu a nechcete řešit tokeny, začněte integrací z HACS a tenhle článek berte jako popis toho, co se děje pod kapotou.
Jak Bakaláři API funguje
Bakaláři mají REST API na stejném serveru jako webové rozhraní. Adresa je vždycky ve tvaru https://vase-skola.bakalari.cz, případně má škola Bakaláře na vlastní doméně – podívejte se do adresního řádku, když se přihlašujete přes prohlížeč.
Přihlášení
Autentizace je OAuth 2.0 s grant_type=password. Pošlete login a heslo, dostanete zpátky access_token a refresh_token.
POST https://vase-skola.bakalari.cz/api/login
Content-Type: application/x-www-form-urlencoded
client_id=ANDR&grant_type=password&username=VAS_LOGIN&password=VASE_HESLO
Hodnota client_id=ANDR je pevná – je to identifikátor android klienta a Bakaláři ho očekávají. V Node-RED to je jeden function node, který připraví tělo požadavku, a za ním http request:
msg.payload = "client_id=ANDR&grant_type=password"
+ "&username=" + global.get('bakalariUser')
+ "&password=" + global.get('bakalariPass');
msg.headers = { "Content-Type": "application/x-www-form-urlencoded" };
msg.method = 'POST';
msg.url = 'https://vase-skola.bakalari.cz/api/login';
return msg;
Následující node token vytáhne a uloží do zprávy. Zároveň je to místo, kde se pozná neúspěšné přihlášení:
if (!msg.payload || !msg.payload.access_token) {
node.error('Login failed', msg);
return null;
}
msg.access_token = msg.payload.access_token;
return msg;
Token má platnost v řádu hodin. Existuje sice refresh_token, ale u flow, který běží každých dvacet minut, se nevyplatí ho řešit – je jednodušší se přihlásit znovu při každém běhu. Jedno volání navíc za dvacet minut školní server nezatíží.
Endpointy, které potřebujete
| Endpoint | Metoda | Co vrací |
|---|---|---|
/api/3/komens/messages/received |
POST | Přijaté zprávy od učitelů |
/api/3/komens/messages/noticeboard |
POST | Zprávy z nástěnky |
/api/3/homeworks/count-actual |
GET | Počet aktuálních úkolů (samotné číslo) |
/api/3/marks |
GET | Známky seskupené po předmětech |
Ke každému požadavku patří hlavička s tokenem:
msg.headers = {
"Authorization": "Bearer " + msg.access_token,
"Content-Type": "application/x-www-form-urlencoded"
};
Past č. 1: metoda není u všech endpointů stejná
Tohle mě stálo nejvíc času a v žádné dokumentaci jsem to nenašel. Komens a nástěnka odpovídají jen na POST, i když nic neposíláte a tělo požadavku je prázdné. Úkoly a známky naopak chtějí GET a na POST vrátí chybu.
Když u Komens pošlete GET, nedostanete chybu 405, jak by člověk čekal – dostanete odpověď, která vypadá skoro správně, ale seznam zpráv je prázdný. Flow tiše hlásí nula nepřečtených a vy si týden myslíte, že vám učitelé nepíšou.
Doporučuju si každý endpoint nejdřív vyzkoušet ručně –
injectnode,http requestadebugnode na výstupu. Až uvidíte v debug panelu skutečná data, teprve pak stavte zbytek.
Past č. 2: odpověď zpracovávejte jako text
Nastavte http request node na návratový typ a UTF-8 string, ne na a parsed JSON object. Když totiž server vrátí chybovou stránku místo JSONu, automatické parsování spadne a v debug panelu uvidíte jen nicneříkající hlášku. Vlastní parsování s try/catch vám ukáže, co server ve skutečnosti poslal:
if (typeof msg.statusCode !== 'undefined' && msg.statusCode !== 200) {
node.error('Komens HTTP error: ' + msg.statusCode, msg);
node.status({ fill: 'red', shape: 'dot', text: 'HTTP ' + msg.statusCode });
return msg;
}
let raw = (typeof msg.payload === 'string') ? msg.payload : JSON.stringify(msg.payload);
try {
let data = JSON.parse(raw);
let msgs = data.Messages || [];
let total = msgs.length;
let unread = msgs.filter(m => !m.Read).length;
msg.payload = { total: total, unread: unread };
node.status({ fill: 'green', shape: 'dot', text: 'OK ' + unread });
return msg;
} catch (e) {
node.error('JSON parse error: ' + e.message, { raw: raw });
node.status({ fill: 'yellow', shape: 'ring', text: 'parse error' });
return msg;
}
Volání node.status() je detail, který se vyplatí. Pod nodem pak v editoru vidíte zelenou tečku s počtem nepřečtených zpráv, nebo červenou s HTTP kódem. Nemusíte otevírat debug panel, abyste věděli, že flow běží.
Helpery v Home Assistantu
Data z API se ukládají do obyčejných helperů. Vytvořte je v Nastavení → Zařízení a služby → Pomocníci:
# Čísla (Pomocník typu "Číslo", rozsah 0-99, krok 1)
input_number.helper_skola_celkem_zprav
input_number.helper_skola_neprectene_zpravy
input_number.helper_skola_celkem_zprav_nastenka
input_number.helper_skola_neprectene_zpravy_nastenka
input_number.helper_skola_nove_ukoly
# Texty (Pomocník typu "Text", max. délka 255)
input_text.znamka_1
input_text.znamka_2
...
input_text.znamka_9
Devět textových helperů na známky je záměrné. Node-RED do nich zapisuje nové známky po jedné a devět jich je dost i na vysvědčovací týden. Kdo chce víc, přidá další – flow se řídí jedním switch nodem, kde stačí přidat výstup.
Flow: zprávy a notifikace
Kostra celého flow vypadá takhle. Jeden inject node s opakováním po 1200 sekundách (20 minut) spustí přihlášení a to se pak větví do tří samostatných dotazů:
[inject: každých 1200 s]
→ [function: připrav login]
→ [http request: POST /api/login]
→ [function: vytáhni access_token]
│
├→ [function: připrav Komens] → [http request] → [function: spočítej] → helpery + notifikace
├→ [function: připrav nástěnku] → [http request] → [function: spočítej] → helpery
└→ [function: připrav úkoly] → [http request] → [function: parsuj] → helper
Přihlášení se tedy provádí jen jednou za běh a token se předá všem třem větvím ve zprávě.

Zelené popisky pod nody jsou výstup z node.status() – v editoru tak na první pohled vidíte, kdy flow naposledy proběhl a s jakým výsledkem.
Kdy poslat notifikaci
Tady je jediné místo, kde se dá logika snadno pokazit. Notifikace nemá odejít pokaždé, když jsou nepřečtené zprávy – to by chodila každých dvacet minut, dokud si je někdo nepřečte. Má odejít, jen když počet nepřečtených vzroste proti minulému běhu.
Předchozí hodnotu už máte uloženou v helperu, takže stačí porovnat:
const predchozi = parseInt(msg.predchozi) || 0; // z current state nodu
const aktualni = msg.payload.unread;
// vždy ulož aktuální stav
node.send([{ payload: { value: aktualni } }, null]);
// notifikaci jen při nárůstu
if (aktualni > predchozi) {
return [null, { payload: { pocet: aktualni - predchozi } }];
}
return null;
Před tenhle node vložte current state node na input_number.helper_skola_neprectene_zpravy, aby byla předchozí hodnota ve zprávě k dispozici.
Samotná notifikace
Service: notify.mobile_app_VAS_TELEFON
Data:
{
"title": "Zpráva ze školy",
"message": "Nová zpráva od učitele",
"data": {
"color": "#FFA500",
"priority": "high",
"ttl": 0
}
}

data zadává hůř.Kombinace priority: high a ttl: 0 obchází na Androidu úsporný režim, který by notifikaci jinak mohl doručit až za hodinu. U školních zpráv to má smysl – u méně důležitých notifikací to nechte být, jinak si vybijete baterii.
Node zduplikujte pro každý telefon v rodině. U nás chodí notifikace na Android i na iPhone, takže jsou dva.
Flow: známky s barevným rozlišením
Nejzajímavější část. Endpoint /api/3/marks vrací všechny známky za pololetí seskupené po předmětech, a u každé je příznak IsNew. Nás zajímají jen ty nové.
Struktura odpovědi vypadá zjednodušeně takhle:
{
"Subjects": [
{
"Subject": { "Abbrev": "Čj", "Name": "Český jazyk" },
"Marks": [
{
"MarkText": "1",
"Weight": null,
"Caption": "Prezentace knihy",
"IsNew": true
}
]
}
]
}
Trik s ha-alert
Karta typu Markdown v Lovelace umí vykreslit komponentu <ha-alert>. To je ten barevný pruh, který znáte z upozornění v nastavení Home Assistantu – a má čtyři varianty: info, success, warning a error.
Když do textového helperu uložíte rovnou celou tu značku, karta ji vykreslí barevně. Žádné šablony v Lovelace, žádné card-mod. Barvu určuje Node-RED podle hodnoty známky:
let cislo = 1;
for (const predmet of msg.payload.Subjects) {
for (const znamka of predmet.Marks) {
if (!znamka.IsNew) continue;
let typ;
switch (znamka.MarkText) {
case "1":
case "2": typ = "success"; break;
case "3": typ = "warning"; break;
default: typ = "error";
}
const vaha = znamka.Weight ?? "-";
msg.payload = '<ha-alert alert-type="' + typ + '">'
+ predmet.Subject.Abbrev + " - " + znamka.MarkText
+ " [" + vaha + "] (" + znamka.Caption + ")"
+ '</ha-alert>';
msg.cislo = cislo;
node.send(msg);
cislo++;
}
}
Za function node přijde switch node, který podle msg.cislo rozhodí zprávy do devíti výstupů, a na každém výstupu je call service na input_text.set_value příslušného helperu.
Pozor na
Weight. U některých známek je váhanull– typicky u těch, které učitel zadal bez váhy. Bez ošetření se vám do textu propíše doslova řetězec „null“. Konstrukceznamka.Weight ?? "-"to vyřeší.
Vyčištění před každým během
Než flow začne zapisovat nové známky, musí ty staré smazat – jinak by na dashboardu zůstala viset známka z minulého týdne. Úplně první node za inject je proto call service na input_text.set_value se všemi devíti helpery najednou a prázdnou hodnotou:
Service: input_text.set_value
Entity: input_text.znamka_1, input_text.znamka_2, ... input_text.znamka_9
Data: { "value": "" }
Karta na dashboard
Zobrazení je pak triviální – obyčejná Markdown karta, která vypíše obsah helperů. Prázdné se nevykreslí, takže karta roste a zmenšuje se podle toho, kolik je nových známek:
type: markdown
title: Škola
content: |
**Nepřečtené zprávy:** {{ states('input_number.helper_skola_neprectene_zpravy') | int }}
**Nástěnka:** {{ states('input_number.helper_skola_neprectene_zpravy_nastenka') | int }}
**Domácí úkoly:** {{ states('input_number.helper_skola_nove_ukoly') | int }}
{{ states('input_text.znamka_1') }}
{{ states('input_text.znamka_2') }}
{{ states('input_text.znamka_3') }}
{{ states('input_text.znamka_4') }}
{{ states('input_text.znamka_5') }}
Kdo chce, aby se karta vůbec nezobrazovala, když nic nového není, obalí ji kartou typu Conditional s podmínkou na input_number.helper_skola_neprectene_zpravy větší než nula.
Odznáček, který se objeví jen když je co číst
Na hlavní dashboard nechci kartu se školou pořád. Chci jen malý odznáček nahoře, který se zjeví ve chvíli, kdy přijde nepřečtená zpráva, a jinak tam vůbec není. Na to se hodí mushroom-template-badge z kolekce Mushroom (instaluje se přes HACS):
type: custom:mushroom-template-badge
icon: mdi:message-alert-outline
color: '#D0021B'
entity: input_number.helper_skola_neprectene_zpravy
label: >-
{{ states("input_number.helper_skola_neprectene_zpravy") | int }} nová
zpráva
content: Ze školy
visibility:
- condition: numeric_state
entity: input_number.helper_skola_neprectene_zpravy
above: 0

Klíčová je sekce visibility. Není to totéž co Conditional karta – podmínka je součástí samotného odznáčku, takže nepotřebujete nic obalovat a v editoru dashboardu zůstane všechno na jednom místě.
V editoru se náhled odznáčku zobrazuje i při nule, takže nečekejte, že zmizí už tam. Skryje se až na hotovém dashboardu.
Bezpečnost: kde skončí vaše heslo
Tohle je nejdůležitější odstavec celého článku a málem jsem si na tom sám naběhl.
Když napíšete login a heslo natvrdo do function nodu, uloží se v čitelné podobě do flows.json. Ten soubor pak skončí v každé záloze Home Assistantu, v každém exportu flow a v každém screenshotu editoru, který někam pošlete. Bakalářský účet přitom není žádná drobnost – jsou za ním údaje o dítěti.
Řešení, které funguje v Node-RED add-onu: uložte přihlašovací údaje do globálního kontextu v souboru settings.js, který se s flow neexportuje.
// v settings.js v konfiguračním adresáři Node-RED
functionGlobalContext: {
bakalariUser: "vas-login",
bakalariPass: "vase-heslo",
bakalariUrl: "https://vase-skola.bakalari.cz"
},
Ve function nodu je pak vytáhnete přes global.get('bakalariUser'), jak je vidět v ukázce přihlášení výše. Export flow, který někomu pošlete, obsahuje jen názvy proměnných.
Pokud už máte heslo napsané přímo ve flow, projděte si i staré zálohy. A než pošlete komukoli export flow, otevřete ho v textovém editoru a vyhledejte v něm slovo
password.
Časté problémy
Přihlášení vrací chybu, přitom heslo je správně. Zkontrolujte Content-Type. Bakaláři chtějí application/x-www-form-urlencoded, ne JSON. A tělo požadavku musí být řetězec, ne objekt.
Komens vrací prázdný seznam. Skoro jistě posíláte GET místo POST. Viz past č. 1 výše.
Dostávám 401 uprostřed dne. Vypršel token. Ujistěte se, že se každý běh flow začíná přihlášením a token se nikde neukládá napříč běhy.
Notifikace chodí pořád dokola. Porovnáváte absolutní počet nepřečtených místo nárůstu proti minulému běhu.
Známky se na kartě zobrazují jako text s ostrými závorkami. Karta musí být typu Markdown. V kartě typu Entities nebo Entity se ha-alert nevykreslí.
Adresa školy nefunguje. Některé školy mají Bakaláře na vlastní doméně, jiné v podsložce. Rozhoduje to, co vidíte v prohlížeči po přihlášení – API sedí vždycky na stejném základu plus /api/….
Flow ke stažení
Kompletní flow je ke stažení jako JSON. Přihlašovací údaje i adresa školy jsou v něm nahrazené zástupnými hodnotami.
⬇️ bakalari-node-red.json (verze z 2. 9. 2026)
Import: v Node-RED menu → Import → vybrat soubor, pak Import to → new flow.
Co upravit po importu
- Adresu školy ve všech
functionnodech – hledejtevase-skola.bakalari.cz. - Přihlašovací údaje –
VAS_LOGINaVASE_HESLO. Ideálně je rovnou přesuňte dosettings.jspodle sekce o bezpečnosti. - Notifikační nody –
notify.mobile_app_VAS_TELEFONanotify.mobile_app_DRUHY_TELEFON. - Názvy helperů – v mém flow mají v názvu jméno dítěte, protože je hlídám dvě. Pokud sledujete víc dětí, celý tab zduplikujte a přejmenujte helpery.
Co dál
API má i další endpointy, ke kterým jsem se zatím nedostal – suplování (/api/3/substitutions/actual) a rozvrh (/api/3/timetable/actual). Ze suplování by šlo postavit ranní hlášení „dnes odpadá první hodina“, což je informace, kterou by rodič ocenil dřív než v sedm ráno u dveří školy.
Data z Bakaláří se dají poslat kamkoli, kam Home Assistant dosáhne. U nás končí kromě dashboardu ještě na tabletu v kuchyni a v ranním hlášení na reproduktoru. Jakmile máte čísla v helperech, je zbytek jen otázka toho, kde je chcete vidět.
Pokud máte jiný školní systém – Škola OnLine, iŠkola, EduPage – princip zůstává stejný: najít API, přihlásit se, porovnat s minulým stavem. Liší se jen adresy a názvy polí.