VW Group Connect v5.1.0
Home Assistant integration for Audi · VW · Škoda · SEAT · CUPRA · Porsche · VW US/CA.
What's new in this release / Was ist neu in diesem Release
Hinzugefügt / Added
-
Six more readings from the EU Data Act feed: climate humidity and power
draw, two battery states, the battery's own warning field, and the outside
temperature sensor's health. @Massen88's ID.4 Pro reported all six, and the
report is what made them usable rather than merely visible: the official
dictionary this integration ships already lists all six, but with no unit
and no type — the car supplied the units VW left out (Unit_PerCent,
Unit_Watt). Two of the new readings are real measurements, the humidity at
the climate sensor and what heating and cooling are drawing in watts, which on
an electric car is range you can watch. The other four are states, and all six
arrive as diagnostics, switched off by default, on the portal cars whose feed
carries them.Two were traps worth describing, because both are documented by VW's own
catalogue and neither is obvious from the name. The battery's
"charging active" signal latches: it stays active after the ignition goes
on until the standstill flag clears, so it reads active while the car is
merely switched on. It is therefore published as a raw signal and deliberately
never becomes the charging state your automations use — that still comes from
the brand channels. And the leaf whose name reads like an outside temperature
is the outside temperature sensor's fault status; it is published as a
status, and it is genuinely useful, because it says whether the outside
temperature this integration already shows you can be trusted.The humidity reading is named after its source rather than a place: the
catalogue says only "measured relative humidity", and while the signal's
prefix and its sensor module both point at a cabin sensor, nothing available
here proves cabin against intake air. It is kept separate from the existing
outside-humidity reading, which comes off a different channel entirely, so a
car reporting both will settle it (#1863). -
Volkswagen Companion (ADB): VW app 4.8.1 support. The Companion's reads
and controls work again on VW app 4.8.1; departure time and Repeat edits need
a direct ADB connection, and weekday changes stay in the app. Developed and
checked on a Tiguan eHybrid in
gszigethy/vwgroup-connect-ha
(gszigethy#112, gszigethy#113,
gszigethy#115): each read was compared with the app, and
each control was sent once to the car through this code, read back and put
back. Thanks @gszigethy.- Commands on 4.8.1: charging start/stop, the charge target, climate
start/stop, the temperature dial, window heating alone, climate at unlock,
automatic window heating, the climate zones, Synchronise now, and the
departure timers (on/off, time and Repeat). 4.8.1 is listed per command
after its own live test, not in the preset's read list, so app builds
between 4.6.4 and 4.8.1, which nobody has checked, keep their reads and
stay without commands. - Departure timer editor on 4.8.1. The app rebuilt the timer page; its
time picker is hidden fromuiautomatorand its day buttons show no
state. A 6 kB helper (companion/a11y/, Java source and build steps
included, SHA-256 pinned) is pushed over ADB for the edit and removed
afterwards. It reads the hidden wheels and sets them with their own
accessibility action, so the time is checked before Save. A change of
weekdays cannot be checked before Save on this page, so it is refused
there (change the days in the app). The ADB Bridge add-on and the agent
relay cannot run the helper and refuse the edit before changing anything
on the page. - Last long-haul trip: five new Companion-only sensors (distance,
duration, average speed, fuel and electric consumption) from the Driving
data card 4.8.1 added. They appear only on Companion entries once the card
was read, and are not mapped to any cloud or EU Data Act field.
- Commands on 4.8.1: charging start/stop, the charge target, climate
Geändert / Changed
-
The volkswagen.de HTTP/2 channel now uses Home Assistant's own SSL context
instead of a private one. When that channel was built, handing httpx a
shared context was genuinely dangerous: httpx sets the ALPN protocols on
whatever context it is given, so a context shared with the rest of Home
Assistant made every other integration's connections try HTTP/2 and die
with a garbled status line. @eurojojo hit exactly that, and it surfaced as a
bogus "invalid credentials" on an unrelated login — the worst possible
symptom, because it sends people hunting for a password problem they do not
have (#1679). This integration therefore built its own
context, in a background thread, because loading the certificate bundle
blocks. Home Assistant 2026.2 fixed the problem at its root: it keeps a
separate context per protocol set, so the HTTP/2 one it hands out is a
different object from the HTTP/1.1 one everything else uses, and it builds
them all up front. Asking for that one removes the private context, the
background thread and the lock around it, and puts this channel on the
sanctioned route. Nothing changes for cars. Which context gets handed over
is now pinned by a test that checks the object itself, rather than one that
checked how it was built. -
The read-only volkswagen.de channel can now be added to a Volkswagen
Commercial Vehicles entry. @benbrummer could not add it to a T6.1, and when
I went looking for the reason I expected to find one. The whole restriction
was a single equality check at the top of the sign-in step, comparing the
entry's brand against one exact string. Nothing downstream of it is
brand-specific: the connector identifies itself to the site asvolkswagen
either way, because that names volkswagen.de rather than our brand list.
And the two brands are already treated as one thing where it matters most —
Commercial Vehicles uses the same We Connect app, and its EU Data Act
configuration is the Volkswagen one with a different state name. His own
report said the same from the outside: identical entity count and identical
values whether the van is set up as Volkswagen EU or as Commercial Vehicles.What is still not established is whether volkswagen.de serves a Commercial
VIN once the sign-in is allowed to start. That is a question about VW's site,
and the gate was the thing preventing anyone from asking it; the channel is
read-only, so the cost of being wrong is a failed sign-in the owner sees at
once rather than anything silent. If Commercial VINs turn out not to be
served, the gate comes back — this time with a reason attached to it. Every
other brand is still refused, the abort text is unchanged so its twelve
translations stay accurate, and a test checks that the commercial client
really does inherit the arming method, because a widened gate that handed
someone a channel which is stored, shown as configured and never armed would
be a worse outcome than the refusal it replaces
(#1801).
Behoben / Fixed
-
When the manufacturer says "too many requests, wait N seconds", the
integration now waits instead of asking three more times. A throttled
request was retried after 5, 10 and 20 seconds before the account-level pause
engaged — three further requests at the exact moment the backend had said to
stop — and theRetry-Afterheader it sends with that refusal was not read on
this path at all, so the pause that followed was a flat 30 minutes rather than
the time actually asked for, which can be wrong in either direction. With the
header present there is nothing left to find out by asking again: the retries
are skipped and the client holds for what was requested, with a floor so a
one-second value cannot become a loop and a ceiling at the half hour this
kind of refusal already held for, so no header can stall things for longer
than they could be stalled before. A backend asking for more than that is
not waited out in full on purpose: resuming after half an hour now sends one
request rather than four, and if that one is refused the refusal is honoured
in turn. A refusal that carries no
such instruction behaves exactly as before, because that one can still be a
momentary burst.Two smaller things in the same place. The remaining-request-budget headers
were being discarded on precisely the two responses that are about the
budget, so someone sending diagnostics after a lockout could not show us
their quota; they are now kept. And the pause is logged with the number that
produced it.@indigomejor found this in his own lockout log and said what was wrong with
it, after the first half of this issue was already fixed: the lockout came
back at a lower request rate with no second client on the account, which
ruled out the explanation everyone including me was leaning on and pointed
here instead (#1850). -
A portal feed that has stopped no longer overrides your polling interval.
For a car with the EU Data Act portal armed, the integration aligns its next
poll to the portal's own delivery cadence — a snapshot a minute late is worth
catching a minute later rather than a whole interval later. That retry had no
limit, so on a feed that had stopped delivering it fired on every poll: the
expected drop stayed permanently overdue and the cadence stuck at the
three-minute lower bound for as long as the outage lasted, whatever interval
you had configured. The retry now gets a budget of three polls, after which
your configured interval applies again, and the first snapshot that actually
lands restores the alignment. @indigomejor measured this on an Octavia:
ten minutes configured, a flat 3.4-minute cadence across three days, roughly
seventeen polls an hour, ending in a rate-limit lockout — and raising the
interval could not help, because the interval was only ever the upper bound
(#1850). -
The volkswagen.de sign-in works again on accounts that get an e-mail
code. The advertising opt-in VW put in front of the login was already being
declined, but only on the two paths that reach it before the code is
entered. On an account that uses a code, that is never the case: the sign-in
stops at the challenge page to collect the code, and the opt-in appears only
after it is submitted — so for exactly the people reporting this, the fix in
5.0.0 could not run. Entering a correct code ended with "login did not
complete" and asking for a new one could not help, because the next one
landed on the same page. The code step now declines the opt-in as the other
two do, and it does so before judging the landing, which also covers the
opt-in being served with an error status. @nuggie found the cause on 5.0.0
and posted the log line that proves it — a sign-in chain ending at
consent.vwgroup.ioafter the code POST, which only exists to be read
because the same report round added logging to that step. @Fishermanjb
reported it first and supplied the original logs, and @Ra72xx hit it
independently on a second account and confirmed the workaround of declining
the opt-in once in a browser
(#1827). -
Setup no longer fails on Home Assistant versions before 2026.6. Every
release since 4.13.0 stopped during setup withsetup failed: ImportError (message redacted)and then looped on "No vehicles found for this account",
because the repair-notice module imported a name —RepairsFlowResult—
that Home Assistant only began exporting in 2026.6. That name is used
nowhere except in type annotations, so it is no longer imported while the
integration runs. Two things made this far harder to diagnose than it needed
to be, and both are fixed as well:hacs.jsonstill advertised2024.4.0,
so HACS offered the update to precisely the installations it would break (it
now declares2026.6.0), and the setup error withheld everything but the
exception class, so "ImportError" was the entire diagnosis. It now also names
the module and the symbol that could not be imported. Those are plain names;
the exception message and the module's file path stay withheld, because the
message can carry an access token and the path carries the account name of
whoever runs Home Assistant. @thomascombe diagnosed the whole chain on a
Golf 8 GTE — down to the file, the line and the version boundary, having
first added that missing log line locally himself — and confirmed the same
release sets up after upgrading the core (#1858). -
A Škoda account no longer gets rate-limited by this integration's own
retrying. @indigomejor measured an account-level lockout and handed over
what it took to find the cause: roughly 176 requests an hour, an HTTP 429 and
then a hard refusal that pauses the integration for two hours. The official
Škoda channel kept calling after it had started failing, because the guard in
front of it only reacted to an explicit rate-limit answer — a 5xx was
invisible to it, so a channel that was down was asked again every cycle. It
now stands back for 5, then 15, then 30 minutes per channel after repeated
failures, and resets as soon as a call succeeds. The reactive rate-limit
guard still runs first, so nothing that worked before changes. He also ruled
out the duplicate GitHub suggested, on version, cause, remedy and repair
state, before filing (#1850). -
Porsche remote commands no longer report "API error 201" while working.
Every lock, unlock, climate and charging call surfaced an error in Home
Assistant although the car carried the command out. Porsche answers
"201 Created" for an accepted command, and this was the one brand client that
did not count 201 as success. The error message was the smaller half: the
failure happened before the response body was read, and that body is where
the command's own id and its "accepted" verdict live — which this integration
uses to check afterwards whether the car really did take the command. So a
command Porsche went on to reject looked exactly like one it performed.
@tp9csmv79n-code found the cause and verified the fix before filing
(#1853). -
The "EU Data Act portal session expired" repair no longer opens after every restart.
The session had not expired. Since each entry got its own portal cookie jar (#1793), the
startup check for the continuous data request, and the one-time export, still went out on
Home Assistant's shared session. After a restart that session has no portal cookies, so
the portal answered 401 and the repair asked you to sign in again, only for it to come
back on the next restart. Both now use the entry's own portal session for cookie
logins, including supplementary portals. Bearer-only device-grant entries skip the
cookie-only kickoff and must create requests in the portal UI; manual request and
export actions explain this requirement. An entry unloading during a data-request
check or export no longer attempts a closed session or saves a retry timestamp
that would delay provisioning after reload. Thanks @gszigethy. -
The "Unknown user" notice now only says that when the login actually answered it.
17eab6f9(#1809) stopped the repair notice and the setup abort from calling a backend
refusal "an MEB / ID-family car" — an Audi e-tron 55 quattro, a GE-platform car from 2019,
had been told it was an ID.3/ID.4/Enyaq/Born/Q4. What replaced that claim was narrower and
checkable: "the durable Car-Net login answered Unknown user for this car". The rule deciding
when to say that was left exactly as it was, and on that rule the sentence reaches people
whose login never answered it. A bareinvalid_grant— the generic OAuth2 rejection, also
produced by an audience mismatch (#464) and by a refused register — was read as the same
verdict, and the entry's other write site flagged it on ANY mint failure at all, a 5xx or a
timeout included. The one field that would have told the two apart,error_description,
carries the actual "Unknown user" text, but the #1355 redaction dropped it before the setup
flow ever saw it, so even a true claim could not have been substantiated. Measured live
against an Audi e-tron 50 quattro, MY2022 — MLB-evo, not MEB:HTTP 400,error=invalid_grant,
error_description="Unknown user <account-uuid>". The description is now carried on the
exception as an attribute (never in its message — it embeds an account UUID), and a new
classifier decides on it: only a confirmed "Unknown user" keeps the Car-Net wording, and
everything else — a 5xx, a timeout, an audience mismatch, anunauthorized_client— gets its
own notice that says the cause is undetermined rather than guessing one. Ten of the twelve shipped languages still carried the pre-17eab6f9"this is an
ID/MEB model" wording in this same notice and in the setup abort —enanddewere fixed,
the other ten were not caught by the translation-parity test because the keys still existed,
just with the old text — so this PR carries the correction and the new notice to all of
them. Setting up the EU Data Act portal with MBB commands no longer loses the portal when the
command channel fails for any other reason: every mint failure now keeps the read-only
portal entry with the reason recorded, a refusal already at register is classified like one
at the token exchange, a Reconfigure no longer carries an earlier verdict into the next
attempt, and the previous notice is taken down when the reason changes. Measured on, and the
fix contributed by, @woifh's own Audi e-tron. -
A sign-in Volkswagen has walled off no longer costs up to eight rejected attempts and a
wrong explanation. On VW EU and Audi the primary login reaches the very last step — right page,
right POST,authenticated, authorization code in hand — and then the exchange of that code
for a token is refused with400 invalid assertion headers. Nothing about the account is
wrong, but the integration treated it as one more rejection to retry: it walked the whole
client_id × qmauthcandidate chain — eight rejected sign-ins per login on VW EU (four
client ids × two qmauth secrets; the log in #1712 shows attempts #0–#6 moving on, then the
last one failing) and six on Audi (three × two), and a refresh that fails falls back to a
login — and ended on "all candidates exhausted" — a message that reads
like a credentials problem, which is what sent people re-entering a password that was already
correct. It now stops once every qmauth secret has drawn that answer (two attempts on either
brand;
one refused secret alone does not stop it, because the chain keeps the prior pair for VW
re-rotations), and only for Audi and VW, the brands that sign those headers. This is the same
wall as #1165 on VW North America, which has failed fast with its own honest error since
July; the European side simply had no equivalent, so it now gets one — in the config flow,
and also at setup and on re-login, where the read-only portal fallback used to hide it: if
the portal fails too, the entry now stops with this reason and its own repair notice instead
of "invalid credentials". (It already stopped there before rather than retrying, because
"invalid credentials" is a hard setup reason too; what changes is what it tells you.) Why
retrying cannot help was settled against the shipping app rather than assumed: myAudi 5.8.1 carries no
qmauthsymbol in any of its six DEX files or its native libraries, nor the HMAC secret the
integration signsx-qmauthwith, while it does carry the Play Integrity client
(StandardIntegrityManager,requestIntegrityToken) and still sendsx-assertion. The app
has moved from the HMAC scheme to a Google-signed attestation bound to VW's package and
signing certificate, which no open-source client can mint — so there is no candidate left for
the chain to find. That also answers the question #1712 left open, and not in the hoped-for
direction: the credentials have not merely rotated. What changes for you is honesty and cost,
not access — the primary login stays blocked, vehicle data keeps flowing through the
read-only EU Data Act portal where that works for your car, and where it does not, the
integration now says so in every shipped language instead of blaming your password. Measured on an Audi e-tron 50 quattro, MY2022, and
contributed by @woifh (#1712). -
Signing in again with an e-mail address and a password now discards the
expired sign-in it replaces. On an entry first set up with the brand app's
code sign-in, the old token was kept in two places and neither was replaced,
so the reload spent a refresh attempt on a sign-in the manufacturer has
retired before it got round to using the new password — and that wasted
attempt counted towards the pause that protects the account from being
rate-limited. The leftover was also still listed in the diagnostics
afterwards, where it looked exactly like the cause of the failure that
followed; @ratclma spotted it there and asked the right question about it.
Entries that do have a password keep their stored tokens, because for Audi,
Škoda, SEAT and CUPRA the same kind of token is the normal durable channel
(#1813). -
A "portal session expired" notice no longer survives the sign-in that fixed
it. The notice asks you to sign in again, and data arriving through the
portal afterwards is the proof that you did — but clearing it also required a
flag that only existed on the object which had raised it, and reloading the
entry replaces that object while the notice itself persists. So anyone who
reloaded, or whose entry was reloaded for them, kept being told their session
had expired while their car was updating normally. The clear now rests on the
evidence alone. Found by @Anoxym, who read the code and sent the sequence
rather than the symptom (#1867). -
A SEAT or CUPRA entry reading through the portal stops being nagged about an
access path it no longer uses. The notice about outdated app headers is
cleared for a portal-served entry, and has been since 4.7.x — but only from
inside the polling loop, so an entry whose poll does not complete kept the
notice for as long as the entry existed. @georghaberkorn68-ctrl reported one
standing at 535 since August, next to a car whose data was arriving fine. It
is now also cleared when the entry starts up. An entry that is not reading
through the portal keeps its notice, including across a restart: a real
access problem should not blink out and come back
(#1866). -
When the SEAT/CUPRA user lookup fails, the log now says what happened. It
used to print one sentence with nothing in it, so a refused request, a
forbidden one, a timeout and a malformed answer were indistinguishable — and
@georghaberkorn68-ctrl spent a twelve-minute debug capture on a question that
no log this code could produce was able to answer. The exception and, where
there is one, the HTTP status are now named. Deliberately nothing else: that
error object also holds the request address and the response body, which are
the two things this line must never grow
(#1866). -
Diagnostics now name the portal field a value came from, not only the
channel. @Joassens reported a Passat GTE whose fuel level sat at exactly
100 % across 467 km of driving while the tank went to half, killed his own
"stale because I hadn't burned any fuel" theory with the odometer, and ended
with the sentence this change exists for: "I can't tell which from
diagnostics, since they don't include the raw portal field."He was right, and nothing in the file could have told him. The portal parser
reads most fields from a list of candidate leaf names, first present wins —
the fuel level alone has five.field_sourcesrecorded which channel won
andfield_captured_tswhen the car measured it; neither recorded which
leaf won. Elimination could not recover it either, becausefirst()marks
every candidate of a call as consumed along with their walker synonyms, so all
five vanish from the unmapped-field list together. And the function that
stashes the provenance was already being handed the chosen leaf — it used it
to look up a timestamp and then discarded the name.It is kept now, as
field_source_leaf, following the same path as its two
siblings: never gap-filled field by field, rebuilt in the channel merge from
whichever channel owns the surviving value, and carried with the value through
a historical import. That last part is the one with teeth — a leaf name that
outlived the value it described would blame a portal field for a reading that
came from somewhere else, which is worse than recording nothing, so a test
measures a merge where one field keeps its leaf and another loses it in the
same pass. The fuel level, having now been disputed by two reporters, was also
added to the handful of fields that record their provenance at all
(#465). -
A refused command now explains itself in the log, not only in the Home
Assistant dialog. @Joassens tested the MBB client-identity refusal on a
Passat GTE and confirmed the message reads correctly in the UI — the car's
gateway accepted the S-PIN and then refused this integration's client
identity, none of it the owner's fault. Then he looked at the log file for the
same event and found one line:command_start_climate(***074295) failed: VehicleCommandError (status=None). The whole explanation lived only in the
error the UI surfaced, so anyone working from a log — which, when a command
fails, is usually the person who was asked to send one — got a class name and
a null status.The reason a command error carries is now part of that error line. It applies
toVehicleCommandErroralone, because every reason that class carries is
prose written for the owner and already shown to them; for every other
exception the line still logs the class and HTTP status only. That restriction
was already there and stays, but its stated justification was out of date: it
said a raw exception string can carry the VIN-bearing command URL, and
APIErrorhas masked the VIN out of its own message since the #1355 redaction
work. What the restriction actually protects against is the arbitrary
exceptions that land in the same handler — a network or ADB library error
whose text nobody here has vetted. Both halves of that are now pinned by
tests, including one that fails ifAPIErrorever stops redacting, so the
comment above the log line cannot quietly become wrong again
(#584). -
A Companion screen that could not be read is no longer reported as a screen
with nothing on it, and one failed read no longer costs half an hour of
them. Two defects of the same shape, found by reading the three Companion
transports against each other.The direct-ADB transport has refused a screen dump with no node tree in it
since v2.26.0, because a faileduiautomatorrun leaves its output file
untouched and the previous screen would otherwise be parsed as the live one.
The phone-agent relay overrode that method and returned whatever the agent
sent, so an agent that walked the accessibility tree and found no window —
display off, a secure screen, the car app not in front — reported success with
nothing in it. Zero nodes is what the read path treats as a car that had
nothing to say: the overview anchor check is skipped, the adaptive failure
backoff is reset, and the poll is recorded as healthy. The one outcome that
most needed to be visible was the only one that left no trace anywhere. The
relay now applies the same guard as its sibling.And on the ADB side a single empty dump bought the full failure backoff — 30
minutes, doubling per consecutive failure to six hours — with no second
attempt. Butuiautomator dumpgives up when the screen does not go idle,
and an animation or a passing toast is enough to cause that; it is gone a
second later. The dump is now retried once, after a short settle, before it
becomes a failure, and the error says it tried twice. Three deliberate limits:
the retry re-runs the whole command rather than only re-reading the dump file,
so the stale-screen protection stays intact; a transport error is not
retried, because the connection has already been dropped for the next read to
rebuild and a second attempt could only report "not connected" over the real
reason; and the healthy path still costs exactly one round trip. On the relay
there is no retry, because another attempt there means another trip through
the agent's own poll and the usual cause of an empty tree does not change in
that time. -
The token refresh limit no longer announces a pause it never takes, and no
longer reports itself at error level once every poll. The limit itself is
unchanged and correct: three refreshes per rolling hour, which is what keeps a
misbehaving entry from hammering the sign-in until the account is locked. What
was wrong was everything it said about itself. The message read "pausing to
prevent IP ban; please reauthenticate from the UI", and nothing paused — the
call refuses one attempt and returns, so the next poll asks again and is
refused again. @georghaberkorn68-ctrl's CUPRA logged that sentence three times
per cycle, every three minutes, for hours across two days, which reads as a
runaway loop rather than as a limit doing its job. And the one instruction it
gave could not help him: his primary sign-in was being refused outright before
the portal fallback took over, so no credential he could type would change the
answer.The refusal now says what it does — that it is refusing further refreshes, and
roughly when the next one is allowed, computed from the oldest attempt still
inside the window rather than from a constant. It is logged at error level
once per episode and at debug level while the budget stays spent, and the
"episode" resets as soon as an attempt is actually booked, so a genuinely new
storm weeks later is reported as news again. Where credentials are mentioned
at all, it is to say that re-entering them will not help when the sign-in
itself is what is being refused, and the errors that say why it is refused
sit directly above the line. The same guard exists a second time in the
Porsche client, which is not a subclass and had its own copy of both the
wording and the level; both are fixed, and a test fails if a third copy of the
old sentence ever ships
(#1866). -
Volkswagen Companion (ADB) reads on VW app 4.8.1. The parking position
read taps the button around the app's Vehicle location label (4.8.1 made
the label itself not clickable) and never the map. The Driving data
carousel is read whatever card it opens on; the first read sweeps both ends
and remembers which cards exist, again daily. Departure timers read their
weekdays from the list's recurrence label and Repeat from the new editor
(gszigethy#112, gszigethy#113). -
Importing a historical export that no longer exists now says so, instead of
showing a green tick. @placidcasual98 confirmed that after v5.0.0 the
import works — battery level, electric range, charging schedule and the three
departure timers all arrived on his car and stayed — but he got there the slow
way. His first attempt ran against an export that had expired off the portal,
andvag_connect.import_historical_exportreturned quietly, which ends the
service call normally and makes Home Assistant draw a success tick. Nothing
had been imported. He only worked out why by opening the portal himself, and
said what would have saved him the time: "A message saying no file was found
would have saved me some time."The service already logged which of the failure modes it was; what it did not
do was fail. It now raises, exactly as the neighbouring branch already does
when the portal is not ready at all, and the message carries the portal's own
reason —no_requestmeans the one-time export was never requested or has
since expired,emptyorno_contentmeans the portal took the request but
has not produced the file yet. In all twelve languages. A failed import still
writes nothing, pushes nothing and leaves the snapshot untouched, which is the
half of the old behaviour that was always right
(#1403). -
A Companion cancellation test no longer fails at random.
test_cancelling_during_the_upload_lets_it_finish_before_cleanupwaited a
fixed 0.05 s and then assumed the helper upload was in flight. In a full-suite
run on a loaded thread pool it sometimes is not: the work item is still queued,
cancelling drops it before it ever starts, and the test then fails for the one
reason that is not a defect — there was no upload to let finish, and so no
cleanup race either. It now waits for the upload thread to actually start
before cancelling, which is what the test's own name claims it does. No
production code changed; the behaviour it guards is unaltered. -
A portal session that has expired no longer makes the integration re-post a
data request and re-raise the same notice on every restart. @alagerqv read
the kickoff loop and found what his own entry was doing: his recorded attempt
time sat frozen at 10 October, long past the 24-hour window that is supposed
to hold the next attempt off, so every setup tried again and raised the
"portal session expired" card again.The cause is one word. The loop records the attempt before posting, with a
comment explaining that the record is what makes the backoff survive a
restart — and the session-expired handler then returned straight out of the
method, past the only line that writes it. The promise in the comment and the
behaviour of the code had been disagreeing since the handler was added. It
breaks out of the loop now instead, so the record reaches the write.That alone would have fixed only half of it. The handler also records the
attempt itself when nothing has yet: a refused probe raises before the
record is made, and a probe the portal refuses is a failed attempt exactly as
much as a refused post is. An identifier adopted for an earlier vehicle in the
same pass now survives too, instead of being discarded along with everything
else.And three places raised this condition without logging anything, which is why
the card turned up in his log with no line next to it saying where it came
from — one of them sitting directly above a sibling branch that has always
logged its status. All three now say what happened, and the card itself
announces that it is being raised at a level a normal installation records
(#1867). -
A trigger or condition scoped to one car no longer applies to every car on
the account when the scope cannot be resolved. The helper that turns a
config'svin:option ortarget: device_idinto a set of VINs returned an
empty set for four different situations and documented one of them: nothing
was asked for; a config shape we cannot read; adevice_idthe registry no
longer knows, which is what a car removed and re-added leaves behind; and a
target:expressed with any other field, such as an area or a label.Both callers then failed open on all four. The trigger ended up subscribing
withvin=None, which is the account-wide listener the dispatcher fires for
every VIN, and the condition'sif vins and …guard is skipped entirely on a
falsy empty set, so every vehicle was tested. The two cases that say "I meant
one car" therefore did the opposite of what was asked, and said nothing about
it — an automation written to unlock-trigger on one car ran for all of them.The helper now answers three ways instead of two: nothing asked for, a
resolved scope, or asked and resolved to nothing — and the last of those
matches nothing rather than everything. It also logs a warning naming the
target that could not be resolved, because an automation that has been firing
account-wide by accident would otherwise just stop one day with no
explanation. Unscoped triggers and conditions are untouched and still apply
to every vehicle, which is their documented behaviour. -
Two portal buttons no longer show a success tick over a refusal.
@Dulcow pressed "Create EU Data Act data request", watched his activity list,
and saw the press recorded and then nothing at all — no success, no failure.
He found out only by opening the portal himself that no request had been
created. The integration had the portal's answer the whole time: it records
the status for diagnostics, and had no way to reach the person standing in
front of the button.Its sibling had the same shape. The one-time historical export returned
quietly both when the portal refused the request and when the session had
expired, so that button reported success too — while the import half of the
same feature was changed for exactly this a few hours earlier. Two halves of
one feature should not disagree about whether a failure is worth mentioning.Both now say what happened, with the portal's own reason where there is one,
in all twelve languages. The create button passes its vehicle so the answer
is about that car, and clears any verdict left by an earlier press so the
message belongs to the press you just made. Two things deliberately did not
change: a successful press is still silent, and a refused one still leaves
nothing behind — in particular a refused export does not start the
client-side "pending" clock, which would have shown an export that does not
exist. The export's error is raised by the button rather than deeper down,
because the same code also runs while Home Assistant is shutting the entry
down, where a failure is neither a refusal nor something anyone is waiting
to be told about. -
A Volkswagen server outage or a rate-limited account is no longer reported
as a wrong password. The login walks a list of strategies, and its catch-all
redacts the message of whatever the last one raised — for a good reason,
written at the site: a low-level network error's text can carry the full OAuth
callback URL, and those tokens decode to the owner's e-mail and a working
access token. That redaction stays. What was wrong is that the error's class
was thrown away with its message, so everything came out as a plain
authentication failure.That was never only a wording problem. On the polling path an authentication
failure becomes "invalid credentials" and starts a re-authentication flow, so
a VW 5xx storm or an account their backend had temporarily rate-limited asked
people to re-enter credentials that were never wrong — the exact
reconfiguring-during-an-outage that the dedicated error classes were created
to prevent, with their own translated messages already sitting in the config
flow and their own handling already in the coordinator's self-healing-error
check. All of it was simply unreachable from that oneraise.Three classes now come through as themselves: the backend-unavailable error,
the rate-limit error, and the volkswagen.de site-unavailable error. The bar
for being on that list is deliberately narrow and is pinned by a test: the
message has to be one this integration wrote, built from a status code and a
brand name, and must never be able to carry a URL, an address or a token.
Everything else, including an error carrying a request URL of our own, is
still redacted and still flattened exactly as before
(#1855).
Sicherheit / Security
-
Sign-in host hardening. Two guards in the login code decided whether a
host was the manufacturer's by looking for a name inside it rather than
matching the name itself, and one step followed a continuation link offered
by a page without checking where that link pointed. All three now match on a
name boundary, and the link is followed only back to the page that offered
it, to the manufacturer's own sign-in hosts, or to the portal.Nothing about a working sign-in changes. The same reference
implementations the portal operator points maintainers at already validate
the same thing, every legitimate value in the project's own fixtures was
checked against the new rule, and a live read-only portal login completes
unchanged. A value outside that set now ends the sign-in with the same notice
an absent one already produced, instead of being followed, and the notice
names the page rather than the value.
Full diff vs v5.0.0 / Kompletter Diff zu v5.0.0
Every commit, file and code change between the two versions:
Jeder Commit, jede Datei und jede Code-Änderung zwischen beiden Versionen:
Recent releases / Letzte Releases
If you're upgrading from an older version, also read these notes:
Wenn du von einer älteren Version updatest, lies auch diese Release-Notes:
Full history: CHANGELOG.md
Installation / Update
HACS users: the update notification appears automatically within ~6 hours of the release.
HACS-User: Der Update-Hinweis erscheint automatisch innerhalb von ~6 Stunden nach dem Release.
Manual install / Manuelle Installation:
- Download
vag_connect.zipfrom this release /vag_connect.zipaus diesem Release herunterladen - Unzip and copy
custom_components/vag_connect/to your HAconfig/custom_components/folder, replacing any older copy / Entpacken und den Ordnercustom_components/vag_connect/in dein HAconfig/custom_components/kopieren — alten Ordner ersetzen - Restart Home Assistant / Home Assistant neu starten
Supported brands / Unterstützte Marken
Volkswagen EU · Audi · Škoda · SEAT · CUPRA · Volkswagen US/CA (Beta) · Porsche (Beta)
Found a bug? / Bug gefunden?
Open an issue with the bug-report template: https://github.com/its-me-prash/vwgroup-connect-ha/issues/new/choose
Bug-Report-Template: https://github.com/its-me-prash/vwgroup-connect-ha/issues/new/choose
Please mask your VIN to the last 6 characters and remove email/tokens/GPS before posting.
Bitte VIN auf die letzten 6 Zeichen kürzen und Email/Tokens/GPS vor dem Posten entfernen.