Thermia Diplomat heat pump control via Homey

bentrik
Fri, 20 Mar 2026 16:21:06 +0000
Devices

I wanted to control the hot water production on my Thermia Diplomat Inverter M heat pump from Homey Pro, but there was no official Homey app available. After some digging through the Thermia Online web app using browser DevTools, I managed to reverse-engineer the API and build a working HomeyScript integration. Sharing it here in case others find it useful.

Tested on:

Homey Pro 2026

Thermia Diplomat Inverter M [230]

Thermia Online account (online.thermia.se)

What it does

Turns hot water production ON or OFF via Homey Flows

Reads temperatures from the heat pump every 10 minutes (outdoor, supply line, brine in, hot water)

Stores temperatures as Homey Logic variables — visible in Insights with graphs over time

Auto-renews the authentication token so it never expires

Sends a push notification if authentication fails

My setup turns hot water OFF at 06:00 and ON again at 22:00 to avoid peak electricity hours, with a safety flow that turns it back on if the water temperature drops below 50°C during the day.

Prerequisites

HomeyScript app installed on your Homey

A Thermia Online account at online.thermia.se

A PC/Mac browser with DevTools (Chrome or Edge recommended)

Basic familiarity with Homey Flows

Step 1 — Get your refresh token

The Thermia API uses token-based authentication. You need to get an initial refresh token from your browser:

1. Go to online.thermia.se and log in

2. Open DevTools (F12) → Network tab

3. Refresh the page

4. Find the request named token in the list

5. Click it → Response tab

6. Copy the value of the refresh_token field — it is a long ~1400 character string with 5 dot-separated segments starting with eyJ

Do not copy the access_token or id_token — these are shorter 3-part strings and will not work.

Step 2 — Find your installation ID

1. In DevTools with the Network tab still open, look for requests with URLs containing /installationstatus/ or /Registers/Installations/

2. Your installation ID is the number in those URLs — for example /Registers/Installations/XXXXXXX/Groups/

3. Note this number — you will need to replace XXXXXXX with it in the scripts below

Step 3 — Create Homey Logic variables

Create these variables in Homey → Logic → Variables:

Name | Type
thermia_refresh_token | Text
thermia_hotwater_command | Text
thermia_hotwater_status | Text
thermia_temp_hot_water | Number
thermia_temp_outdoor | Number
thermia_temp_supply | Number
thermia_temp_brine_in | Number

Step 4 — Store the refresh token

Create a new HomeyScript called thermia_set_token and run it once with your token pasted in:

javascript

const newRefreshToken = "PASTE_YOUR_REFRESH_TOKEN_HERE";

const allVars = Object.values(await Homey.logic.getVariables());
const refreshVar = allVars.find(v => v.name === "thermia_refresh_token");

await Homey.logic.updateVariable({
  id: refreshVar.id,
  variable: { value: newRefreshToken }
});

log("Stored length: " + newRefreshToken.length);
log("Segments: " + newRefreshToken.split(".").length);
log("✅ Done — expect length ~1400 and segments 5");

Step 5 — The scripts

Replace XXXXXXX with your own installation ID in both scripts before running them.

Script 1: thermia_read_temperatures

javascript

const token_var = await Homey.logic.getVariables();
const allVars = Object.values(token_var);
const refreshVar = allVars.find(v => v.name === "thermia_refresh_token");

const loginBody = new URLSearchParams();
loginBody.append("grant_type",    "refresh_token");
loginBody.append("client_id",     "09ea4903-9e95-45fe-ae1f-e3b7d32fa385");
loginBody.append("refresh_token", refreshVar.value.trim());
loginBody.append("redirect_uri",  "https://www.online.thermia.se/login");

const loginRaw = await fetch("https://thermialogin.b2clogin.com/thermialogin.onmicrosoft.com/b2c_1a_signuporsigninonline/oauth2/v2.0/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: loginBody
});
const loginData = JSON.parse(await loginRaw.text());

if (!loginData.access_token) {
  log("❌ Token refresh failed - paste a new refresh_token into Homey Logic");
  log(JSON.stringify(loginData));
  await Homey.notifications.createNotification({
    excerpt: "⚠️ Thermia token expired — paste a new refresh_token into Homey Logic"
  });
  return;
}

if (loginData.refresh_token) {
  await Homey.logic.updateVariable({
    id: refreshVar.id,
    variable: { value: loginData.refresh_token }
  });
  log("✅ Refresh token renewed (" + loginData.refresh_token.length + " chars)");
}

const BASE    = "https://online-classic-serviceapi.azurewebsites.net/api/v1";
const INSTALL = "XXXXXXX"; // ← Replace with your installation ID
const headers = { "Authorization": "Bearer " + loginData.access_token, "Accept": "application/json" };

const res = await fetch(BASE + "/Registers/Installations/" + INSTALL + "/Groups/REG_GROUP_TEMPERATURES", { headers });
const data = JSON.parse(await res.text());

const mapping = {
  "REG_HOT_WATER_TEMPERATURE": "thermia_temp_hot_water",
  "REG_OUTDOOR_TEMPERATURE":   "thermia_temp_outdoor",
  "REG_SUPPLY_LINE":           "thermia_temp_supply",
  "REG_BRINE_IN":              "thermia_temp_brine_in"
};

for (const reg of data) {
  const varName = mapping[reg.registerName];
  if (!varName) continue;
  const homeyVar = allVars.find(v => v.name === varName);
  if (!homeyVar) { log("⚠️ Variable not found: " + varName); continue; }
  await Homey.logic.updateVariable({ id: homeyVar.id, variable: { value: reg.registerValue } });
  log(varName + " = " + reg.registerValue + "°C");
}

Script 2: thermia_hotwater

javascript

