Skip to content

update_data(): failed MU Opgaver call aborts the whole update; unguarded .json() in ugeplaner takes all entities unavailable #372

Description

@dennis-mathew-maersk

Two related defects in AulaChildClient.update_data(), both triggered when Aula
returns a non-JSON body (session expiry, rate limiting, or an Aula outage serving an
HTML error page). Line numbers are against main @ 4a0e7f2 / release v0.1.66.

Observed on Aula 0.1.66, Home Assistant 2026.8.1, ZHA/HAOS, two children at a school
that exposes neither widget 0030 nor 0023.

Problem 1 — a failed MU Opgaver call aborts the entire update, silently skipping ugeplaner

client.py:940-951:

if self._mu_opgaver is True:
    try:
        guardian = self._session.get(
            self.apiurl
            + "?method=profiles.getProfileContext&portalrole=guardian"
            + self._get_access_token_param(),
            verify=True,
        ).json()["data"]["userId"]
    except Exception as e:
        _LOGGER.warning(
            f"Error retrieving MU Opgaver: Empty or ambiguous response: {e}"
        )
        return          # <-- returns from update_data(), not from the MU section

That is a bare return out of update_data(). The Ugeplaner block lives below it at
client.py:1007, so a failure in MU Opgaver means ugeplan/ugeplan_next are never
refreshed on that cycle
. Nothing in the log connects the two — the only trace is a
WARNING naming MU Opgaver, while the visible symptom is stale schedule data:

WARNING (SyncWorker_3) [custom_components.aula.client] Error retrieving MU Opgaver: Empty or ambiguous response: Expecting value: line 1 column 1 (char 0)

This is worst for users who have mu_opgaver enabled but whose school exposes no
supported widget: the feature can never return anything (mu_opgaver attribute reads
"Min Uddannelse Opgaver not available"), yet its failure path still takes down the
schedule data that does work.

Suggested fix: scope the early exit to the MU section so one optional feature cannot
abort the others — e.g. wrap the MU block in its own function and return from that,
or replace the bare return with a flag that skips only the MU work.

Problem 2 — unguarded .json() in the ugeplaner block takes all entities unavailable

client.py:1007-1017:

if self._ugeplan is True:
    guardian_response = self._session.get(
        self.apiurl
        + "?method=profiles.getProfileContext&portalrole=guardian"
        + self._get_access_token_param(),
        verify=True,
    ).json()                                    # <-- not guarded
    guardian_data = guardian_response.get("data") if guardian_response else None
    if not guardian_data or "userId" not in guardian_data:
        _LOGGER.warning("Could not get guardian userId for ugeplaner")
        return True

The .get("data") result is carefully null-checked on the next line, but the .json()
call itself is not protected. On a non-JSON body it raises
json.decoder.JSONDecodeError, which propagates out of update_data() into the
DataUpdateCoordinator created in sensor.py:65, so the coordinator marks the update
failed and every Aula entity goes unavailable:

ERROR (MainThread) [custom_components.aula.sensor] Error requesting sensor data: Expecting value: line 1 column 1 (char 0)

Note the MU Opgaver block at line 940 already wraps the identical request in
try/except — the guard is just missing here.

Suggested fix: wrap in try/except and fall through to the existing
"Could not get guardian userId for ugeplaner" path, so a transient Aula hiccup leaves
the previous values in place instead of dropping every entity to unavailable.

Why this combination bites

With both defects present, a single bad reply from Aula either silently skips the
schedule refresh (Problem 1) or drops every entity to unavailable (Problem 2).
Downstream automations that read ugeplan — in my case a TTS announcement of the
kids' timetable each morning — then have to treat "integration is briefly unhealthy"
and "no school today" as the same state.

Happy to open a PR for either or both if the maintainers would like.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions