Custom Home Assistant integration for DSB Mobile – the German school substitution plan system used by many schools in Germany.
| Sensor | Description | Icon |
|---|---|---|
| Vertretungsplan | Per-student substitution plan with merged timetable | 🏫 |
| Schulinfo | Daily school announcements and metadata | 📋 |
| Raw Sensor | All raw DSB data for debugging (optional) | 📊 |
- 🧒 Child name & class in setup flow – determines entity IDs and sensor naming
- 📅 YAML timetable support – provide a custom timetable file for schedule merging
- 🔄 Manual fetch – data is fetched on demand via
dsb_api.fetch_updatesservice - 🔃 Live schedule reload – reload timetable YAML via
dsb_api.reload_schedulewithout restarting HA - 🏷️ Smart sensor naming – entity IDs follow
sensor.dsb_[child]_[class]_vertretungsplanpattern - 👨👩👧👦 Multi-child support – add the integration multiple times, one per child
- 🔐 Secure authentication – DSB Mobile API credentials validated at setup
- 📏 Recorder-safe – raw debug sensor disabled by default to avoid large attributes
- 🔑 Persistent hash storage – change detection hashes stored per child for automation use
When a YAML timetable is provided, the integration merges the regular timetable with DSB substitution data. Each lesson gets a status:
| Status | Meaning |
|---|---|
normal |
No changes – regular lesson |
vertretung |
Substitution teacher or room change |
entfall |
Lesson cancelled |
verlegung |
Lesson moved from/to another slot |
raum_aenderung |
Room changed |
betreuung |
Supervised study period |
The timetable supports split classes (e.g. two language groups, religion/ethics groups, or gender-separated sports) through an exclude list that filters out lessons not relevant to the specific student [1].
- Open HACS in Home Assistant
- Click ⋮ (top right) → Custom repositories
- Add
https://github.com/workFLOw42/DSB_Mobile_Apias Integration - Search for DSB Mobile API and install
- Restart Home Assistant
- Download the latest release
- Copy the
custom_components/dsb_apifolder to yourconfig/custom_components/directory - Restart Home Assistant
Step 1 – Credentials:
- Go to Settings → Devices & Services → Add Integration
- Search for DSB Mobile API
- Enter your DSB Mobile credentials:
| Field | Description | Example |
|---|---|---|
| Username | School ID for DSB Mobile | 12345 |
| Password | Password for DSB Mobile | •••••••• |
Step 2 – Child / Student:
- Enter a name or short code and the class:
| Field | Description | Example |
|---|---|---|
| Child name | Short code for the student | MAX |
| Class | Class name as shown in DSB | 7B |
This determines entity IDs and the default timetable filename:
| Input | Entity ID | Schedule File |
|---|---|---|
MAX + 7B |
sensor.dsb_max_7b_vertretungsplan |
MAX_Stundenplan.yaml |
LISA + 5A |
sensor.dsb_lisa_5a_vertretungsplan |
LISA_Stundenplan.yaml |
⚠️ Entity IDs are set during initial setup and cannot be changed later.
Step 3 – Schedule Settings:
- Optionally provide a timetable YAML file (must be in your HA config directory)
- Optionally enable the Raw Debug Sensor
Add the integration once per child:
- Settings → Devices & Services → Add Integration → DSB Mobile API
- Use the same credentials but a different child name and class
- Each child gets its own set of sensors with unique entity IDs
- Go to Settings → Devices & Services
- Find DSB Mobile API and click Configure
- Change child name, class, schedule file, or raw sensor toggle
The integration can load a custom YAML timetable from your HA config directory. This enables merging the regular timetable with DSB substitution data.
meta:
schueler: "MAX"
klasse: "7B"
jahrgangsstufe: 7
schule: "myschool"
exclude:
- fach: "L2" # Student takes French, not Latin
- fach: "K" # Student has Ethics, not Catholic Religion
- fach: "Ev" # Student has Ethics, not Evangelical Religion
- fach: "Sw" # Student has male sports (Sm)
- fach: "E1"
lehrer: "SMI" # Student is in the other English group
stundenplan:
montag:
"1": { fach: E1, raum: "108", lehrer: MUL, uhrzeit: "08:00-08:45" }
"2": { fach: E1, raum: "108", lehrer: MUL, uhrzeit: "08:45-09:30" }
# ...
dienstag:
# ...The YAML can include additional blocks used by calendar sync automations:
zeitraum:
start: "2026-02-04"
ende: "2026-07-31"
sensoren:
schulaufgaben: "sensor.myschool_max_schulaufgaben"
termine: "sensor.myschool_max_termine"
vertretungsplan: "sensor.dsb_max_7b_vertretungsplan"
ogts:
betreuung: "Jane Doe"
montag: { start: "13:05", ende: "16:00" }
donnerstag: { start: "13:05", ende: "16:00" }
termine_filter:
include_keywords:
- "7."
- "7B"
- "Unterstufe"
exclude_keywords:
- "Q12"
- "Q13"
- "Abitur"
ferien_keyword: "ferien"
feiertag_keywords:
- "unterrichtsfrei"
- "Buß- und Bettag"
- "Christi Himmelfahrt"
emojis:
schulaufgabe: "✒️"
ogts: "📝"
schultermin: "🏫"
ferien: "🏖️"
feiertag: "🎇"
kurzstunden: "🌞"
vertretung: "☢️"
entfall: "👻"Schools often split classes into groups for languages, religion/ethics, or sports. The exclude list filters out lessons that don't apply to the specific student [1]:
| Example | Reason |
|---|---|
fach: "L2" |
Student takes French, not Latin |
fach: "K" |
Student has Ethics, not Catholic Religion |
fach: "Sw" |
Student has male sports |
fach: "E1", lehrer: "SMI" |
Student is in the other English group |
After editing the YAML, reload it without restarting HA:
service: dsb_api.reload_scheduleThis integration uses manual fetch only (no automatic polling). To fetch data:
service: dsb_api.fetch_updatesautomation:
- alias: "Fetch DSB data every 15 minutes on school mornings"
trigger:
- platform: time_pattern
minutes: /15
condition:
- condition: time
after: "06:45:00"
before: "14:00:00"
- condition: state
entity_id: input_boolean.ferien_status_heute
state: "off"
action:
- service: dsb_api.fetch_updatesGo to Developer Tools → Services → search for dsb_api.fetch_updates → Call Service
State: 2|a3f8c2d1 (change count + data hash – state only changes when actual data changes)
Attributes:
days:
"2026-03-05":
wochentag: donnerstag
schedule:
"1":
fach: M
raum: "110"
lehrer: GEI
uhrzeit: 08:00-08:45
status: normal
vertreter: null
dsb_text: null
dsb_art: null
"3":
fach: G
raum: "008"
lehrer: BRE
uhrzeit: 09:50-10:35
status: entfall
vertreter: null
dsb_text: "G (BRE) 3. Std. entfällt"
dsb_art: Entfall
changes:
- stunde: "3"
status: entfall
fach: G
text: "G (BRE) 3. Std. entfällt"
change_count: 1
dates:
- "2026-03-05"
schedule_raw:
montag:
"1": { fach: E1, raum: "108", lehrer: MUL, uhrzeit: "08:00-08:45" }
# ...
config:
meta: { schueler: MAX, klasse: 7B, ... }
zeitraum: { start: "2026-02-04", ende: "2026-07-31" }
ogts: { ... }
lehrer_profil: [MUL, GEI, BRE, ...]
emojis: { ... }
hashes:
exams: "a1b2c3d4"
termine: "e5f6g7h8"
yaml: "i9j0k1l2"
klasse: "7B"
schueler: "MAX"
schedule_file: "MAX_Stundenplan.yaml"
last_updated: "2026-03-05T07:45:00"State: 2 (number of days with info)
Attributes:
tage:
"2026-03-05":
title: "Vertretungsplan Donnerstag 05.03.2026"
nachrichten:
- "Bitte beachten Sie die geänderten Pausenzeiten."
stand: "05.03.2026 07:30"
klassen_betroffen: ["5A", "6D", "7B", "10B"]
total_eintraege: 12
dates:
- "2026-03-05"
last_updated: "2026-03-05T07:45:00"The integration provides persistent hash storage for change detection in automations. Hashes are stored per child in .storage/dsb_[child]_hashes.json.
service: dsb_api.set_hash
data:
child_name: "MAX"
hash_key: "exams" # exams, termine, or yaml
hash_value: "a1b2c3d4"Hashes are exposed in the Vertretungsplan sensor attributes:
{{ state_attr('sensor.dsb_max_7b_vertretungsplan', 'hashes').exams }}
{{ state_attr('sensor.dsb_max_7b_vertretungsplan', 'hashes').termine }}
{{ state_attr('sensor.dsb_max_7b_vertretungsplan', 'hashes').yaml }}type: markdown
title: 📋 Vertretungsplan Heute
content: >
{% set today = now().strftime('%Y-%m-%d') %}
{% set days = state_attr('sensor.dsb_max_7b_vertretungsplan', 'days') %}
{% set day = days.get(today, {}) %}
{% set changes = day.get('changes', []) %}
{% if changes | length > 0 %}
{% for c in changes %}
- **{{ c.stunde }}. Std.** {{ c.fach }}: {{ c.status }}
{% if c.text %}– {{ c.text }}{% endif %}
{% endfor %}
{% else %}
Keine Änderungen heute ✅
{% endif %}type: markdown
title: 📢 Schulnachrichten
content: >
{% set info = state_attr('sensor.dsb_max_schulinfo', 'tage') %}
{% for date, data in info.items() %}
**{{ date }}** (Stand: {{ data.stand }})
{% for msg in data.nachrichten %}
- {{ msg }}
{% endfor %}
{% endfor %}| Problem | Solution |
|---|---|
| Login fails | Verify credentials at dsbmobile.de |
| No data after setup | Call dsb_api.fetch_updates manually |
| Schedule not loaded | Check file exists in /config/ and call dsb_api.reload_schedule |
| Wrong lessons shown | Review exclude rules in your YAML – check split class groups [1] |
| Sensor shows 0 changes | This is correct if there are no substitutions today |
| Entity ID wrong | Entity IDs are set at setup – delete & re-add integration |
logger:
default: info
logs:
custom_components.dsb_api: debug- 🧒 Child name & class in setup flow – determines entity IDs and default schedule filename
- 👨👩👧👦 Multi-child support – add integration multiple times with different children
- 🏷️ Smart sensor naming –
sensor.dsb_[child]_[class]_vertretungsplan - 🔑 Persistent hash storage –
dsb_api.set_hashservice for automation change detection - 📝 Extended YAML support – ogts, termine_filter, emojis, zeitraum blocks
- 🔧 Config data in sensor – full YAML config exposed as
configattribute - 🧑🏫 Automatic teacher profile –
lehrer_profilcomputed from timetable - 🔀 Verlegung status – lesson relocations now detected separately
- 🐛 Fixed: OptionsFlow compatibility with HA 2024.x+
- Stability improvements
- Schedule YAML support
- Exclude rules for split classes
- Manual fetch via service
- Initial release
This project is licensed under the MIT License.
- Built for Home Assistant
- Data provided by DSB Mobile by heinekingmedia GmbH
- Inspired by ha-deutsche-ferien and Elternportal_API