[APP][Pro] Wallbox Charger - Smart EV charging technology made simple

Met onderstaand script lukt het me om de dingen te doen die ik wil.
Status opvragen, laden starten en stoppen, Amperes instellen.
Gemaakt met behulp van Grok (AI).

Het wordt allemaal aangestuurd via flows en gemonitord via het Dashboard van @Satoer . Ik heb er dus geen Virtual Device voor aangemaakt. Maar wellicht is dit ook mogelijk.
Status flow loopt iedere 2 minuten, meer kan waarschijnlijk, maar voor mij volstaat het en loopt het momenteel zonder errors :slight_smile:

// === WALLBOX PULSAR PLUS - HOMEYSCRIPT (volledig werkend 2026) ===
// Vul je gegevens in:
const username = 'hidden';          // myWallbox e-mail
const password = 'hidden';         // myWallbox wachtwoord
const MYCHARGERID = hidden;                // Numeriek ID uit je JSON: 417955

const BASEURL = "https://api.wall-box.com/";
const URL_AUTH = 'auth/token/user';
const URL_GROUPS = 'v3/chargers/groups';       // Voor status ophalen
const URL_REMOTE = `v3/chargers/${MYCHARGERID}/remote-action`;
const URL_SET_CURRENT = `v2/charger/${MYCHARGERID}`;

// Handmatige base64 (nodig in HomeyScript)
function toBase64(str) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  let output = '';
  let i = 0;
  str = unescape(encodeURIComponent(str));
  while (i < str.length) {
    const a = str.charCodeAt(i++);
    const b = str.charCodeAt(i++);
    const c = str.charCodeAt(i++);
    const triplet = (a << 16) | (isNaN(b) ? 0 : b << 8) | (isNaN(c) ? 0 : c);
    output += chars.charAt((triplet >> 18) & 63) +
              chars.charAt((triplet >> 12) & 63) +
              (isNaN(b) ? '=' : chars.charAt((triplet >> 6) & 63)) +
              (isNaN(c) ? '=' : chars.charAt(triplet & 63));
  }
  return output;
}

async function getToken() {
  const authString = username + ':' + password;
  const base64Auth = toBase64(authString);

  const response = await fetch(BASEURL + URL_AUTH, {
    method: 'POST',
    headers: {
      'Authorization': 'Basic ' + base64Auth,
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    const err = 'Auth fout: HTTP ' + response.status;
    console.log(err);
    await tag('Wallbox_error', err);
    throw new Error(err);
  }

  const data = await response.json();
  if (!data.jwt) {
    const err = 'Geen JWT in response';
    console.log(err);
    await tag('Wallbox_error', err);
    throw new Error(err);
  }
  return data.jwt;
}

// Status ophalen via groups (meest betrouwbaar)
async function getStatus(token) {
  const response = await fetch(BASEURL + URL_GROUPS, {
    method: 'GET',
    headers: {
      'Authorization': 'Bearer ' + token,
      'Accept': 'application/json'
    }
  });

  if (!response.ok) {
    const err = 'Groups fout: HTTP ' + response.status;
    console.log(err);
    await tag('Wallbox_error', err);
    throw new Error(err);
  }

  const data = await response.json();
  let chargerStatus = {
    status: 0,
    chargingPower: 0,
    maxChargingCurrent: 0,
    addedEnergy: 0,
    locked: false,
    connected: false
  };

  if (data.result && data.result.groups) {
    for (const group of data.result.groups) {
      if (group.chargers) {
        for (const ch of group.chargers) {
          if (ch.id === MYCHARGERID) {
            chargerStatus = {
              status: ch.status || 0,
              chargingPower: ch.chargingPower || 0,
              maxChargingCurrent: ch.maxChargingCurrent || 0,
              addedEnergy: ch.addedEnergy || 0,
              locked: ch.locked === 1,
              connected: ch.ocppConnectionStatus === 1
            };
            break;
          }
        }
      }
    }
  }

  if (chargerStatus.status === 0) {
    const err = 'Charger niet gevonden in groups';
    console.log(err);
    await tag('Wallbox_error', err);
    throw new Error(err);
  }

  return chargerStatus;
}

// Start / resume opladen
async function startCharging(token) {
  const response = await fetch(BASEURL + URL_REMOTE, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + token,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ action: 1 })  // 1 = resume/start
  });

  if (!response.ok) {
    const err = 'Start fout: HTTP ' + response.status;
    console.log(err);
    await tag('Wallbox_error', err);
    throw new Error(err);
  }
}

