Split heat pump power consumption by operating mode

Hi all,

I would like to split up the power metering that I currently have running for my heat pump.

At the moment, I have one power meter installed that measures the total power consumption of the heat pump. However, since the heat pump has several operating modes, I would like to see how much energy is consumed per mode.

For example, the total cumulative consumption is 7.5 kWh, but I would like to split this into something like:

  • Cooling: 5.0 kWh
  • Tap water: 2.2 kWh
  • Idle / standby usage: 0.3 kWh

I already created an Advanced Virtual Device for tap water.

In an Advanced Flow, I can use IF cards to check which mode the heat pump is currently running in. But I am not sure what the best next step is after that.

How can I use the current heat pump mode to add the measured energy consumption to the correct virtual device or counter?

Thanks for helping out.

I managed to get it working, I created one Advanced Virtual Device and within this device 4 Number fields.

I also created 6 Variables

Then I (ChatGPT) created a script, and this was a first time right script:

// ========================================
// Warmtepomp kWh verdelen per modus
// Met automatische dagreset
// Gebruikt cumulatieve kWh-meterstand
// ========================================


// =====================
// INSTELLINGEN
// =====================

// Naam van je Homey-device met de cumulatieve kWh-waarde
const ENERGY_DEVICE_NAME = 'Warmtepomp pm';

// Capability voor cumulatief energieverbruik in kWh
// Meestal is dit 'meter_power'
const ENERGY_CAPABILITY = 'meter_power';

// Tijdzone voor dagreset
const TIME_ZONE = 'Europe/Amsterdam';

// Logic variabele waarin de huidige modus staat
const MODE_VARIABLE_NAME = 'HP Modus';

// Logic variabele waarin we de vorige totaalstand bewaren
const PREVIOUS_TOTAL_VARIABLE_NAME = 'HP Vorige totaal kWh';

// Logic tekstvariabele waarin we bijhouden wanneer de laatste dagreset was
const LAST_RESET_DATE_VARIABLE_NAME = 'HP Laatste reset datum';

// Logic variabelen waarin we de kWh per modus optellen
const TARGET_VARIABLES = {
  verwarmen: 'HP kWh Verwarmen',
  koelen: 'HP kWh Koelen',
  warmwater: 'HP kWh Warmwater',
  standby: 'HP kWh Standby',
};

// Beveiliging tegen rare sprongen.
// Als je script elke minuut draait, is 5 kWh per run extreem hoog.
const MAX_DELTA_KWH = 5;


// =====================
// HULPFUNCTIES
// =====================

async function getDeviceByName(deviceName) {
  const devices = await Homey.devices.getDevices();

  for (const device of Object.values(devices)) {
    if (device.name === deviceName) {
      return device;
    }
  }

  throw new Error(`Device niet gevonden: ${deviceName}`);
}

async function getLogicVariableByName(variableName) {
  const variables = await Homey.logic.getVariables();

  for (const variable of Object.values(variables)) {
    if (variable.name === variableName) {
      return variable;
    }
  }

  throw new Error(`Logic variabele niet gevonden: ${variableName}`);
}

async function getLogicValue(variableName) {
  const variable = await getLogicVariableByName(variableName);
  return variable.value;
}

