/** * Script zum zyklischen Auslesen der aktuellen Werte und setzen des ChargeModes einer Myenergi Zappi Wallbox * * Im Normalfall sollten die CT1-3 die Ladeleistung der Wallbox und CT4-6 die Messwerte der externen CTs sein. * Je nach Installation kann das aber anders sein. * Im Oberen Teil dieses Scripts sind ein paar Konstanten zu setzen um den Zugriff auf die Myenergi-API zu konfigurieren und wo die Datenpunkte aerzeugt werden. * * Als Basis für den Zugriff dient die myenergi-api https://github.com/bisand/myenergi-api * Die API muss als zusätzliches NPM-Modul in den Einstellungen des JS-Adapters bekannt gemacht werden. * * * BENUTZUNG AUF EIGENE GEFAHR - NUR FÜR ENTWICKLER */ var test = require('myenergi-api'); /** * Root der Datenpunkte, die erzeugt werden */ const dataRoot = "0_userdata.0.zappiTest1"; //const dataRoot = "javascript.0.zappitest1"; /** * Seriennummer des Hubs */ const hubSerialNo = 4711; /** * Seriennummer des Zappis */ const zappiSerialNo = 0815; /** * Der im Myenergi-Portal erzeugte API-Key für den Hub. * (nicht das Login Passwort) */ const hubApiKey = "API-Key"; /** * Anzahl der Phasen des Zappis, bei 3 Phasen werden zusätzliche Datenpunkte erzeugt mit den Summen der 3 Phasen */ const phaseInstallation = 3; /** * Poll Intervall in dem die Werte abgerufen werden in Sekunden */ const pollInterval = 10; /** * Der "Client" um auf die API Schnittstelle zuzugreifen */ const myenergi = new test.MyEnergi(hubSerialNo, hubApiKey); /** * Definition der Komanndopunkte */ const ZappiCommands = { /** * Zappi Charge Mode * * 1 = Fast * 2 = Eco * 3 = Eco+ * 4 = Stopped */ chargemodeFast: { id: 'fast', group: 'chargemode', name: 'sets chargemode FAST', type: 'boolean', role: "button", read: false, write: true }, chargemodeEco: { id: 'eco', group: 'chargemode', name: 'sets chargemode ECO', type: 'boolean', role: "button", read: true, write: true }, chargemodeEcoPlus: { id: 'ecoplus', group: 'chargemode', name: 'sets chargemode ECO+', type: 'boolean', role: "button", read: true, write: true }, chargemodesStop: { id: 'stop', group: 'chargemode', name: 'sets chargemode STOP', type: 'boolean', role: "button", read: true, write: true } } /** * Definition der Datenpunkte * Es werden noch nicht alle möglichen Datenpunkte abgefragt. TBD */ const ZappiValues = { sno: { id: 'serialId', name: 'Seriennummer', type: 'string' }, dat: { id: 'datum', name: 'Current Date', type: 'string' }, tim: { id: 'zeit', name: 'Current Time', type: 'string' }, ectp1: { id: 'powerCt1', name: 'Physical CT connection 1 value Watts', type: 'number', unit: "W" }, ectp2: { id: 'powerCt2', name: 'Physical CT connection 2 value Watts', type: 'number', unit: "W" }, ectp3: { id: 'powerCt3', name: 'Physical CT connection 3 value Watts', type: 'number', unit: "W" }, ectp4: { id: 'powerCt4', name: 'Physical CT connection 4 value Watts', type: 'number', unit: "W" }, ectp5: { id: 'powerCt5', name: 'Physical CT connection 5 value Watts', type: 'number', unit: "W" }, ectp6: { id: 'powerCt6', name: 'Physical CT connection 6 value Watts', type: 'number', unit: "W" }, dst: { id: 'dst', name: 'Use Daylight Savings Time', type: 'number' }, frw: { id: 'frequency', name: 'Supply Frequency', type: 'number', unit: "Hz" }, div: { id: 'diversion', name: 'Diversion amount Watts', type: 'number', unit: "W" }, gen: { id: 'generated', name: 'Generated Watts', type: 'number', unit: "W" }, grd: { id: 'grid', name: 'Watts from grid (?)', type: 'number', unit: "W" }, fwv: { id: 'firmwareVersion', name: 'Firmware Version', type: 'string' }, mgl: { id: 'minimumGreenLevel', name: 'Minimum Green Level', type: 'number', unit: "%" }, pha: { id: 'phases', name: 'Number of Phases in Installation ?', type: 'number', }, pri: { id: 'priority', name: 'Priority', type: 'number', }, /** * Status * 1 = Paused, * 3 = Diverting/Charging, * 5 = Complete */ sta: { id: 'status', name: 'Status', type: 'number', }, tz: { id: 'timezone', name: 'Timezone (?)', type: 'number', }, vol: { id: 'voltage', name: 'Voltage', type: 'number', divisor: 10, unit: "V" }, che: { id: 'lastCharge', name: 'Latest charge session. Charge added in kWh', type: 'number', unit: "kWh" }, /** * Lock Status * * 4 bits * 1st digit - ?, * 2nd digit - 1 unlocked, 0 locked * TBD */ lck: { id: 'lockState', name: 'Lock Status', type: 'number', }, /** * Charger Status * * A = EV Disconnected, * B1 = EV Connected, * B2 = Waiting for EV, * C1 = EV Ready to Charge, * C2 = Charging, * F = Fault */ pst: { id: 'chargerState', name: 'Charger Status', type: 'string', }, /** * Boost Mode. * * 1 = ON * 0 = OFF * * @example * // Logic for selecting MANUAL or SMART * if (tbh !== undefined && bsm === 1) * MANUAL * else * SMART */ bsm: { id: 'boostModeManual', name: 'Boost Mode Manual', type: 'number', }, bst: { id: 'boostModeTimer', name: 'Boost Mode Timer (?)', type: 'number', }, bss: { id: 'boostModeSmart', name: 'Boost Mode Smart (?)', type: 'number', }, /** * Zappi Charge Mode * * 1 = Fast * 2 = Eco * 3 = Eco+ * 4 = Stopped */ zmo: { id: 'chargeMode', name: 'Zappi Charge Mode', type: 'number', } } function createZappiDataPoints() { for (const [key,value] of Object.entries(ZappiValues)) { const objectName = dataRoot + "." + value.id; if ( !existsState(objectName )) { console.log("Datenpunkt angelegt" + objectName); createState(objectName, { name: value.name, type: value.type, write: true, role: (value.role?value.role:'value'), unit: (value.unit?value.unit:'') }) } } if(phaseInstallation == 3) { var objectName = dataRoot + ".PowerCtSumGroup1"; if ( !existsState(objectName )) { console.log("Datenpunkt angelegt" + objectName); createState(objectName, { name: "Sum CT 1-3", type: "number", write: true, role: "value", unit: "W" }) } objectName = dataRoot + ".PowerCtSumGroup2"; if ( !existsState(objectName )) { console.log("Datenpunkt angelegt" + objectName); createState(objectName, { name: "Sum CT 4-6", type: "number", write: true, role: "value", unit: "W" }) } } } function createCommands(){ const commandDataRoot = dataRoot+".commands"; for (const [key,value] of Object.entries(ZappiCommands)) { const objectName = commandDataRoot+"."+value.group+"."+value.id; if ( !existsState(objectName )) { console.log("Command-Datenpunkt angelegt" + objectName); createState(objectName, { type: value.type, name: value.name, role: value.role, write: value.write, read: value.read }) } setState(objectName, false , true); } } function updateDatapoints(statusZappi){ //console.log("************* updating **********"); const statesMap = new Map(Object.entries(statusZappi)); for (const [key,value] of Object.entries(ZappiValues)) { const objectName = dataRoot + '.' + value.id; //console.log("aktualisiere: " + key + " - " + value.id); //console.log("aktueller Wert:" + convertValue(value, statesMap.get(key))); //console.log("Datenpunkt: " + objectName); const convertdValue = convertValue(value, statesMap.get(key)); setState(objectName, convertdValue , true); } if(phaseInstallation == 3) { var ectp1 = 0; var ectp2 = 0; var ectp3 = 0; var ectp4 = 0; var ectp5 = 0; var ectp6 = 0; if( !(typeof statusZappi.ectp1 === "undefined") ) { ectp1 = statusZappi.ectp1; } if( !(typeof statusZappi.ectp2 === "undefined") ) { ectp2 = statusZappi.ectp2; } if( !(typeof statusZappi.ectp3 === "undefined") ) { ectp3 = statusZappi.ectp3; } if( !(typeof statusZappi.ectp4 === "undefined") ) { ectp4 = statusZappi.ectp4; } if( !(typeof statusZappi.ectp5 === "undefined") ) { ectp5 = statusZappi.ectp5; } if( !(typeof statusZappi.ectp6 === "undefined") ) { ectp6 = statusZappi.ectp6; } setState(dataRoot+".PowerCtSumGroup1", (ectp1+ectp2+ectp3) , true); setState(dataRoot+".PowerCtSumGroup2", (ectp4+ectp5+ectp6) , true); } //console.log("********* updating finished ******"); } const convertValue = (tag, data) => { switch(tag.type) { case 'number': if(data == null) { return 0; } return parseFloat(data) / (tag.divisor?tag.divisor:1); case 'boolean': if (tag.bitnum) { let mask = 1 << (tag.bitnum); // vorher tag.bitnum-1 // console.log("Convert: " + tag.name + " - " + ((parseInt(data) & mask) != 0)); return (parseInt(data) & mask) != 0 } return data !== '0'; case 'string': return "" + data; } return null } const run = (async () => { const zappiResponse = JSON.stringify(await myenergi.getStatusZappi(zappiSerialNo)); //console.log(zappiResponse); var statusZappi = JSON.parse(zappiResponse); updateDatapoints(statusZappi); }); on({id: Array.prototype.slice.apply($("state[id=0_userdata.0.zappiTest1.commands.chargemode.*]")), change: "gt"}, async function (obj) { setState(obj.id, false , true); var modeApi = null; if(obj.id.endsWith("fast")) { modeApi = test.ZappiChargeMode.Fast; } else if (obj.id.endsWith("stop")) { modeApi = test.ZappiChargeMode.Off; } else if (obj.id.endsWith("eco")) { modeApi = test.ZappiChargeMode.Eco; } else if (obj.id.endsWith("ecoplus")) { modeApi = test.ZappiChargeMode.EcoPlus; } try { //console.log("Setze Mode " + modeApi); const resultJson = await myenergi.setZappiChargeMode(zappiSerialNo, modeApi); const result = JSON.parse(JSON.stringify(resultJson)); if(!(result.status = 0)) { setState(obj.id, false , true); } else { console.log("Zappi ChargeMode konnte nicht gesetzt werden."); } } catch (e) { console.log(e); } }); createZappiDataPoints(); createCommands(); schedule("*/"+pollInterval+" * * * * *", run);