// Stop / pause opladen
async function stopCharging(token) {
  const response = await fetch(BASEURL + URL_REMOTE, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + token,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ action: 2 })  // 2 = pause/stop
  });

  if (!response.ok) {
    const err = 'Stop fout: HTTP ' + response.status;
    console.log(err);
    await tag('Wallbox_error', err);
    throw new Error(err);
  }
}

// Set max laadvermogen (amps)
async function setAmps(token, amps) {
  if (amps < 6 || amps > 32) {
    const err = 'Amps moet tussen 6 en 32 liggen';
    console.log(err);
    await tag('Wallbox_error', err);
    throw new Error(err);
  }
  const response = await fetch(BASEURL + URL_SET_CURRENT, {
    method: 'PUT',
    headers: {
      'Authorization': 'Bearer ' + token,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ maxChargingCurrent: amps })
  });

  if (!response.ok) {
    const err = 'Set amps fout: HTTP ' + response.status;
    console.log(err);
    await tag('Wallbox_error', err);
    throw new Error(err);
  }
}

// === Hoofdscript: run met argument via Flow ===
if (!args || args.length === 0 || typeof args[0] !== 'string') {
  const err = 'Geef een argument: status | start | stop | set 6 | set 10';
  console.log(err);
  await tag('Wallbox_error', err);
  throw new Error(err);
}

const arg = args[0].trim().toLowerCase();

try {
  const token = await getToken();

  if (arg === 'status') {
    const status = await getStatus(token);

    // Maak expliciete tags (nu verschijnen ze echt!)
    await tag('Wallbox_status', status.status);                  // Number: bijv. 182
    await tag('Wallbox_chargingPower', status.chargingPower);    // Number: kW
    await tag('Wallbox_maxCurrent', status.maxChargingCurrent);  // Number: A
    await tag('Wallbox_addedEnergy', status.addedEnergy);        // Number: kWh deze sessie
    await tag('Wallbox_locked', status.locked ? 'true' : 'false');
    await tag('Wallbox_connected', status.connected ? 'true' : 'false');
    await tag('Wallbox_lastUpdate', new Date().toLocaleString()); // Text: wanneer laatste update

    console.log('Status tags aangemaakt:', status);
    return 'Status opgehaald en tags gezet';

  } else if (arg === 'start') {
    await startCharging(token);
    await tag('Wallbox_lastAction', 'Gestart');
    await tag('Wallbox_error', '');  // Reset error
    console.log('Opladen gestart / hervat');
    return 'Gestart';

  } else if (arg === 'stop') {
    await stopCharging(token);
    await tag('Wallbox_lastAction', 'Gestopt');
    await tag('Wallbox_error', '');  // Reset error
    console.log('Opladen gestopt / gepauzeerd');
    return 'Gestopt';

  } else if (arg.startsWith('set ')) {
    const ampsStr = arg.split(' ')[1];
    const amps = parseInt(ampsStr, 10);
    if (isNaN(amps)) throw new Error('Ongeldig amps: ' + ampsStr);

    await setAmps(token, amps);
    await tag('Wallbox_lastAction', `Set ${amps}A`);
    await tag('Wallbox_maxCurrent', amps);  // Update direct
    await tag('Wallbox_error', '');  // Reset error
    console.log(`Max current gezet op ${amps}A`);
    return `Set ${amps}A`;

  } else {
    throw new Error('Ongeldig argument: ' + arg);
  }
} catch (err) {
  console.log('Hoofdfout:', err.message);
  await tag('Wallbox_error', err.message);
  throw err;
}