Toyota: the Subaru realm, and the header the app now sends everywhere

pytoyoda moved from 5.2.0 to 5.2.7 in the four days before this, and most of
it is Pydantic defaults and pagination that a connector returning raw JSON
never had. Two things cross over.

The Solterra is a Toyota underneath and lives on the same Connected Europe
backend, but a Subaru account does not log in as the MyToyota app: it has its
own ForgeRock realm (alliance-subaru), its own OAuth client and redirect, and
its own client credentials, on the same host. The four login URLs that were
constants become a brandAuth chosen at Init — Toyota and Lexus keep the tme
realm and the oneapp client, Subaru gets its own — and the three URLs are
derived from the parts that differ, because the shape is ForgeRock's and does
not vary by realm. Past the login nothing changes: same data API, same
endpoints, same headers, which is why it is a third option on the connector
rather than a fourth connector. A brand on the wrong realm authenticates
against a directory it is not in and reads as a bad password, so a test pins
each brand to its realm and client.

The app now sends x-user-region on every call. The gateway only insists on it
for remote commands on Suzuki-badged DCM24 cars, which this connector never
issues; but a read that is identical to the app's is cheaper to keep than one
that is deliberately distinct, so it goes out too. The x-appbrand/brand pair
that Lexus already sent goes out for Subaru as well; a Toyota account still
sends neither.

The brand is chosen in three places that do not read the descriptor — the Web
App and Phone App settings forms hard-code their options — so Subaru is added
to both, and to the six translation files behind them. The car importer
expands "S" to Subaru so an imported Solterra gets its make.

Left where they were: the trips endpoint, which predates the last sync and
wants a date range and a card, and the wake calls, which a read-only
connector does not make.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5 committed 2026-09-21 16:28:50 +02:00
1 parent c9a95131a5
commit a26c608fc3
12 files changed
+175 -26

No files matched your search

@@ -362,6 +362,10 @@ func TestToyotaVehiclesFallbacks(t *testing.T) {
if got[0].Name != "Lexus IS 300h" {
t.Errorf("name = %q, want Lexus IS 300h", got[0].Name)
}
// The third brand on the wire: a Solterra reports itself as "S".
if m := toyotaMake("S"); m != "Subaru" {
t.Errorf(`toyotaMake("S") = %q, want Subaru`, m)
}
// A vehicle with nothing to address it by is skipped, not imported blind.
anonymous := toyotaSource{}.vehicles(json.RawMessage(`{"payload": [{"colour": "blue"}]}`))
@@ -105,14 +105,16 @@ func (toyotaSource) vehicles(raw json.RawMessage) []providerVehicle {
return out
}
// toyotaMake expands the one-letter brand code the app uses on the wire ("T"/"L")
// and otherwise passes the reported brand through in title case.
// toyotaMake expands the one-letter brand code the app uses on the wire
// ("T"/"L"/"S") and otherwise passes the reported brand through in title case.
func toyotaMake(brand string) string {
switch strings.ToUpper(strings.TrimSpace(brand)) {
case "T", "TOYOTA", "":
return "Toyota"
case "L", "LEXUS":
return "Lexus"
case "S", "SUBARU":
return "Subaru"
}
b := strings.TrimSpace(brand)
return strings.ToUpper(b[:1]) + strings.ToLower(b[1:])