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.