// ── CONFIGURATION ───────────────────────────────────────────────
const CLIENT_ID   = "09ea4903-9e95-45fe-ae1f-e3b7d32fa385";
const TOKEN_URL   = "https://thermialogin.b2clogin.com/thermialogin.onmicrosoft.com/b2c_1a_signuporsigninonline/oauth2/v2.0/token";
const REDIRECT    = "https://www.online.thermia.se/login";
const BASE        = "https://online-classic-serviceapi.azurewebsites.net/api/v1";
const INSTALL     = "XXXXXXX"; // ← Replace with your installation ID
const REG_SPEC_ID = 29442;     // Hot water on/off register — may differ on other models

// ── STEP 1: READ HOMEY LOGIC VARIABLES ──────────────────────────
const allVars     = Object.values(await Homey.logic.getVariables());
const refreshVar  = allVars.find(v => v.name === "thermia_refresh_token");
const commandVar  = allVars.find(v => v.name === "thermia_hotwater_command");
const statusVar   = allVars.find(v => v.name === "thermia_hotwater_status");

if (!refreshVar) { log("❌ Missing variable: thermia_refresh_token"); return; }
if (!commandVar) { log("❌ Missing variable: thermia_hotwater_command"); return; }

const command = (commandVar.value || "").trim().toLowerCase();
log("Command: " + command);

// ── STEP 2: AUTHENTICATE WITH THERMIA ───────────────────────────
const loginBody = new URLSearchParams();
loginBody.append("grant_type",    "refresh_token");
loginBody.append("client_id",     CLIENT_ID);
loginBody.append("refresh_token", refreshVar.value.trim());
loginBody.append("redirect_uri",  REDIRECT);

const loginRaw = await fetch(TOKEN_URL, {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: loginBody
});
const loginData = JSON.parse(await loginRaw.text());

if (!loginData.access_token) {
  log("❌ Token refresh failed - paste a new refresh_token into Homey Logic");
  log(JSON.stringify(loginData));
  await Homey.notifications.createNotification({
    excerpt: "⚠️ Thermia token expired — paste a new refresh_token into Homey Logic"
  });
  return;
}

if (loginData.refresh_token) {
  await Homey.logic.updateVariable({
    id: refreshVar.id,
    variable: { value: loginData.refresh_token }
  });
  log("✅ Refresh token renewed (" + loginData.refresh_token.length + " chars)");
}

const headers = {
  "Authorization": "Bearer " + loginData.access_token,
  "Accept":        "application/json",
  "Content-Type":  "application/json"
};

// ── STEP 3: READ CURRENT HOT WATER STATE ────────────────────────
const getRes  = await fetch(BASE + "/Registers/Installations/" + INSTALL + "/Groups/REG_GROUP_HOT_WATER", { headers });
const getData = JSON.parse(await getRes.text());
const currentValue = getData[0].registerValue;
log("Current: " + (currentValue === 1 ? "ON" : "OFF"));

// ── STEP 4: HANDLE "STATUS" COMMAND ─────────────────────────────
if (command === "status") {
  const s = currentValue === 1 ? "on" : "off";
  if (statusVar) await Homey.logic.updateVariable({ id: statusVar.id, variable: { value: s } });
  log("Status: " + s);
  return s;
}

// ── STEP 5: VALIDATE THE COMMAND ────────────────────────────────
const targetValue = command === "on" ? 1 : command === "off" ? 0 : -1;
if (targetValue === -1) {
  log("❌ Unknown command: " + command + " (use on/off/status)");
  return;
}

// ── STEP 6: SKIP IF ALREADY IN TARGET STATE ──────────────────────
if (currentValue === targetValue) {
  log("Already " + command.toUpperCase() + " — no change needed");
  return command;
}

// ── STEP 7: SEND THE COMMAND TO THE HEAT PUMP ───────────────────
const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
  const r = Math.random() * 16 | 0;
  return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
});

const postRes = await fetch(BASE + "/Registers/Installations/" + INSTALL + "/Registers", {
  method: "POST",
  headers,
  body: JSON.stringify({
    registerSpecificationId: REG_SPEC_ID,
    registerValue: targetValue,
    clientUuid: uuid
  })
});
log("Set " + command.toUpperCase() + ": HTTP " + postRes.status);

// ── STEP 8: SAVE THE NEW STATE TO HOMEY ─────────────────────────
if (statusVar) {
  await Homey.logic.updateVariable({ id: statusVar.id, variable: { value: command } });
}

return command;

Step 6 — Set up Flows

Temperature polling (every 10 minutes):

WHEN: Every 10 minutes

THEN: Run script thermia_read_temperatures

Hot water OFF at 06:00:

WHEN: Time is 06:00

THEN: Set thermia_hotwater_command to off

THEN: Run script thermia_hotwater

Hot water ON at 22:00:

WHEN: Time is 22:00

THEN: Set thermia_hotwater_command to on

THEN: Run script thermia_hotwater

Optional — Safety rescue if water gets too cold:

WHEN: Every 10 minutes

AND: Time is between 06:01 and 21:59

AND: thermia_temp_hot_water is less than 50

THEN: Set thermia_hotwater_command to on

THEN: Run script thermia_hotwater

Notes

The client_id in the scripts is the public Thermia Online app ID — it is the same for all users and not a secret

The REG_SPEC_ID (29442) is the register ID for hot water on/off on the Diplomat Inverter M — it may be different on other Thermia models. If it doesn’t work, check the DevTools network traffic when toggling hot water in the Thermia web app and look for the registerSpecificationId value in the POST request payload

The refresh token auto-renews on every script run, so you should never need to paste a new one manually as long as the polling flow runs at least once a day

Temperature graphs appear automatically in Homey Insights once the polling flow has been running for a while

5 posts - 3 participants