diff --git a/CHANGELOG.md b/CHANGELOG.md index f85c298..b1b6371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## vag_vehicle 0.2.1 + +Show only a VIN's last four characters in log lines (`****1234`). Logs get pasted into public issues, and a full VIN reached srcfl/device-drivers#143 that way. Every log call goes through one masking function, so API errors that quote a URL with the VIN are masked too. The driver still uses the full VIN for API calls and as its serial. + +## tesla_vehicle 0.2.4 + +Show only a VIN's last four characters in log lines (`****1234`). Logs get pasted into public issues, and a full VIN reached srcfl/device-drivers#143 that way. Every log call goes through one masking function, so API errors that quote a URL with the VIN are masked too. The driver still uses the full VIN for API calls and as its serial. + +## tesla_cloud 0.1.1 + +Show only a VIN's last four characters in log lines (`****1234`). Logs get pasted into public issues, and a full VIN reached srcfl/device-drivers#143 that way. Every log call goes through one masking function, so API errors that quote a URL with the VIN are masked too. The driver still uses the full VIN for API calls and as its serial. + +## teslamate_vehicle 0.1.1 + +Show only a VIN's last four characters in log lines (`****1234`). Logs get pasted into public issues, and a full VIN reached srcfl/device-drivers#143 that way. Every log call goes through one masking function, so API errors that quote a URL with the VIN are masked too. The driver still uses the full VIN for API calls and as its serial. + ## myuplink 1.2.3 Record NIBE F750 as hardware-tested with the existing read-only MyUplink Cloud REST API v2 driver. OAuth connection and live telemetry were verified on an F750 in FTW; observed points included BT1 average outdoor temperature, BT12 condenser temperature and more than 100 additional heat-pump telemetry points. No control or write path is added. diff --git a/SUPPORT_STATUS.md b/SUPPORT_STATUS.md index 4bacf8c..490462e 100644 --- a/SUPPORT_STATUS.md +++ b/SUPPORT_STATUS.md @@ -158,18 +158,18 @@ Catalog source is not proof that a target can install or run a driver. | sonnen | 2.0.3 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | sungrow | 1.5.10 | ftw-core | not_assessed | — | not_recorded | not_assessed | | sungrow | 1.5.10 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | -| tesla_cloud | 0.1.0 | ftw-core | not_assessed | — | not_recorded | not_assessed | -| tesla_cloud | 0.1.0 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | -| tesla_vehicle | 0.2.3 | ftw-core | not_assessed | — | not_recorded | not_assessed | -| tesla_vehicle | 0.2.3 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | +| tesla_cloud | 0.1.1 | ftw-core | not_assessed | — | not_recorded | not_assessed | +| tesla_cloud | 0.1.1 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | +| tesla_vehicle | 0.2.4 | ftw-core | not_assessed | — | not_recorded | not_assessed | +| tesla_vehicle | 0.2.4 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | tesla_wall_connector | 0.1.1 | ftw-core | not_assessed | — | not_recorded | not_assessed | | tesla_wall_connector | 0.1.1 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | -| teslamate_vehicle | 0.1.0 | ftw-core | not_assessed | — | not_recorded | not_assessed | -| teslamate_vehicle | 0.1.0 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | +| teslamate_vehicle | 0.1.1 | ftw-core | not_assessed | — | not_recorded | not_assessed | +| teslamate_vehicle | 0.1.1 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | tibber | 1.1.2 | ftw-core | not_assessed | — | not_recorded | not_assessed | | tibber | 1.1.2 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | -| vag_vehicle | 0.2.0 | ftw-core | not_assessed | — | not_recorded | not_assessed | -| vag_vehicle | 0.2.0 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | +| vag_vehicle | 0.2.1 | ftw-core | not_assessed | — | not_recorded | not_assessed | +| vag_vehicle | 0.2.1 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | varta | 1.1.1 | ftw-core | not_assessed | — | not_recorded | not_assessed | | varta | 1.1.1 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | victron | 2.1.2 | ftw-core | not_assessed | — | not_recorded | not_assessed | diff --git a/devices.yaml b/devices.yaml index f3a6f21..9b96334 100644 --- a/devices.yaml +++ b/devices.yaml @@ -1806,7 +1806,7 @@ manufacturers: protocols: - protocol: http driver: "tesla_vehicle" - version: "0.2.3" + version: "0.2.4" ders: [vehicle] control: true firmware_versions: "" @@ -1817,7 +1817,7 @@ manufacturers: protocols: - protocol: http driver: "tesla_cloud" - version: "0.1.0" + version: "0.1.1" ders: [vehicle] control: false firmware_versions: "" @@ -1828,7 +1828,7 @@ manufacturers: protocols: - protocol: mqtt driver: "teslamate_vehicle" - version: "0.1.0" + version: "0.1.1" ders: [vehicle] control: false firmware_versions: "" @@ -2064,7 +2064,7 @@ manufacturers: protocols: - protocol: http driver: "vag_vehicle" - version: "0.2.0" + version: "0.2.1" ders: [vehicle] control: false firmware_versions: "" diff --git a/drivers/lua/tesla_cloud.lua b/drivers/lua/tesla_cloud.lua index 81ec566..ae725df 100644 --- a/drivers/lua/tesla_cloud.lua +++ b/drivers/lua/tesla_cloud.lua @@ -58,7 +58,7 @@ DRIVER = { id = "tesla_cloud", name = "Tesla Vehicle (Fleet API)", manufacturer = "Tesla", - version = "0.1.0", + version = "0.1.1", protocols = { "http" }, capabilities = { "vehicle" }, read_only = true, @@ -123,6 +123,17 @@ local next_request_ms = 0 -- checked yet since start). local was_online = nil +-- Logs get pasted into public issues, and a VIN identifies the owner's +-- car. Every log line therefore shows only the VIN's last four characters. +local function log(level, message) + message = tostring(message) + if type(vin) == "string" and #vin > 4 then + local escaped = vin:gsub("%p", "%%%0") + message = message:gsub(escaped, "****" .. vin:sub(-4)) + end + host.log(level, message) +end + -- Last vendor observation. seen_ms is host.millis() of the last successful -- charge_state read. Age is measured on that clock; Tesla's unix ms cannot -- be compared to host.millis(). @@ -200,17 +211,17 @@ local function persist_refresh(new_token) if not host.persist_secret then return end local ok, perr = host.persist_secret("refresh_token", refresh_token) if not ok then - host.log("warn", "tesla_cloud: could not persist rotated refresh_token: " .. tostring(perr)) + log("warn", "tesla_cloud: could not persist rotated refresh_token: " .. tostring(perr)) end end local function fetch_token() if not refresh_token or refresh_token == "" then - host.log("warn", "tesla_cloud: not connected — set refresh_token from a Tesla Fleet API auth-code exchange") + log("warn", "tesla_cloud: not connected — set refresh_token from a Tesla Fleet API auth-code exchange") return false end if not client_id or client_id == "" then - host.log("error", "tesla_cloud: client_id required") + log("error", "tesla_cloud: client_id required") return false end local body = "grant_type=refresh_token" @@ -224,12 +235,12 @@ local function fetch_token() Accept = "application/json", }) if err then - host.log("error", "tesla_cloud: token refresh failed: " .. redact_http_err(err)) + log("error", "tesla_cloud: token refresh failed: " .. redact_http_err(err)) return false end local data, derr = decode_json(resp) if derr or type(data) ~= "table" or not data.access_token then - host.log("error", "tesla_cloud: no access_token in refresh response") + log("error", "tesla_cloud: no access_token in refresh response") return false end access_token = data.access_token @@ -401,7 +412,7 @@ function driver_init(config) local region = tostring(config.region or "eu"):lower() base_url = REGION_URL[region] if not base_url then - host.log("warn", "tesla_cloud: unknown region " .. region .. ", using eu") + log("warn", "tesla_cloud: unknown region " .. region .. ", using eu") region = "eu" base_url = REGION_URL.eu end @@ -412,9 +423,9 @@ function driver_init(config) -- First poll soon; driver_poll sets POLL_MS on every path after that. host.set_poll_interval(500) if not client_id or client_id == "" or not refresh_token or refresh_token == "" then - host.log("error", "tesla_cloud: client_id and refresh_token required (Fleet API auth-code exchange)") + log("error", "tesla_cloud: client_id and refresh_token required (Fleet API auth-code exchange)") end - host.log("info", "tesla_cloud: init region=" .. region .. + log("info", "tesla_cloud: init region=" .. region .. " vin=" .. tostring(vin or "(discover)") .. " telemetry-only") end @@ -443,7 +454,7 @@ function driver_poll() end end if err or type(row) ~= "table" then - host.log("warn", "tesla_cloud: vehicle status: " .. redact_http_err(err)) + log("warn", "tesla_cloud: vehicle status: " .. redact_http_err(err)) wait(ERROR_RETRY_MS) emit_vehicle(false) return POLL_MS @@ -451,7 +462,7 @@ function driver_poll() bind_identity(row.vin) if not vin or vin == "" then - host.log("warn", "tesla_cloud: no VIN on account") + log("warn", "tesla_cloud: no VIN on account") wait(ERROR_RETRY_MS) return POLL_MS end @@ -461,7 +472,7 @@ function driver_poll() local woke_just_now = online and was_online == false was_online = online if not online then - host.log("debug", "tesla_cloud: " .. vin .. " is " .. (state ~= "" and state or "unknown") .. + log("debug", "tesla_cloud: " .. vin .. " is " .. (state ~= "" and state or "unknown") .. " — not calling vehicle_data") wait(ASLEEP_EVERY_MS) emit_vehicle(false) @@ -470,7 +481,7 @@ function driver_poll() if woke_just_now then -- The car wakes briefly on its own. A live call now would keep it -- awake; if it is still online at the next check, it is in use. - host.log("debug", "tesla_cloud: " .. vin .. " just woke — reading at the next check") + log("debug", "tesla_cloud: " .. vin .. " just woke — reading at the next check") wait(ASLEEP_EVERY_MS) emit_vehicle(false) return POLL_MS @@ -486,20 +497,20 @@ function driver_poll() if ferr then local es = tostring(ferr) if es:match("HTTP 408") or es:match("vehicle unavailable") then - host.log("debug", "tesla_cloud: vehicle_data unavailable (asleep)") + log("debug", "tesla_cloud: vehicle_data unavailable (asleep)") wait(ASLEEP_EVERY_MS) elseif es:match("HTTP 429") then - host.log("warn", "tesla_cloud: rate limited") + log("warn", "tesla_cloud: rate limited") wait(BACKOFF_MS) else - host.log("warn", "tesla_cloud: vehicle_data: " .. redact_http_err(ferr)) + log("warn", "tesla_cloud: vehicle_data: " .. redact_http_err(ferr)) wait(ERROR_RETRY_MS) end emit_vehicle(false) return POLL_MS end if type(cs) ~= "table" or not remember(cs) then - host.log("debug", "tesla_cloud: no battery_level in charge_state") + log("debug", "tesla_cloud: no battery_level in charge_state") wait(IDLE_EVERY_MS) emit_vehicle(false) return POLL_MS @@ -510,7 +521,7 @@ function driver_poll() else wait(IDLE_EVERY_MS) end - host.log("info", "tesla_cloud: emit soc=" .. tostring(last.soc) .. + log("info", "tesla_cloud: emit soc=" .. tostring(last.soc) .. " limit=" .. tostring(last.charge_limit) .. " state=" .. tostring(last.charging_state)) emit_vehicle(true) diff --git a/drivers/lua/tesla_vehicle.lua b/drivers/lua/tesla_vehicle.lua index 8252c5e..8e44253 100644 --- a/drivers/lua/tesla_vehicle.lua +++ b/drivers/lua/tesla_vehicle.lua @@ -29,7 +29,7 @@ DRIVER = { id = "tesla_vehicle", name = "Tesla Vehicle (BLE Proxy)", manufacturer = "Tesla", - version = "0.2.3", + version = "0.2.4", protocols = { "http" }, capabilities = { "vehicle" }, description = "Read-only vehicle SoC + charge limit via Tesla API-compatible HTTP endpoint (e.g. TeslaBLEProxy).", @@ -105,6 +105,17 @@ local last_wakeup_ms = 0 local last_wake_attempt_ms = 0 -- includes both periodic + retry wakes local pending_wake_retry = false -- set by failed poll, consumed by next poll +-- Logs get pasted into public issues, and a VIN identifies the owner's +-- car. Every log line therefore shows only the VIN's last four characters. +local function log(level, message) + message = tostring(message) + if type(vin) == "string" and #vin > 4 then + local escaped = vin:gsub("%p", "%%%0") + message = message:gsub(escaped, "****" .. vin:sub(-4)) + end + host.log(level, message) +end + -- Cached last-known reading so we can keep publishing a value while -- the vehicle is asleep. Tesla returns 408 "vehicle unavailable" when -- the car is in deep sleep; we treat that as "use last-known" until @@ -152,18 +163,18 @@ end function driver_init(config) if not config then - host.log("error", "tesla: config required (ip + vin)") + log("error", "tesla: config required (ip + vin)") return end local ip = config.ip vin = config.vin if not ip or ip == "" then - host.log("error", "tesla: `ip` required (LAN address of TeslaBLEProxy)") + log("error", "tesla: `ip` required (LAN address of TeslaBLEProxy)") return end if not vin or vin == "" then - host.log("error", "tesla: `vin` required (vehicle VIN the proxy is paired to)") + log("error", "tesla: `vin` required (vehicle VIN the proxy is paired to)") return end @@ -196,7 +207,7 @@ function driver_init(config) -- The registry re-reads PollInterval() on every iteration, so the -- mid-flight change takes effect immediately. host.set_poll_interval(500) - host.log("info", "tesla: driver initialized vin=" .. tostring(vin) .. + log("info", "tesla: driver initialized vin=" .. tostring(vin) .. " proxy=" .. base_url .. " poll_s=" .. tostring(POLL_INTERVAL_MS / 1000) .. " (first poll within 1s)") @@ -279,7 +290,7 @@ function driver_poll() -- Previous poll failed; we armed a retry. Consume it. do_wakeup = true pending_wake_retry = false - host.log("info", "tesla: wake-retry firing after previous poll failure") + log("info", "tesla: wake-retry firing after previous poll failure") elseif (last_wakeup_ms > 0) and ((now - last_wakeup_ms) >= wake_cadence_ms) then do_wakeup = true elseif last.charging_state == "Charging" and last.ts_ms > 0 and @@ -292,7 +303,7 @@ function driver_poll() -- stale "Charging" reading. Gated to Charging only so a parked -- car doesn't get its 12 V drained by speculative wakes. do_wakeup = true - host.log("info", "tesla: stale-while-charging force-wake (" .. + log("info", "tesla: stale-while-charging force-wake (" .. tostring(math.floor((now - last.ts_ms) / 1000)) .. "s since last emit)") end @@ -307,7 +318,7 @@ function driver_poll() url = url .. "&wakeup=true" last_wakeup_ms = now last_wake_attempt_ms = now - host.log("info", "tesla: forcing BLE wakeup on this poll" .. + log("info", "tesla: forcing BLE wakeup on this poll" .. " (cadence=" .. tostring(wake_cadence_ms / 60000) .. "min" .. " charging=" .. tostring(last.charging_state == "Charging") .. ")") end @@ -329,7 +340,7 @@ function driver_poll() -- already busy; adding another wake won't help. local es = tostring(err) if es:match("HTTP 503") or es:match("HTTP 408") or es:match("Command Disallowed") then - host.log("debug", "tesla: proxy busy (BLE busy) — backing off 3 min") + log("debug", "tesla: proxy busy (BLE busy) — backing off 3 min") emit_last() return 180000 -- 3 min end @@ -342,24 +353,24 @@ function driver_poll() -- already woke (no point retrying with another wake — that's -- the case the proxy is genuinely failing) — fall back to the -- normal interval and let the periodic cadence catch up. - host.log("warn", "tesla: poll HTTP error: " .. es) + log("warn", "tesla: poll HTTP error: " .. es) emit_last() if not do_wakeup then pending_wake_retry = true - host.log("info", "tesla: wake-retry armed (next poll will force BLE wake)") + log("info", "tesla: wake-retry armed (next poll will force BLE wake)") return FAILURE_RETRY_INTERVAL_MS end return steady_ms end if not body or body == "" then - host.log("warn", "tesla: empty body from proxy") + log("warn", "tesla: empty body from proxy") emit_last() return steady_ms end local decoded, derr = safe_json_decode(body) if derr or not decoded then - host.log("warn", "tesla: json decode failed: " .. tostring(derr)) + log("warn", "tesla: json decode failed: " .. tostring(derr)) emit_last() return steady_ms end @@ -385,7 +396,7 @@ function driver_poll() end end if not charge_state then - host.log("debug", "tesla: no charge_state in response") + log("debug", "tesla: no charge_state in response") emit_last() return steady_ms end @@ -418,7 +429,7 @@ function driver_poll() last.charger_actual_current = charger_actual_current last.ts_ms = host.millis() - host.log("info", "tesla: emit soc=" .. tostring(soc) .. + log("info", "tesla: emit soc=" .. tostring(soc) .. " limit=" .. tostring(limit) .. " state=" .. tostring(cs) .. " amps=" .. tostring(charge_amps) .. @@ -433,7 +444,7 @@ function driver_poll() stale = false, }) if emit_err then - host.log("warn", "tesla: emit returned error: " .. tostring(emit_err)) + log("warn", "tesla: emit returned error: " .. tostring(emit_err)) end else -- Malformed response (no battery_level) → keep last-known. @@ -461,7 +472,7 @@ function driver_command(action, _, _) -- caller's wake just supersedes it. if action == "wake_up" or action == "ev_wake" then if not base_url or not vin then - host.log("warn", "tesla: wake_up before init") + log("warn", "tesla: wake_up before init") return false end local url = base_url .. "/api/1/vehicles/" .. vin .. "/command/wake_up" @@ -469,17 +480,17 @@ function driver_command(action, _, _) if err then local es = tostring(err) if es:match("HTTP 503") or es:match("HTTP 408") then - host.log("debug", "tesla: wake_up busy, will retry on next caller: " .. es) + log("debug", "tesla: wake_up busy, will retry on next caller: " .. es) return false end - host.log("warn", "tesla: wake_up failed: " .. es) + log("warn", "tesla: wake_up failed: " .. es) return false end last_wakeup_ms = host.millis() last_wake_attempt_ms = last_wakeup_ms pending_wake_retry = false local snippet = (body and #body > 0) and body:sub(1, 200) or "(empty body)" - host.log("info", "tesla: wake_up sent: " .. snippet) + log("info", "tesla: wake_up sent: " .. snippet) return true end -- Wake-and-start support. The loadpoint controller fires the @@ -492,7 +503,7 @@ function driver_command(action, _, _) -- — the Go side has no Tesla-specific knowledge. if action == "charge_start" or action == "ev_start" then if not base_url or not vin then - host.log("warn", "tesla: charge_start before init") + log("warn", "tesla: charge_start before init") return false end local url = base_url .. "/api/1/vehicles/" .. vin .. "/command/charge_start" @@ -506,10 +517,10 @@ function driver_command(action, _, _) -- busy or rate-limited. Not an error from our perspective — -- the controller's cooldown will retry on the next window. if es:match("HTTP 503") or es:match("HTTP 408") then - host.log("debug", "tesla: charge_start busy/asleep, will retry: " .. es) + log("debug", "tesla: charge_start busy/asleep, will retry: " .. es) return false end - host.log("warn", "tesla: charge_start failed: " .. es) + log("warn", "tesla: charge_start failed: " .. es) return false end -- Surface the proxy's response body so we can see WHY a @@ -518,10 +529,10 @@ function driver_command(action, _, _) -- (idempotent / no-op), or a vehicle-side rejection (e.g. user -- has charge-on-schedule enabled). local snippet = (body and #body > 0) and body:sub(1, 200) or "(empty body)" - host.log("info", "tesla: charge_start response: " .. snippet) + log("info", "tesla: charge_start response: " .. snippet) return true end - host.log("debug", "tesla: command ignored: " .. tostring(action)) + log("debug", "tesla: command ignored: " .. tostring(action)) return false end diff --git a/drivers/lua/teslamate_vehicle.lua b/drivers/lua/teslamate_vehicle.lua index 31bf77d..4c64988 100644 --- a/drivers/lua/teslamate_vehicle.lua +++ b/drivers/lua/teslamate_vehicle.lua @@ -59,7 +59,7 @@ DRIVER = { id = "teslamate_vehicle", name = "Tesla Vehicle (TeslaMate)", manufacturer = "Tesla", - version = "0.1.0", + version = "0.1.1", protocols = { "mqtt" }, capabilities = { "vehicle" }, read_only = true, @@ -91,6 +91,17 @@ local AWAKE = { local vin = nil +-- Logs get pasted into public issues, and a VIN identifies the owner's +-- car. Every log line therefore shows only the VIN's last four characters. +local function log(level, message) + message = tostring(message) + if type(vin) == "string" and #vin > 4 then + local escaped = vin:gsub("%p", "%%%0") + message = message:gsub(escaped, "****" .. vin:sub(-4)) + end + host.log(level, message) +end + -- Last live (awake) observation. seen_ms is host.millis() when TeslaMate -- last published charge fields while the car was awake. TeslaMate's -- ISO `since` cannot be compared to host.millis() (monotonic uptime), @@ -326,7 +337,7 @@ function driver_init(config) vin = tostring(config.vin) host.set_sn(vin) else - host.log("error", "teslamate_vehicle: config.vin required (TeslaMate MQTT does not publish VIN)") + log("error", "teslamate_vehicle: config.vin required (TeslaMate MQTT does not publish VIN)") end if config.topic_prefix and tostring(config.topic_prefix) ~= "" then @@ -349,9 +360,9 @@ function driver_init(config) -- Real host returns nil on success. The test mock returns true. local err = host.mqtt_subscribe(base_topic .. "/#") if type(err) == "string" and err ~= "" then - host.log("error", "teslamate_vehicle: subscribe failed: " .. err) + log("error", "teslamate_vehicle: subscribe failed: " .. err) else - host.log("info", "teslamate_vehicle: subscribed to " .. base_topic .. "/#" .. + log("info", "teslamate_vehicle: subscribed to " .. base_topic .. "/#" .. " vin=" .. tostring(vin or "(missing)") .. " telemetry-only") end @@ -375,7 +386,7 @@ function driver_poll() -- the car is awake starts (or refreshes) the age clock. Idle polls and -- asleep retained values must not look like a new BMS reading. if can_accept_fresh() and saw_charge and remember() then - host.log("info", "teslamate_vehicle: emit soc=" .. tostring(last.soc) .. + log("info", "teslamate_vehicle: emit soc=" .. tostring(last.soc) .. " limit=" .. tostring(last.charge_limit) .. " state=" .. tostring(last.charging_state) .. " tm=" .. tostring(last.tm_state)) diff --git a/drivers/lua/vag_vehicle.lua b/drivers/lua/vag_vehicle.lua index 16ade9e..2de1193 100644 --- a/drivers/lua/vag_vehicle.lua +++ b/drivers/lua/vag_vehicle.lua @@ -66,7 +66,7 @@ DRIVER = { id = "vag_vehicle", name = "VAG Vehicle (EU Data Act)", manufacturer = "Volkswagen Group", - version = "0.2.0", + version = "0.2.1", protocols = { "http" }, capabilities = { "vehicle" }, read_only = true, @@ -202,6 +202,17 @@ local last = { measured_age_s = nil, } +-- Logs get pasted into public issues, and a VIN identifies the owner's +-- car. Every log line therefore shows only the VIN's last four characters. +local function log(level, message) + message = tostring(message) + if type(vin) == "string" and #vin > 4 then + local escaped = vin:gsub("%p", "%%%0") + message = message:gsub(escaped, "****" .. vin:sub(-4)) + end + host.log(level, message) +end + --------------------------------------------------------------------------- -- ZIP + raw DEFLATE (Lua 5.1). The host returns the portal zip as a -- string and has no unzip helper. @@ -1142,13 +1153,13 @@ end function driver_init(config) if not config then - host.log("error", "vag: config required (vin + brand + cookie)") + log("error", "vag: config required (vin + brand + cookie)") return end local err brand_name, err = resolve_brand(config.brand) if not brand_name then - host.log("error", "vag: " .. tostring(err)) + log("error", "vag: " .. tostring(err)) return end vin = config.vin @@ -1156,7 +1167,7 @@ function driver_init(config) vin = vin:gsub("%s+", ""):upper() end if type(vin) ~= "string" or vin == "" then - host.log("error", "vag: `vin` required") + log("error", "vag: `vin` required") return end -- Bind identity even when the session cookie is missing so the site @@ -1170,19 +1181,19 @@ function driver_init(config) email = config.email password = config.password elseif not cookie then - host.log("error", "vag: this FTW version cannot sign in with email and password; update FTW or paste the portal Cookie header. Charging does not need this cloud.") + log("error", "vag: this FTW version cannot sign in with email and password; update FTW or paste the portal Cookie header. Charging does not need this cloud.") return end end if not session_mode() and not cookie then - host.log("error", "vag: `email` and `password` required (or a pasted portal `cookie`) after enabling the 15-minute All Data request. Charging does not need this cloud.") + log("error", "vag: `email` and `password` required (or a pasted portal `cookie`) after enabling the 15-minute All Data request. Charging does not need this cloud.") return end if host.set_watchdog_timeout_s then host.set_watchdog_timeout_s(WATCHDOG_TIMEOUT_S) end host.set_poll_interval(500) - host.log("info", "vag: telemetry-only EU Data Act driver brand=" .. + log("info", "vag: telemetry-only EU Data Act driver brand=" .. brand_name .. " vin=" .. vin .. (session_mode() and " sign-in=email" or " sign-in=cookie")) end @@ -1204,28 +1215,28 @@ function driver_poll() if es:find("not in allowed_hosts", 1, true) then es = es .. " — add identity.vwgroup.io to capabilities.http.allowed_hosts" end - host.log("warn", "vag: sign-in failed, next try in 15 min: " .. es) + log("warn", "vag: sign-in failed, next try in 15 min: " .. es) emit_last() return POLL_INTERVAL_MS end session_ok = true request_id = nil - host.log("info", "vag: signed in to the EU Data Act portal") + log("info", "vag: signed in to the EU Data Act portal") end local id, iderr = ensure_request_id() if not id then local es = tostring(iderr) if session_mode() and not session_ok then - host.log("info", "vag: portal session ended, signing in again") + log("info", "vag: portal session ended, signing in again") emit_last() return 1000 elseif es:match("HTTP 401") or es:match("HTTP 403") then - host.log("warn", "vag: portal session expired — refresh config.cookie. Charging continues without vehicle cloud.") + log("warn", "vag: portal session expired — refresh config.cookie. Charging continues without vehicle cloud.") elseif es:match("HTTP 404") or es:match("no data request") then - host.log("warn", "vag: no continuous data request — enable All Data / 15 min on the EU Data Act portal") + log("warn", "vag: no continuous data request — enable All Data / 15 min on the EU Data Act portal") else - host.log("warn", "vag: metadata failed: " .. es) + log("warn", "vag: metadata failed: " .. es) end emit_last() return POLL_INTERVAL_MS @@ -1237,17 +1248,17 @@ function driver_poll() if lerr then local es = tostring(lerr) if session_mode() and not session_ok then - host.log("info", "vag: portal session ended, signing in again") + log("info", "vag: portal session ended, signing in again") request_id = nil emit_last() return 1000 elseif es:match("HTTP 401") or es:match("HTTP 403") then - host.log("warn", "vag: portal session expired — refresh config.cookie. Charging continues without vehicle cloud.") + log("warn", "vag: portal session expired — refresh config.cookie. Charging continues without vehicle cloud.") request_id = nil elseif es:match("HTTP 404") then - host.log("debug", "vag: no dataset files yet") + log("debug", "vag: no dataset files yet") else - host.log("warn", "vag: list failed: " .. es) + log("warn", "vag: list failed: " .. es) end emit_last() return POLL_INTERVAL_MS @@ -1255,7 +1266,7 @@ function driver_poll() local list, derr = safe_json_decode(list_body) if not list then - host.log("warn", "vag: list json failed: " .. tostring(derr)) + log("warn", "vag: list json failed: " .. tostring(derr)) emit_last() return POLL_INTERVAL_MS end @@ -1276,7 +1287,7 @@ function driver_poll() "/proxy_api/euda-apim/datadelivery/vehicles/" .. vin .. "/" .. id .. "/download", { type = "partial", filename = name }) if zerr then - host.log("warn", "vag: download failed: " .. tostring(zerr)) + log("warn", "vag: download failed: " .. tostring(zerr)) emit_last() return POLL_INTERVAL_MS end @@ -1285,7 +1296,7 @@ function driver_poll() local points, perr = parse_dataset(zip) if not points then - host.log("warn", "vag: dataset decode failed: " .. tostring(perr)) + log("warn", "vag: dataset decode failed: " .. tostring(perr)) emit_last() return POLL_INTERVAL_MS end @@ -1293,7 +1304,7 @@ function driver_poll() local soc_p = lookup(points, SOC_IDS) local soc = soc_p and num(soc_p.value) or nil if soc == nil then - host.log("debug", "vag: dataset had no SoC field") + log("debug", "vag: dataset had no SoC field") emit_last() return POLL_INTERVAL_MS end @@ -1323,7 +1334,7 @@ function driver_poll() fresh = measured <= FRESH_AGE_S end remember(soc, limit, state, ttf, known_age, measured) - host.log("info", "vag: " .. (fresh and "emit" or (known_age and "old reading:" or "first file since start, age unknown, stale:")) .. + log("info", "vag: " .. (fresh and "emit" or (known_age and "old reading:" or "first file since start, age unknown, stale:")) .. " soc=" .. tostring(soc) .. " limit=" .. tostring(limit) .. " state=" .. tostring(state) .. diff --git a/drivers/tests/lua_harness/test_tesla_cloud.lua b/drivers/tests/lua_harness/test_tesla_cloud.lua index cfcaf55..6cf6be8 100644 --- a/drivers/tests/lua_harness/test_tesla_cloud.lua +++ b/drivers/tests/lua_harness/test_tesla_cloud.lua @@ -1,6 +1,13 @@ dofile("drivers/tests/lua_harness/host_mock.lua") local VIN = "5YJ3E1EA1KF000000" + +-- Logs get pasted into public issues, so no log line may carry the full VIN. +local mock_log = host.log +function host.log(level, message) + assert(not tostring(message):find(VIN, 1, true), "log shows the full VIN: " .. tostring(message)) + return mock_log(level, message) +end local AUTH = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token" local EU = "https://fleet-api.prd.eu.vn.cloud.tesla.com" local STATUS = EU .. "/api/1/vehicles/" .. VIN diff --git a/drivers/tests/lua_harness/test_vag_vehicle_login.lua b/drivers/tests/lua_harness/test_vag_vehicle_login.lua index 87dc5a6..c875e3e 100644 --- a/drivers/tests/lua_harness/test_vag_vehicle_login.lua +++ b/drivers/tests/lua_harness/test_vag_vehicle_login.lua @@ -11,9 +11,16 @@ local dataset_zip = f:read("*a") f:close() local VIN = "WVWZZZTESTVIN0001" + +-- Logs get pasted into public issues, so no log line may carry the full VIN. +local mock_log = host.log +function host.log(level, message) + assert(not tostring(message):find(VIN, 1, true), "log shows the full VIN: " .. tostring(message)) + return mock_log(level, message) +end local REQ = "req-continuous-1" -local FILE = "2026-09-26T07-00-00_partial.zip" -local NEXT = "2026-09-26T07-15-00_partial.zip" +local FILE = "20260926070000_" .. VIN .. ".zip" +local NEXT = "20260926071500_" .. VIN .. ".zip" local CID = "9b58543e-1c15-4193-91d5-8a14145bebb0@apps_vw-dilab_com" local ID = "https://identity.vwgroup.io" local PORTAL = "https://eu-data-act.drivesomethinggreater.com" diff --git a/index.yaml b/index.yaml index 4f154cc..936c81f 100644 --- a/index.yaml +++ b/index.yaml @@ -709,25 +709,25 @@ drivers: size_bytes: 49876 sha256: "25407256014764245bedf1cfc193f94a696854ddedbd224f66fef827df92fb82" - name: "tesla_cloud" - version: "0.1.0" + version: "0.1.1" tier: community protocol: http connectivity: cloud setup: [vendor_portal] ders: [vehicle] control: false - size_bytes: 17903 - sha256: "4f9a43703e787a5e72f7a86964bc21f89ade869e88fe2213e1931c55c241f34c" + size_bytes: 18216 + sha256: "ed608e4a4501f7a9534296989bb083bb7960ebf8456bd760d868cb26069ea484" - name: "tesla_vehicle" - version: "0.2.3" + version: "0.2.4" tier: core protocol: http connectivity: local setup: [bridge] ders: [vehicle] control: true - size_bytes: 23888 - sha256: "480c23e8ec13e075254dded23b4a9e3b93bb91b02ac63d29bd126b672a569bdd" + size_bytes: 24166 + sha256: "0d76b44cd01af02aea995648006148649e8c6d88e97416e37b2f509d5eecddf9" - name: "tesla_wall_connector" version: "0.1.1" tier: community @@ -738,15 +738,15 @@ drivers: size_bytes: 8162 sha256: "66b6313eda4f68a92a7cc55931baa2408cb6cbb249145f7e6005456fb821d284" - name: "teslamate_vehicle" - version: "0.1.0" + version: "0.1.1" tier: community protocol: mqtt connectivity: local setup: [bridge] ders: [vehicle] control: false - size_bytes: 13260 - sha256: "916cc84bd8df4080ccccbcc9bc968082771e847a6c1f020cfeefc52d4db68a43" + size_bytes: 13638 + sha256: "2a6014b210c31aab07122051f4e30a91d5e75c9609be62a25b684620c97c0903" - name: "tibber" version: "1.1.2" tier: core @@ -758,15 +758,15 @@ drivers: size_bytes: 12472 sha256: "3cf10b93755fa8b840375f53343e91b5efa98ddffe119a5679b43ee4bfb61c88" - name: "vag_vehicle" - version: "0.2.0" + version: "0.2.1" tier: community protocol: http connectivity: cloud setup: [vendor_portal] ders: [vehicle] control: false - size_bytes: 46268 - sha256: "0e6fc2baa2309b983304247a51759c527bb0f06cad2198949c45bd3ee5614b1d" + size_bytes: 46561 + sha256: "01d3c6d62583e62ea42eaf7bb6b7f98e47b0360d8e65fbfd3210e64f42ccdf73" - name: "varta" version: "1.1.1" tier: community diff --git a/manifests/tesla_cloud.yaml b/manifests/tesla_cloud.yaml index 79aa9b3..4da063f 100644 --- a/manifests/tesla_cloud.yaml +++ b/manifests/tesla_cloud.yaml @@ -1,5 +1,5 @@ name: "tesla_cloud" -version: "0.1.0" +version: "0.1.1" tier: community author: "Sourceful Labs AB" protocol: http @@ -16,9 +16,9 @@ tested_devices: notes: "Read-only vehicle SoC, charge limit and charging state via the official Tesla Fleet API. Optional next to tesla_vehicle (BLE proxy). Does not wake or command the car. Not yet run against a car." min_driver_version: "0.1.0" min_host_version: "2.0.0" -size_bytes: 17903 +size_bytes: 18216 dkb_id: "tesla_cloud" -sha256: "4f9a43703e787a5e72f7a86964bc21f89ade869e88fe2213e1931c55c241f34c" +sha256: "ed608e4a4501f7a9534296989bb083bb7960ebf8456bd760d868cb26069ea484" signature: "" bytecode_sha256: "" diff --git a/manifests/tesla_vehicle.yaml b/manifests/tesla_vehicle.yaml index 128dcd1..87d0f0b 100644 --- a/manifests/tesla_vehicle.yaml +++ b/manifests/tesla_vehicle.yaml @@ -1,5 +1,5 @@ name: "tesla_vehicle" -version: "0.2.3" +version: "0.2.4" tier: core author: "Sourceful Labs AB" protocol: http @@ -16,9 +16,9 @@ tested_devices: notes: "Read-only vehicle SoC + charge limit via Tesla API-compatible HTTP endpoint (e.g. TeslaBLEProxy)." min_driver_version: "0.1.0" min_host_version: "2.0.0" -size_bytes: 23888 +size_bytes: 24166 dkb_id: "tesla_vehicle" -sha256: "480c23e8ec13e075254dded23b4a9e3b93bb91b02ac63d29bd126b672a569bdd" +sha256: "0d76b44cd01af02aea995648006148649e8c6d88e97416e37b2f509d5eecddf9" signature: "" bytecode_sha256: "" diff --git a/manifests/teslamate_vehicle.yaml b/manifests/teslamate_vehicle.yaml index 555d562..7fa37bb 100644 --- a/manifests/teslamate_vehicle.yaml +++ b/manifests/teslamate_vehicle.yaml @@ -1,5 +1,5 @@ name: "teslamate_vehicle" -version: "0.1.0" +version: "0.1.1" tier: community author: "Sourceful Labs AB" protocol: mqtt @@ -21,9 +21,9 @@ upstream_docs: kind: api_docs url_stability: stable min_host_version: "2.0.0" -size_bytes: 13260 +size_bytes: 13638 dkb_id: "teslamate_vehicle" -sha256: "916cc84bd8df4080ccccbcc9bc968082771e847a6c1f020cfeefc52d4db68a43" +sha256: "2a6014b210c31aab07122051f4e30a91d5e75c9609be62a25b684620c97c0903" signature: "" bytecode_sha256: "" diff --git a/manifests/vag_vehicle.yaml b/manifests/vag_vehicle.yaml index 9167b93..fe01c51 100644 --- a/manifests/vag_vehicle.yaml +++ b/manifests/vag_vehicle.yaml @@ -1,5 +1,5 @@ name: "vag_vehicle" -version: "0.2.0" +version: "0.2.1" tier: community author: "Sourceful Labs AB" protocol: http @@ -21,9 +21,9 @@ upstream_docs: kind: other url_stability: stable min_host_version: "2.0.0" -size_bytes: 46268 +size_bytes: 46561 dkb_id: "vag_vehicle" -sha256: "0e6fc2baa2309b983304247a51759c527bb0f06cad2198949c45bd3ee5614b1d" +sha256: "01d3c6d62583e62ea42eaf7bb6b7f98e47b0360d8e65fbfd3210e64f42ccdf73" signature: "" bytecode_sha256: "" diff --git a/support-status.json b/support-status.json index 54a6560..689a13e 100644 --- a/support-status.json +++ b/support-status.json @@ -1619,7 +1619,7 @@ }, { "catalog_source": true, - "catalog_version": "0.1.0", + "catalog_version": "0.1.1", "driver_id": "tesla_cloud", "targets": { "blixt-l1": { @@ -1640,7 +1640,7 @@ }, { "catalog_source": true, - "catalog_version": "0.2.3", + "catalog_version": "0.2.4", "driver_id": "tesla_vehicle", "targets": { "blixt-l1": { @@ -1682,7 +1682,7 @@ }, { "catalog_source": true, - "catalog_version": "0.1.0", + "catalog_version": "0.1.1", "driver_id": "teslamate_vehicle", "targets": { "blixt-l1": { @@ -1724,7 +1724,7 @@ }, { "catalog_source": true, - "catalog_version": "0.2.0", + "catalog_version": "0.2.1", "driver_id": "vag_vehicle", "targets": { "blixt-l1": {