Well, as predicted, they closed the loophole so the app is broken again… As mentioned earlier, as soon as someone finds a solution, it’ll be fixed within a few days. I’ll keep monitoring GitHub for possible solutions, but I’m not actively going to research for another fix, because this loophole was such a stupid security failure on their side, a clear result of the migration to their new system.
Hi
This is for information purposes only.
It is possible to retrieve the data listed below via Enode if you have an ABRP Premium subscription and their API key (ABRP API Documentation), along with the user_token, which can be found in the ABRP mobile app.
I have implemented this myself using HomeyScript and a scheduled job that runs every 10 minutes.
id5_soc
id5_range
id5_charging
id5_dcfc
id5_power
id5_charge_target
id5_speed
id5_odometer
id5_heading
id5_lat
id5_lon
id5_elevation
id5_connected
id5_ext_temp
id5_last_updated
id5_data_age_min
The values provide information about the vehicle’s state of charge, range, charging status, charging power, driving data, GPS position, external temperature, and data freshness.
Would you be willing to share an example of your script?
script that set all Homey Logic variables
// =============================================================================
// ABRP Setup - HomeyScript - Run ONCE
// Creates all Homey Logic variables for the ABRP ID.5 script.
// =============================================================================
const variables = [
// Batteri
{ name: “id5_soc”, type: “number”, value: 0 },
{ name: “id5_range”, type: “number”, value: 0 },
// Opladning
{ name: “id5_charging”, type: “boolean”, value: false },
{ name: “id5_dcfc”, type: “boolean”, value: false },
{ name: “id5_power”, type: “number”, value: 0 },
{ name: “id5_charge_target”, type: “number”, value: 0 },
// Koersel
{ name: “id5_speed”, type: “number”, value: 0 },
{ name: “id5_odometer”, type: “number”, value: 0 },
{ name: “id5_heading”, type: “number”, value: 0 },
// Position
{ name: “id5_lat”, type: “number”, value: 0 },
{ name: “id5_lon”, type: “number”, value: 0 },
{ name: “id5_elevation”, type: “number”, value: 0 },
// Tilstand
{ name: “id5_connected”, type: “boolean”, value: false },
{ name: “id5_ext_temp”, type: “number”, value: 0 },
// Meta
{ name: “id5_last_updated”, type: “string”, value: “” },
{ name: “id5_data_age_min”, type: “number”, value: 0 },
// Aktiv ruteplaner
{ name: “id5_plan_dest”, type: “string”, value: “” },
{ name: “id5_plan_km”, type: “number”, value: 0 },
{ name: “id5_plan_min”, type: “number”, value: 0 },
{ name: “id5_plan_arr_soc”, type: “number”, value: 0 },
];
async function main() {
console.log(“=== ABRP Setup ===”);
console.log(“Opretter " + variables.length + " variabler…\n”);
const existingObj = await Homey.logic.getVariables();
const existing = Object.values(existingObj);
const existingNames = existing.map(v => v.name);
let created = 0;
let skipped = 0;
for (const v of variables) {
if (existingNames.includes(v.name)) {
console.log("Findes allerede: " + v.name);
skipped++;
} else {
await Homey.logic.createVariable({
variable: {
name: v.name,
type: v.type,
value: v.value
}
});
console.log(“Oprettet: " + v.name + " (” + v.type + “)”);
created++;
}
}
console.log(“\n=== Faerdig ===”);
console.log(“Oprettet: " + created + " | Fandtes i forvejen: " + skipped);
console.log(”\nKor nu abrp_id5 scriptet!");
}
return main();
script that get ABRP ID.5 Telemetri
// =============================================================================
// ABRP ID.5 Telemetry - HomeyScript
// Retrieves live data from ABRP and updates Homey Logic variables
// René R. Nielsen - 2026
//
// Run abrp_setup.js ONCE first to create the variables
// Run this script every 10 minutes using a Homey Flow
// =============================================================================
const API_KEY = “Get it from ABRP - A Better Routeplanner”;
const USER_TOKEN = “Get from you phone under developer”;
const BASE = “https://api.iternio.com/1/tlm”;
// =============================================================================
function ts() {
const n = new Date();
const p = x => String(x).padStart(2, “0”);
return p(n.getDate())+“-”+p(n.getMonth()+1)+“-”+n.getFullYear()
+" “+p(n.getHours())+”:“+p(n.getMinutes())+”:"+p(n.getSeconds());
}
async function setVar(vars, name, value) {
const v = _.find(vars, o => o.name === name);
if (!v) { console.log("MANGLER: " + name); return; }
await Homey.logic.updateVariable({ id: v.id, variable: { value } });
}
async function apiGet(endpoint) {
const url = BASE + “/” + endpoint
- “?token=” + USER_TOKEN
- “&api_key=” + API_KEY;
const r = await fetch(url);
const txt = await r.text();
if (!r.ok) throw new Error(endpoint + " HTTP " + r.status + ": " + txt);
const d = JSON.parse(txt);
if (d.status !== “ok”) throw new Error(endpoint + " status: " + d.status);
return d.result;
}
// =============================================================================
async function main() {
console.log(“=== ABRP ID.5 " + ts() + " ===”);
// Hent variabler
const varsObj = await Homey.logic.getVariables();
const vars = Object.values(varsObj);
// ---- 1. Telemetri ----
const res = await apiGet(“get_telemetry”);
const t = res.telemetry || {};
console.log("Koeretoej: " + (res.name || res.typecode));
console.log("Forbundet: " + res.is_connected);
// Dataalder
let dataAgeMin = 0;
if (t.utc) {
dataAgeMin = Math.round((Date.now()/1000 - t.utc) / 60);
console.log(“Dataalder: " + dataAgeMin + " min”);
}
// Beregn raekkevid. fra SOC + kalibreret forbrug
let range = 0;
if (t.soc != null && t.calib_ref_cons != null && t.calib_ref_cons > 0) {
const usableKwh = 77 * (t.soc / 100) * 0.93;
range = Math.round((usableKwh * 1000) / t.calib_ref_cons);
}
console.log(“\nOpdaterer variabler:”);
// Forbindelse
await setVar(vars, “id5_connected”, res.is_connected === true);
await setVar(vars, “id5_data_age_min”, dataAgeMin);
// Batteri
if (t.soc != null) await setVar(vars, “id5_soc”, Math.round(t.soc));
if (range > 0) await setVar(vars, “id5_range”, range);
// Opladning
if (t.is_charging != null) await setVar(vars, “id5_charging”, t.is_charging === true);
if (t.is_dcfc != null) await setVar(vars, “id5_dcfc”, t.is_dcfc === true);
if (t.power != null) await setVar(vars, “id5_power”, Math.round(t.power * 10) / 10);
// Koersel
if (t.speed != null) await setVar(vars, “id5_speed”, Math.round(t.speed));
if (t.odometer != null) await setVar(vars, “id5_odometer”, Math.round(t.odometer));
if (t.heading != null) await setVar(vars, “id5_heading”, Math.round(t.heading));
// Position
if (t.lat != null) await setVar(vars, “id5_lat”, t.lat);
if (t.lon != null) await setVar(vars, “id5_lon”, t.lon);
if (t.elevation != null) await setVar(vars, “id5_elevation”, Math.round(t.elevation));
// Temperatur
if (t.ext_temp != null) await setVar(vars, “id5_ext_temp”, Math.round(t.ext_temp * 10) / 10);
// ---- 2. Naeste opladningsmal ----
try {
const charge = await apiGet(“get_next_charge”);
if (charge && charge.next_charge_to_perc != null) {
await setVar(vars, “id5_charge_target”, Math.round(charge.next_charge_to_perc));
console.log(“Opladningsmal: " + charge.next_charge_to_perc + " %”);
}
} catch (e) {
console.log("Ingen opladningsmal: " + e.message);
}
// ---- 3. Aktiv ruteplaner ----
try {
const plan = await apiGet(“get_latest_plan”);
if (plan) {
const dest = plan.destination || plan.to || “”;
const km = plan.distance ? Math.round(plan.distance / 1000) : 0;
const min = plan.duration ? Math.round(plan.duration / 60) : 0;
const arrSoc = plan.arrival_soc != null ? Math.round(plan.arrival_soc) : 0;
await setVar(vars, "id5_plan_dest", dest);
await setVar(vars, "id5_plan_km", km);
await setVar(vars, "id5_plan_min", min);
await setVar(vars, "id5_plan_arr_soc", arrSoc);
if (dest) {
console.log("Rute: " + dest + " (" + km + " km, " + min + " min, ank. " + arrSoc + "%)");
} else {
console.log("Ingen aktiv rute");
}
}
} catch (e) {
console.log("Ingen ruteplaner: " + e.message);
}
// Tidsstempel
await setVar(vars, “id5_last_updated”, ts());
// ---- Resume ----
console.log(“\n— Resume —”);
console.log(“SOC: " + t.soc + " % (raekkevid. ~” + range + " km)“);
console.log(“Oplader: " + t.is_charging + (t.is_dcfc ? " [DC]” : " [AC]”));
console.log(“Effekt: " + t.power + " kW”);
console.log(“Km-tal: " + t.odometer + " km”);
console.log(“Hastighed: " + (t.speed || 0) + " km/h”);
console.log(“Udetem.: " + (t.ext_temp || “?”) + " C”);
console.log(“Ref.forbr.: " + Math.round(t.calib_ref_cons || 0) + " Wh/km”);
console.log(“=== Faerdig " + ts() + " ===”);
}
return main();
Thanks for this. So a premium ABRP account is required for this to work. Also it looks like you aren’t able to start and stop charging which was the main benefit of the original app for me.
I tried to add my car (VW ID.4) by ABRP, but that generates a connection error (verbindingsfout, invalid oauth scope). Any idea what went wrong? I do have a premium account.
Its a bit confusing for users to understend Renes HomeyScript for all.
You need to add to HomeyScript files (codes)
- abrp_setup.js
- abrp_telemetri.js
Add this: (abrp_setup.js)
// =============================================================================
// ABRP Setup - HomeyScript - Run ONCE
// Creates all Homey Logic variables for the ABRP ID.5 script.
// =============================================================================
const variables = [
// Batteri
{ name: "id5_soc", type: "number", value: 0 },
{ name: "id5_range", type: "number", value: 0 },
// Opladning
{ name: "id5_charging", type: "boolean", value: false },
{ name: "id5_dcfc", type: "boolean", value: false },
{ name: "id5_power", type: "number", value: 0 },
{ name: "id5_charge_target", type: "number", value: 0 },
// Koersel
{ name: "id5_speed", type: "number", value: 0 },
{ name: "id5_odometer", type: "number", value: 0 },
{ name: "id5_heading", type: "number", value: 0 },
// Position
{ name: "id5_lat", type: "number", value: 0 },
{ name: "id5_lon", type: "number", value: 0 },
{ name: "id5_elevation", type: "number", value: 0 },
// Tilstand
{ name: "id5_connected", type: "boolean", value: false },
{ name: "id5_ext_temp", type: "number", value: 0 },
// Meta
{ name: "id5_last_updated", type: "string", value: "" },
{ name: "id5_data_age_min", type: "number", value: 0 },
// Aktiv ruteplaner
{ name: "id5_plan_dest", type: "string", value: "" },
{ name: "id5_plan_km", type: "number", value: 0 },
{ name: "id5_plan_min", type: "number", value: 0 },
{ name: "id5_plan_arr_soc", type: "number", value: 0 },
];
async function main() {
console.log("=== ABRP Setup ===");
console.log("Opretter " + variables.length + " variabler...\n");
const existingObj = await Homey.logic.getVariables();
const existing = Object.values(existingObj);
const existingNames = existing.map(v => v.name);
let created = 0;
let skipped = 0;
for (const v of variables) {
if (existingNames.includes(v.name)) {
console.log("Findes allerede: " + v.name);
skipped++;
} else {
await Homey.logic.createVariable({
variable: {
name: v.name,
type: v.type,
value: v.value
}
});
console.log("Oprettet: " + v.name + " (" + v.type + ")");
created++;
}
}
console.log("\n=== Faerdig ===");
console.log("Oprettet: " + created + " | Fandtes i forvejen: " + skipped);
console.log("\nKor nu abrp_id5 scriptet!");
}
return main();
- Add this (abrp_telemetri.js)
// =============================================================================
// ABRP ID.5 Telemetry - HomeyScript
// Retrieves live data from ABRP and updates Homey Logic variables
// =============================================================================
const API_KEY = "Fyll inn din API-nøkkel her";
const USER_TOKEN = "Fyll inn din User Token her";
const BASE = "https://api.iternio.com/1/tlm";
// =============================================================================
function ts() {
const n = new Date();
const p = x => String(x).padStart(2, "0");
return p(n.getDate()) + "-" + p(n.getMonth() + 1) + "-" + n.getFullYear() +
" " + p(n.getHours()) + ":" + p(n.getMinutes()) + ":" + p(n.getSeconds());
}
async function setVar(vars, name, value) {
const v = vars.find(o => o.name === name);
if (!v) {
console.log("MANGLER: " + name);
return;
}
await Homey.logic.updateVariable({ id: v.id, variable: { value } });
}
async function apiGet(endpoint) {
const url = BASE + "/" + endpoint +
"?token=" + USER_TOKEN +
"&api_key=" + API_KEY;
const r = await fetch(url);
const txt = await r.text();
if (!r.ok) throw new Error(endpoint + " HTTP " + r.status + ": " + txt);
const d = JSON.parse(txt);
if (d.status !== "ok") throw new Error(endpoint + " status: " + d.status);
return d.result;
}
// =============================================================================
async function main() {
console.log("=== ABRP ID.5 " + ts() + " ===");
// Hent variabler
const varsObj = await Homey.logic.getVariables();
const vars = Object.values(varsObj);
// ---- 1. Telemetri ----
const res = await apiGet("get_telemetry");
const t = res.telemetry || {};
console.log("Koeretoej: " + (res.name || res.typecode));
console.log("Forbundet: " + res.is_connected);
// Dataalder
let dataAgeMin = 0;
if (t.utc) {
dataAgeMin = Math.round((Date.now() / 1000 - t.utc) / 60);
console.log("Dataalder: " + dataAgeMin + " min");
}
// Beregn raekkevid. fra SOC + kalibreret forbrug
let range = 0;
if (t.soc != null && t.calib_ref_cons != null && t.calib_ref_cons > 0) {
const usableKwh = 77 * (t.soc / 100) * 0.93;
range = Math.round((usableKwh * 1000) / t.calib_ref_cons);
}
console.log("\nOpdaterer variabler:");
// Forbindelse
await setVar(vars, "id5_connected", res.is_connected === true);
await setVar(vars, "id5_data_age_min", dataAgeMin);
// Batteri
if (t.soc != null) await setVar(vars, "id5_soc", Math.round(t.soc));
if (range > 0) await setVar(vars, "id5_range", range);
// Opladning
if (t.is_charging != null) await setVar(vars, "id5_charging", t.is_charging === true);
if (t.is_dcfc != null) await setVar(vars, "id5_dcfc", t.is_dcfc === true);
if (t.power != null) await setVar(vars, "id5_power", Math.round(t.power * 10) / 10);
// Koersel
if (t.speed != null) await setVar(vars, "id5_speed", Math.round(t.speed));
if (t.odometer != null) await setVar(vars, "id5_odometer", Math.round(t.odometer));
if (t.heading != null) await setVar(vars, "id5_heading", Math.round(t.heading));
// Position
if (t.lat != null) await setVar(vars, "id5_lat", t.lat);
if (t.lon != null) await setVar(vars, "id5_lon", t.lon);
if (t.elevation != null) await setVar(vars, "id5_elevation", Math.round(t.elevation));
// Temperatur
if (t.ext_temp != null) await setVar(vars, "id5_ext_temp", Math.round(t.ext_temp * 10) / 10);
// ---- 2. Naeste opladningsmal ----
try {
const charge = await apiGet("get_next_charge");
if (charge && charge.next_charge_to_perc != null) {
await setVar(vars, "id5_charge_target", Math.round(charge.next_charge_to_perc));
console.log("Opladningsmal: " + charge.next_charge_to_perc + " %");
}
} catch (e) {
console.log("Ingen opladningsmal: " + e.message);
}
// ---- 3. Aktiv ruteplaner ----
try {
const plan = await apiGet("get_latest_plan");
if (plan) {
const dest = plan.destination || plan.to || "";
const km = plan.distance ? Math.round(plan.distance / 1000) : 0;
const min = plan.duration ? Math.round(plan.duration / 60) : 0;
const arrSoc = plan.arrival_soc != null ? Math.round(plan.arrival_soc) : 0;
await setVar(vars, "id5_plan_dest", dest);
await setVar(vars, "id5_plan_km", km);
await setVar(vars, "id5_plan_min", min);
await setVar(vars, "id5_plan_arr_soc", arrSoc);
if (dest) {
console.log("Rute: " + dest + " (" + km + " km, " + min + " min, ank. " + arrSoc + "%)");
} else {
console.log("Ingen aktiv rute");
}
}
} catch (e) {
console.log("Ingen ruteplaner: " + e.message);
}
// Tidsstempel
await setVar(vars, "id5_last_updated", ts());
// ---- Resume ----
console.log("\n--- Resume ---");
console.log("SOC: " + t.soc + " % (raekkevid. ~" + range + " km)");
console.log("Oplader: " + t.is_charging + (t.is_dcfc ? " [DC]" : " [AC]"));
console.log("Effekt: " + t.power + " kW");
console.log("Km-tal: " + t.odometer + " km");
console.log("Hastighed: " + (t.speed || 0) + " km/h");
console.log("Udetem.: " + (t.ext_temp || "?") + " C");
console.log("Ref.forbr.: " + Math.round(t.calib_ref_cons || 0) + " Wh/km");
console.log("=== Faerdig " + ts() + " ===");
}
return main();
Create API: Go into this link
User token you will find:
- Open the ABRP App on your phone (!! This does not work on the Web)
- Log in if you haven’t already
- Navigate to your car settings
- Click “Edit Car Connection Details”
- Under “Generic” as the car connection type, click “Link”
- This will create and display your User Token
- Copy the User Token for use in Homey
You need to run your first HomeyScript ONCE
A BONUS for estimate GOM:
// =============================================================================
// Estimated max range at 100% (Based on ABRP variables)
// =============================================================================
async function main() {
// Fetch all logic variables from Homey
const varsObj = await Homey.logic.getVariables();
const vars = Object.values(varsObj);
// Find the specific variables updated by the ABRP script
const socVar = vars.find(v => v.name === "id5_soc");
const rangeVar = vars.find(v => v.name === "id5_range");
// Check that the variables actually exist in the system
if (!socVar || !rangeVar) {
console.error("Cannot find the variables 'id5_soc' or 'id5_range'. Make sure abrp_setup.js has been run.");
return false;
}
// Extract the current values
const currentSoc = socVar.value;
const currentRange = rangeVar.value;
if (currentSoc == null || currentRange == null) {
console.error("Missing values for either battery or range. Check that the telemetry script is fetching data.");
return false;
}
if (currentSoc <= 0) {
console.error("Battery percentage is 0. Cannot divide by zero.");
return 0;
}
// Perform the calculation and round to the nearest whole kilometer
const estimatedMaxRange = Math.round((currentRange / currentSoc) * 100);
// Log to the console for troubleshooting
console.log(`Current battery: ${currentSoc}%`);
console.log(`Current range: ${currentRange} km`);
console.log(`Estimated range at 100%: ${estimatedMaxRange} km`);
// Return the value so it becomes available as a Tag/Token in Flows
return estimatedMaxRange;
}
return main();
Hello
I know it can be confusing for users to understand my script. I’m good at creating solutions and writing code, but I’m not as strong when it comes to presenting things for end users. Sorry about that, but nobody can be good at everything.
Below you can see the extended version of the script.
id5_soc
id5_range
id5_kwh_left
id5_charging
id5_dcfc
id5_power
id5_charge_target
id5_speed
id5_odometer
id5_heading
id5_lat
id5_lon
id5_elevation
id5_is_home
id5_distance_home
id5_connected
id5_ext_temp
id5_km_driven
id5_kwh_used
id5_charge_kwh
id5_charge_kwh_grid
id5_charge_session_dc
id5_charge_start_soc
id5_charge_dur_min
id5_eff_score
id5_eff_score_today
id5_soh_est
id5_capacity_est
id5_km_month
id5_charge_kwh_month
id5_charge_kwh_month_ac
id5_charge_kwh_month_dc
id5_charge_kwh_month_home
id5_charge_kwh_month_out
id5_charge_session_home
id5_km_today
id5_kwh_today
id5_charge_kwh_today
id5_last_updated
id5_data_age_min
note:
id5_speed Speed (km/h)
id5_odometer Total mileage (km)
id5_heading Heading in degrees 0–360 (0 = North)
id5_km_driven Calculated: current_odometer - previous_odometer
id5_kwh_used Calculated: km_driven × calib_ref_cons / 1000
id5_km_month Kilometers driven this month
id5_charge_kwh_month Total energy charged (all charging types)
id5_charge_kwh_month_ac Total AC charging (home + away)
id5_charge_kwh_month_dc Total DC fast charging
id5_charge_kwh_month_home AC charging at home (within 200 m)
id5_charge_kwh_month_out AC charging away from home (work/hotel/public charger)
id5_lat GPS latitude
id5_lon GPS longitude
id5_elevation ABRP map data - elevation above sea level (meters)
id5_is_home True if the distance from home is less than 200 meters
id5_distance_home Distance from home (meters)
id5_charging Vehicle is currently charging
id5_dcfc True = DC fast charging
id5_power Charging power (kW) - unreliable while vehicle is in standby
id5_charge_target ABRP get_next_charge - target SOC (%) from the route planner
id5_charge_kwh Calculated: (current_soc - start_soc) / 100 × 77
id5_charge_kwh_grid Calculated: charge_kwh / 0.91 (AC) or / 0.94 (DC)
id5_charge_start_soc Stored at session start - SOC (%) when charging began
id5_charge_dur_min Counter - adds 10 minutes per script execution
id5_charge_session_dc Stored at session start - whether the charging session is DC
id5_charge_session_home Stored at session start - whether the vehicle was at home when charging started
id5_soc Battery state of charge (%) 0-100
id5_kwh_left Calculated: soc / 100 × 77
id5_range Calculated: kwh_left × 1000 / calib_ref_cons
It wasn’t a criticism, just to help ![]()
Hi,
I’m using the Volkswagen Homey app (v1.1.32) with a VW ID.4.
I’ve noticed that vehicle data can sometimes be more than 10 hours old in Homey, even though the polling interval is set to 10 minutes.
If I use “Wake vehicle and update data” in the official Volkswagen app, the data is refreshed almost immediately and Homey also gets updated data afterwards.
Has anyone found a way to:
- Force a vehicle refresh from Homey?
- Wake the vehicle through a Flow card or HomeyScript?
- Improve plug-in and charging detection when the vehicle is sleeping?
I can see Flow triggers such as:
- Plug connected/disconnected
- Charging started/stopped
- Timestamp updated
but I cannot find any action card for refreshing vehicle data.
Any ideas?
Thanks!
the app don’t work any more
I know where to find it: behind a very high paywall at Cariad ![]()
I’ve had multiple conversations with Cariad, the company behind the IT infrastructure for the VAG brands, and they’re willing to provide API access… as long as someone is willing to pay for it.
I’ve said it before and I’ll say it again: at Homey, we don’t pay for API access. Our partnerships are built around mutual benefit for both parties, and that’s how we prefer to work.
I can’t share exact numbers, but let’s just say the pricing is measured in multiple euros per vehicle, per month. Excluding set-up and maintenance fees. That adds up very quickly when you’re talking about a smart home integration with thousands of users.
Everything is a business I guess..
It is just sad that those of us who pay for VW online services to use their App need somehow to pay again for the same data to be available in a different platform. Somehow something feels incorrect here.
@Doekse have you maybe tried talking to VW directly if they would be willing to make an official Homey App for this integration?
Does the mobile app use some kind of CAPTCHA? There are captcha solver services which only cost a few euros for 1000 solves (usually you only need to solve the captcha once, since the official mobile app usually also only logs in once). However, bypassing a CAPTCHA like this is usually against the company’s (Volkswagen) ToS though.
@Doekse Wouldn’t it be possible to claim that in order for VW to follow the EU Data Act we should be able to get this integration without extra cost, since we are already paying for the online services for the vehicle?
AFAIK the EU laws (GDPR) only require companies to provide an export of all data on request by the user, and to provide an option for the user to request deletion of their data, but a (free) API is not required as far as I know.
You can, they will give you a zip file with your data AFAIK
Could we automate that
?

