Troubleshooting - HeikoGr/MMM-Webuntis GitHub Wiki

Troubleshooting

No Data Appears

Check these first:

  1. Verify the Node version is at least 22.22.1.
  2. Confirm the auth method matches the account type.
  3. Set logLevel: 'debug' (and add DEBUG to the global logLevel in config.js).
  4. For parent setups, confirm students: [] is present if you expect auto-discovery.
  5. Make sure the selected date range actually contains timetable data.

A Reload Does Not Reset The Retry Backoff

The backend owns the fetch schedule. After a failed fetch (wrong password, server unreachable) it retries after 2 minutes, then 4, 8 … up to 30 minutes, and an instance stays alive for 10 minutes after its last display went away. Reloading the browser therefore does not force an immediate new login attempt; restart MagicMirror (or wait for the next retry) after fixing the credentials.

How Often The Module Logs In

The backend keeps one WebUntis session per account. The bearer token lives about 15 minutes, and the module renews it from the session cookie shortly before it expires instead of logging in again (debug line token renewed from the existing session); a real login follows when that no longer works and at the latest after six hours. logLevel: 'info' shows one line per real login (REST auth: logging in ...).

How many logins you see depends on how busy the account is: the WebUntis REST session idles out after about five minutes without a request. With several instances or Carousel pages of one account the session stays warm and you see about one login per 15 minutes. A single instance stays warm with the default of 4 minutes as well (the longest gap is 4:24 with the ±10 % jitter). With an updateInterval of 5 minutes or more a single instance sits on or beyond that limit: on many fetches it starts with a 401 (Authentication token expired, invalidating cache and retrying... in the debug log) and recovers with a login. That still works, it only costs one extra request and a login per fetch.

If a login fails, the module waits 30 seconds before it tries again with the same credentials, and a 401 no longer keeps the timetable away afterwards. When a login replaces a session that had only aged out, the old one is logged out in the background, and on shutdown every cached session is.

Which Log Level To Pick

All output goes through MagicMirror's Log: the global logLevel in config.js is the upper bound, the module's logLevel can only narrow it. Debug lines need DEBUG in the global list.

logLevel: 'info' is the level for "data disappears now and then". It stays quiet during normal operation and prints exactly two kinds of auth line:

  • one line per real login (REST auth: logging in ... or QR Auth: Starting authentication ...) - cached sessions are reused silently, so every line here is an actual login
  • one line per invalidated session (Invalidating expired token cache)

A login without a preceding invalidation means the token simply aged out. A login right after an invalidation means something rejected the session. Correlating those timestamps with the moment data vanished is usually enough to tell a session problem from an empty timetable.

logLevel: 'debug' adds cache hits, per-request details and payload summaries - useful once you know where to look, too noisy to watch for hours.

Empty View But Data May Have Failed To Load

An empty timetable view does not always mean that there is really no school on that day.

Current runtime behavior to keep in mind:

  • the backend uses a timetable-first fetch strategy because timetable is the auth canary
  • the frontend can preserve older data when a new fetch fails critically
  • if no usable timetable data remains for a day, the grid plugin falls back to no-lessons
  • the lessons plugin may render its empty row text even though the interesting question is really whether data was unavailable or the day was genuinely empty

For investigations, verify these separately:

  1. Was timetable fetching attempted at all?
  2. What does the API status snapshot report for timetable?
  3. Were warnings or warningMeta entries emitted for auth, network, or server errors?
  4. Does the selected date range actually include lessons?
  5. Do debug dumps or PM2 logs show an upstream empty response, a timeout, or an auth refresh?

Telling the four empty states apart

With dumpBackendPayloads: true, open the newest debug_dumps/<timestamp>_<student>_api.json and look at state.collections first - it says per collection whether the latest fetch succeeded (ok), failed or was skipped (unavailable) or was not requested (disabled), and when the last success was (lastSuccessAt). Then state.fetch, state.api, state.warnings, data.lessons and data.dayNotices:

State state.fetch.timetable state.api.timetable state.warnings data.lessons What the widgets show
Timetable never requested false (no grid/lessons plugin) or true with nextDays: 0 null – [] plugin not rendered, or rendered empty
Fetch failed (network, 5xx, 401) true 0, 5xx or 401 network/auth/server warning [] previous data is kept if the session had any; otherwise kein Unterricht plus a warning banner
Endpoint forbidden (403, remembered for 24h) true 403 none [] previous data is kept if any; otherwise kein Unterricht without a warning
Session cookie expired (idle > ~5 min) and the automatic re-login failed true 401 auth warning (session expired / Authentication failed) [] previous data is kept and state.collections.lessons.status is unavailable; a fresh session shows Daten nicht verfügbar per day plus the warning banner
Timetable body without days[] (maintenance page, proxy error) true 0 unusable response warning [] previous data is kept; without previous data: Daten nicht verfügbar
Plan locked by the school (NOT_ALLOWED) true 200 none [] Plan gesperrt (from data.dayNotices[].kind = timetable-restricted)
Genuinely no lessons true 200 none [] kein Unterricht; WebUntis marks such days NO_DATA, forwarded as data.dayNotices[].kind = no-data

Plan gesperrt and a genuinely lesson-free day both come with state.collections.lessons.status = ok; the raw raw_api_*_timetable.json dump written by dumpRawApiResponses: true shows the per-day status (NO_DATA vs. NOT_ALLOWED). Failed requests are dumped as raw_api_*_<type>_error.json with status, error code, redirect location and a body snippet.

Useful references:

  • docs/SERVER_REQUEST_FLOW.md
  • docs/API_V3_MANIFEST.md
  • docs/ARCHITECTURE.md

QR Login Does Not Work

  • Check that the QR code still matches the right account.
  • Prefer QR for SSO-backed accounts.
  • Re-scan the code if the original value was copied manually.

Username / Password Login Does Not Work

  • Re-check school and server.
  • Make sure the account really supports direct login.
  • If the school uses SSO, switch to QR.

The Module Starts But A Widget Is Missing

  • Check displayMode first.
  • Then review plugin-specific plugins.<id>.config settings.
  • Test with a simpler mode such as lessons, exams.

Useful Commands

node --run check
node --run debug
node --run test:auth:curl
pm2 logs --lines 100

When You Need More Detail

The repository still contains technical diagnostics and internals in docs/, especially:

  • docs/CLI.md
  • docs/API_REFERENCE.md
  • docs/SERVER_REQUEST_FLOW.md

If you are debugging module internals rather than just setting it up, start there.

⚠️ **GitHub.com Fallback** ⚠️