Hi Arjen,
I’m not only interested in this; I’ve already started working on it. The standard assumptions made by VRM do not fully align with reality, which, in my opinion, means that DESS is not operating as optimally as it could.
While setting up the flow, I ran into the issue that simply sending the EV forecast to VRM via the API is not sufficient. This caused several problems, as it also resulted in the base consumption being adjusted.
I now have the following working:
- Every 15 minutes, an HTTP request is sent to http://EVCC-ip:7070/api/loadpoints/1/plan. The EVCC response is stored in flow.evccPlan.
- Subsequently, the total consumption forecast, EV consumption forecast (VRM), and HP consumption forecast are retrieved from the VRM API and stored as flow.vrmForecast.
- The function below converts the EVCC charging plan into the VRM format. The updated EV, HP, and BASE forecasts are then sent back to VRM for the next 48 hours. I have also added a msg.reset for testing purposes.
This approach allows me to update the EV forecast from EVCC while keeping the HP and BASE forecasts unchanged.
If you have any feedback, please let me know
[
{
"id": "f930cb0bb5a29883",
"type": "inject",
"z": "9b566e92c3b55369",
"name": "Full reset",
"props": [
{
"p": "reset",
"v": "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48]",
"vt": "json"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 1760,
"y": 600,
"wires": [
[
"4845cf986f1e010d"
]
]
},
{
"id": "142d0d9667bb2a61",
"type": "function",
"z": "9b566e92c3b55369",
"name": "EVCC naar DESS Forecast",
"func": "// Haal beide inputs op uit de flow context geheugens\nlet evccRaw = flow.get(\"evccPlan\");\nlet vrmData = flow.get(\"vrmForecast\");\n\n// ---------------------------------------------------------\n// 1. Veiligheidscontrole: Is alle data aanwezig?\n// ---------------------------------------------------------\nif (!evccRaw) {\n node.error(\"Wachten op evcc data... Nog niet gevonden in flow.evccPlan\", msg);\n return null;\n}\nif (!vrmData || !vrmData.records) {\n node.error(\"Wachten op VRM data... Nog niet gevonden in flow.vrmForecast\", msg);\n return null;\n}\n\n// ---------------------------------------------------------\n// 2. EVCC JSON-string omzetten naar object indien nodig\n// ---------------------------------------------------------\nlet evccData = evccRaw;\nif (typeof evccData === \"string\") {\n try {\n evccData = JSON.parse(evccData);\n } catch (e) {\n node.error(\"Ongeldige JSON-string in flow.evccPlan\", msg);\n return null;\n }\n}\n\nif (!evccData.plan || !Array.isArray(evccData.plan)) {\n node.error(\"Geen 'plan' array gevonden in evcc data\", msg);\n return null;\n}\n\n// evcc /plan API bevat meestal geen live power, we pakken 11000W (11kW) als veilige fallback\nconst evPower = Number(evccData.power) || 11000;\n\n// ---------------------------------------------------------\n// 3. VRM Data indexeren op basis van Unix Timestamp (ms)\n// ---------------------------------------------------------\nconst vrmMap = {}; \nconst records = vrmData.records;\n\nif (records.vrm_consumption_fc) {\n records.vrm_consumption_fc.forEach(item => {\n const ts = item[0];\n if (!vrmMap[ts]) vrmMap[ts] = { total: 0, ev: 0, hp: 0 };\n vrmMap[ts].total = item[1];\n });\n}\nif (records.vrm_consum_evcs_fc) {\n records.vrm_consum_evcs_fc.forEach(item => {\n const ts = item[0];\n if (!vrmMap[ts]) vrmMap[ts] = { total: 0, ev: 0, hp: 0 };\n vrmMap[ts].ev = item[1];\n });\n}\nif (records.vrm_consum_hp_fc) {\n records.vrm_consum_hp_fc.forEach(item => {\n const ts = item[0];\n if (!vrmMap[ts]) vrmMap[ts] = { total: 0, ev: 0, hp: 0 };\n vrmMap[ts].hp = item[1];\n });\n}\n\n// ---------------------------------------------------------\n// 4. Huidige tijd bepalen voor offset berekening\n// ---------------------------------------------------------\nconst now = new Date();\nconst currentHour = new Date(now);\ncurrentHour.setMinutes(0, 0, 0);\n\n// ---------------------------------------------------------\n// 5. EVCC Laadplanning koppelen aan VRM Timestamps\n// ---------------------------------------------------------\nconst evccMap = {}; \n\nfor (const slot of evccData.plan) {\n const start = new Date(slot.start).getTime();\n const end = new Date(slot.end).getTime();\n\n if (end <= now.getTime()) {\n continue;\n }\n\n const effectiveStart = Math.max(start, now.getTime());\n\n for (const tsStr in vrmMap) {\n const ts = Number(tsStr);\n const hourStart = ts;\n const hourEnd = ts + 3600000; \n\n const overlapStart = Math.max(effectiveStart, hourStart);\n const overlapEnd = Math.min(end, hourEnd);\n const minutes = Math.max(0, (overlapEnd - overlapStart) / 60000);\n\n if (minutes > 0) {\n const averagePowerInHour = (evPower * minutes) / 60;\n if (!evccMap[ts]) evccMap[ts] = 0;\n evccMap[ts] += averagePowerInHour;\n }\n }\n}\n\n// ---------------------------------------------------------\n// 6. DESS Output Array opbouwen\n// ---------------------------------------------------------\nconst output = [];\n\nfor (const tsStr in vrmMap) {\n const ts = Number(tsStr);\n const dataHour = vrmMap[ts];\n\n const offset = Math.round((ts - currentHour.getTime()) / 3600000);\n\n // Alleen toekomstige uren verwerken\n if (offset >= 1) {\n // Formule: base = totaal - ev - hp\n let calculatedBase = dataHour.total - dataHour.ev - dataHour.hp;\n calculatedBase = Math.max(0, calculatedBase); // Nooit negatief\n\n // Bepaal de EV waarde op basis van de evcc match\n const finalEv = evccMap[ts] ? evccMap[ts] : 0;\n\n output.push({\n offset: offset,\n ev: Math.round(finalEv),\n hp: Math.round(dataHour.hp),\n base: Math.round(calculatedBase)\n });\n }\n}\n\n// Sorteren op netste volgorde van uren\noutput.sort((a, b) => a.offset - b.offset);\n\n// Geef een volledig schoon bericht terug om Node-RED waarschuwingen te voorkomen\nreturn {\n payload: output\n};\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1720,
"y": 520,
"wires": [
[
"4845cf986f1e010d"
]
]
}
]