async function setLogicValue(variableName, value) {
  const variable = await getLogicVariableByName(variableName);

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

function normalizeMode(value) {
  if (value === null || value === undefined) {
    return 'standby';
  }

  const mode = String(value).toLowerCase().trim();

  if (mode === 'verwarmen' || mode === 'heating' || mode === 'heat') {
    return 'verwarmen';
  }

  if (mode === 'koelen' || mode === 'cooling' || mode === 'cool') {
    return 'koelen';
  }

  if (
    mode === 'warmwater' ||
    mode === 'tapwater' ||
    mode === 'tap water' ||
    mode === 'dhw'
  ) {
    return 'warmwater';
  }

  if (mode === 'standby' || mode === 'stand-by' || mode === 'idle') {
    return 'standby';
  }

  return 'standby';
}

function round(value, decimals) {
  const factor = Math.pow(10, decimals);
  return Math.round(value * factor) / factor;
}

function getTodayDateKey() {
  const parts = new Intl.DateTimeFormat('nl-NL', {
    timeZone: TIME_ZONE,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  }).formatToParts(new Date());

  const year = parts.find(part => part.type === 'year').value;
  const month = parts.find(part => part.type === 'month').value;
  const day = parts.find(part => part.type === 'day').value;

  return `${year}-${month}-${day}`;
}

async function resetDailyCounters() {
  for (const variableName of Object.values(TARGET_VARIABLES)) {
    await setLogicValue(variableName, 0);
  }
}


// =====================
// HOOFDSCRIPT
// =====================

// 1. Lees huidige totaalstand van de warmtepomp
const energyDevice = await getDeviceByName(ENERGY_DEVICE_NAME);

if (!energyDevice.capabilitiesObj[ENERGY_CAPABILITY]) {
  throw new Error(
    `Capability '${ENERGY_CAPABILITY}' niet gevonden op device '${ENERGY_DEVICE_NAME}'.`
  );
}

const currentTotalRaw = energyDevice.capabilitiesObj[ENERGY_CAPABILITY].value;
const currentTotalKwh = Number(currentTotalRaw);

if (isNaN(currentTotalKwh)) {
  throw new Error(`Ongeldige totaalstand: ${currentTotalRaw}`);
}


// 2. Lees vorige totaalstand
const previousTotalRaw = await getLogicValue(PREVIOUS_TOTAL_VARIABLE_NAME);
const previousTotalKwh = Number(previousTotalRaw);


// 3. Vandaag bepalen
const todayDateKey = getTodayDateKey();

let lastResetDateRaw = await getLogicValue(LAST_RESET_DATE_VARIABLE_NAME);
let lastResetDate = '';

if (lastResetDateRaw !== null && lastResetDateRaw !== undefined) {
  lastResetDate = String(lastResetDateRaw).trim();
}


// 4. Eerste run herkennen
if (!previousTotalKwh || previousTotalKwh <= 0 || isNaN(previousTotalKwh)) {
  await setLogicValue(PREVIOUS_TOTAL_VARIABLE_NAME, currentTotalKwh);
  await setLogicValue(LAST_RESET_DATE_VARIABLE_NAME, todayDateKey);

  log('Eerste run of vorige totaalstand was leeg.');
  log(`Huidige totaalstand opgeslagen: ${currentTotalKwh} kWh`);
  log(`Resetdatum opgeslagen: ${todayDateKey}`);
  log('Er is nog niets geboekt.');

  return {
    status: 'first_run',
    currentTotalKwh: currentTotalKwh,
    resetDate: todayDateKey,
  };
}


// 5. Als resetdatum nog leeg is, alleen vullen en verder normaal doorgaan
if (!lastResetDate) {
  await setLogicValue(LAST_RESET_DATE_VARIABLE_NAME, todayDateKey);
  lastResetDate = todayDateKey;

  log(`Resetdatum was leeg en is gezet naar: ${todayDateKey}`);
}


// 6. Automatische dagreset
if (lastResetDate !== todayDateKey) {
  await resetDailyCounters();

  // Belangrijk:
  // vorige totaalstand gelijkzetten aan huidige fysieke meterstand
  // zodat de nieuwe dag vanaf deze stand begint.
  await setLogicValue(PREVIOUS_TOTAL_VARIABLE_NAME, currentTotalKwh);
  await setLogicValue(LAST_RESET_DATE_VARIABLE_NAME, todayDateKey);

  log('Dagreset uitgevoerd.');
  log(`Vorige resetdatum: ${lastResetDate}`);
  log(`Nieuwe resetdatum: ${todayDateKey}`);
  log(`Nieuwe startstand: ${currentTotalKwh} kWh`);
  log('Alle dagtellers zijn op 0 gezet.');

  return {
    status: 'daily_reset',
    previousResetDate: lastResetDate,
    newResetDate: todayDateKey,
    newStartTotalKwh: currentTotalKwh,
  };
}


// 7. Bereken verschil sinds vorige run
let deltaKwh = currentTotalKwh - previousTotalKwh;


// 8. Bescherming tegen meter-reset of negatieve waarde
if (deltaKwh < 0) {
  await setLogicValue(PREVIOUS_TOTAL_VARIABLE_NAME, currentTotalKwh);

  log('Waarschuwing: totaalstand is lager dan vorige totaalstand.');
  log(`Vorige totaalstand: ${previousTotalKwh} kWh`);
  log(`Huidige totaalstand: ${currentTotalKwh} kWh`);
  log('Waarschijnlijk meter-reset. Er is niets geboekt.');

  return {
    status: 'meter_reset_detected',
    previousTotalKwh: previousTotalKwh,
    currentTotalKwh: currentTotalKwh,
  };
}


// 9. Bescherming tegen onrealistisch grote sprong
if (deltaKwh > MAX_DELTA_KWH) {
  await setLogicValue(PREVIOUS_TOTAL_VARIABLE_NAME, currentTotalKwh);

  log('Waarschuwing: onrealistisch grote kWh-sprong.');
  log(`Delta: ${deltaKwh} kWh`);
  log('Er is niets geboekt om foutieve telling te voorkomen.');

  return {
    status: 'delta_too_large',
    previousTotalKwh: previousTotalKwh,
    currentTotalKwh: currentTotalKwh,
    deltaKwh: deltaKwh,
  };
}


// 10. Als er geen nieuw verbruik is, alleen vorige stand bijwerken
if (deltaKwh === 0) {
  await setLogicValue(PREVIOUS_TOTAL_VARIABLE_NAME, currentTotalKwh);

  log('Geen nieuw verbruik sinds vorige run.');
  log(`Totaalstand: ${currentTotalKwh} kWh`);

  return {
    status: 'no_delta',
    currentTotalKwh: currentTotalKwh,
    resetDate: todayDateKey,
  };
}


// 11. Lees huidige modus
const rawMode = await getLogicValue(MODE_VARIABLE_NAME);
const mode = normalizeMode(rawMode);

if (!TARGET_VARIABLES[mode]) {
  throw new Error(`Geen doelvariabele gevonden voor modus: ${mode}`);
}


// 12. Tel delta op bij juiste teller
const targetVariableName = TARGET_VARIABLES[mode];

const currentModeTotalRaw = await getLogicValue(targetVariableName);
const currentModeTotal = Number(currentModeTotalRaw) || 0;

const newModeTotal = currentModeTotal + deltaKwh;
const newModeTotalRounded = round(newModeTotal, 3);

await setLogicValue(targetVariableName, newModeTotalRounded);


// 13. Sla huidige totaalstand op als vorige totaalstand
await setLogicValue(PREVIOUS_TOTAL_VARIABLE_NAME, currentTotalKwh);


// 14. Log resultaat
log(`Modus: ${mode}`);
log(`Vorige totaalstand: ${previousTotalKwh} kWh`);
log(`Huidige totaalstand: ${currentTotalKwh} kWh`);
log(`Delta: ${round(deltaKwh, 4)} kWh`);
log(`${targetVariableName}: ${newModeTotalRounded} kWh`);
log(`Resetdatum: ${todayDateKey}`);

return {
  status: 'ok',
  mode: mode,
  previousTotalKwh: previousTotalKwh,
  currentTotalKwh: currentTotalKwh,
  deltaKwh: round(deltaKwh, 4),
  targetVariableName: targetVariableName,
  newModeTotal: newModeTotalRounded,
  resetDate: todayDateKey,
};

In short:

  • The physical kWh meter remains cumulative.
  • The script calculates the delta since the previous run.
  • The delta is assigned to the currently active mode.
  • The mode is stored in a Logic variable.
  • The script runs every minute and also when the mode changes.
  • The daily mode counters are reset automatically once per day.

New question I have, is there a way to show all 4 PM’s in the Energy tab? Now it shows the Main device, and by clicking on this device is shows the 4 PM’s. I expected to see the 4 PM’s separate in the Energy tab.