NEWS
Huawei Wallbox - S Charger (7kw/h / 22kw/h) Adapter test
-
Hey zusammen,
ich habe es hinbekommen einen 100% funktionstüchtigen Adapter hinzubekommen!
Inkl. ändern der Werte
Kommt die Tage über GIT!
-
@stolly82 das sieht ja sehr gut aus.;-)
Grüße
Fabio -
@stolly82 said in Huawei Wallbox - S Charger (7kw/h / 22kw/h) Adapter test:
Hey zusammen,
ich habe es hinbekommen einen 100% funktionstüchtigen Adapter hinzubekommen!
Inkl. ändern der Werte
Kommt die Tage über GIT!
@stolly82 said in Huawei Wallbox - S Charger (7kw/h / 22kw/h) Adapter test:
ich habe es hinbekommen einen 100% funktionstüchtigen Adapter hinzubekommen!
Inkl. ändern der Werte
Kommt die Tage über GIT!
Danke dass du deine Zeit und dein Wissen für einen ioBroker Adapter einsetzt. Stell diesen doch bitte allen interessierten Userns zur Verfügung indem du ihn in die Repositories aufnehmen läßt:
Infos dazu hier:
https://github.com/ioBroker/ioBroker.repositories#requirements-for-adapter-to-get-added-to-the-latest-repositoryUnd bezüglich direkter Installation von Github seien alle User gewarnt dies NICHT auf produktiven Systemen durchzuführen:
Von direkten Installation von GitHub - insbesondere auf produktiven Systemen - wird explizit abgeraten. GitHub Versionen können sich jederzeit (auch kurzfristig) ändern und durchaus auch in sich inkonsistent und fehlerhaft sein. Versionsangaben von GitHub Installationen sind Schall und Rauch da die Versionsnummer zumindest bei Verwendung der standardmäßigen Umgebung erst im Zuge der Releaseerstellung geändert wird.
Auf explizite Aufforderung durch den Entwickler kann eine GitHub Installation zur Fehlereingrenzung oder zum Test neuer Funktionalität - unter Inkaufnahme des erhöhten Risikos - natürlich erfolgen.
Adapter die nur via GitHub oder npm (also NICHT aus einem der beiden Repositories) installierbar sind sind mit erhöhter Vorsicht zu betrachten. Hier sollte der Entwickler drum ersucht werden eine Aufnahme in die Repositories zu veranlassen indem z.B. ein Issue im Adapterrepository erstellt wird.
Und falls es irgendwie unklar ist:
ioBroker unterstützt folgende Arten von Installation:
-
aus dem STABLE Repository
Das sind Adapter Releases die keine groben Fehler aufweisen (sollten). Natürlich kann es auch dort Fehler geben, die Behebung davon obliegt dem jeweiligen Dev und kann ggF auch dauern.
-
aus dem LATEST Repository
Das sind Adapter Releases die neu erstellt wurden und nur rudimentär getestet sind - oft auch als BETA Releases bezeichnet. Releases aus dem LATEST sind primär für unsere zahlreichen freiwilligen Tester gedacht. BETA / LATEST Releases können durchaus Fehler aufweisen - auch wenn ich davon ausgehe dass jeder aintainer dies zu vermeiden versucht. Vom Einsatz auf produktiven Systemen wird abgeraten außer man braucht irgenein neues Feature (z.B. neues Gerät) unbedingt. Hier muss dann jeder Entscheiden was ihm wichtig ist.
-
direkt von GITHUB
Von Installationen direkt aus Giuthub wird definitiv abgeraten außer auf Anweisung des Maintainers und für den Fall dass man gemeinsam mit diesem etwas testen will. Details siehe oben.
-
direkt von npm
Diese Installation kann erforderlich sein, wenn man eine bestimmte Version installieren möchte / muss. Im Normalfall sollte man direkte npm Installationen ebenso meiden wie direkte GitHub Installationen - ausgenommen um z.B. zu einer bekannten Version downzugraden - obwohl hier wenigstens ein definierter Stand garantiert ist.
Adapter die NUR via npm und/oder nur via GitHub installierbar sind sollte man meiden - diese wurden nicht mal einem rudimentären Review unterzogen und sollten mit dem Attribut "vollständig auf eigenes Risiko verwenden" installiert werden. Hier empfiehlt es sich den Developer zu ersuchen eine Aufnahme in die Repos zu veranlassen. Wenn dieser darauf nicht reagiert sollte man von einer eher kurzen und unklaren Lebensdauer des Adapters ausgehen. Support meiner-/unsererseits für solche Adapter ist mit Sicherheit minimalistisch.
-
-
@stolly82 Mich würde dieser Adapter auch interessieren und ich würde beim Testen mithelfen. Wäre auch definitiv ein guter Kandidat für einen "offiziellen" Adapter inkl. Repo-Aufnahme.
-
Hier schon mal vorab das Javascript, das könnt Ihr zum testen so übernehmen!
Einfach oben die Zugangsdaten aus der mobilen App eingeben und fertig.
@baerengraben @Fabioconst axios = require('axios'); // Konfiguration const CONFIG = { userName: 'MySUERNAME', password: 'MyPASSWORD', grantType: 'password', appClientId: '86366133-B8B5-41FA-8EB9-E5A64229E3E1', httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }), interval: 1200000 // Intervall in Millisekunden für wiederkehrende Abfragen }; // Beschreibbare IDs (manuell festgelegt) const WRITABLE_IDS = [ 20001, 20002, 20004, 20005, 20006, 20010, 20011, 20013, 20015, 20016, 20017, 538976529, 538976290, 538976533, 538976534, 538976570, 538976800 ]; let authData = null; // Authentifizierung und Planung der Token-Erneuerung async function authenticateAndScheduleRenewal() { authData = await authenticate(); if (!authData) { log('Authentifizierung fehlgeschlagen', 'error'); return; } const refreshTime = (authData.expires - 300) * 1000; // Erneuerung 5 Minuten vor Ablauf log(`Nächste Token-Erneuerung in ${(refreshTime / 1000 / 60).toFixed(2)} Minuten`, 'info'); setTimeout(authenticateAndScheduleRenewal, refreshTime); } // Authentifizierung async function authenticate() { try { const response = await axios.post( "https://intl.fusionsolar.huawei.com:32800/rest/neteco/appauthen/v1/smapp/app/token", JSON.stringify({ userName: CONFIG.userName, value: CONFIG.password, grantType: CONFIG.grantType, verifyCode: "", appClientId: CONFIG.appClientId }), { headers: { "Content-Type": "application/json" }, httpsAgent: CONFIG.httpsAgent } ); if (response.data && response.data.data && response.data.data.accessToken) { log("Benutzer validiert. Antwort: " + JSON.stringify(response.data), 'info'); setState('huawei-smart-charger.info.connection', true, true); response.data.data.expiryTime = new Date().getTime() + (response.data.data.expires * 1000); return response.data.data; } else { log("Fehler bei der Authentifizierung: Ungültige Antwort. Antwort: " + JSON.stringify(response.data), 'error'); setState('huawei-smart-charger.info.connection', false, true); return null; } } catch (error) { log("Fehler bei der Authentifizierung: " + error.message, 'error'); setState('huawei-smart-charger.info.connection', false, true); return null; } } // Benutzerinformationen abrufen async function getUserDetailInfo() { try { const response = await axios.get( "https://" + authData.regionFloatIp + ':32800/rest/neteco/phoneapp/v1/datacenter/getuserdetailinfo', { headers: { 'roaRand': authData.roaRand, 'Cookie': 'locale=de-de;bspsession=' + authData.accessToken + ';dp-session=' + authData.accessToken + '; Secure; HttpOnly' }, httpsAgent: CONFIG.httpsAgent } ); log("Benutzerinformationen: " + JSON.stringify(response.data), 'info'); const userInfo = response.data; await createAndSetObjects('huawei-smart-charger.User', userInfo); } catch (error) { log("Fehler beim Abrufen der Benutzerinformationen: " + error.message, 'error'); } } // Serverinformationen abrufen async function getServerInfo() { try { const response = await axios.get( "https://" + authData.regionFloatIp + ":32800/rest/neteco/phoneapp/v2/fusionsolarbusiness/company/getorganizationcompanybyuser", { headers: { 'roaRand': authData.roaRand, 'Cookie': 'locale=de-de;bspsession=' + authData.accessToken + ';dp-session=' + authData.accessToken + '; Secure; HttpOnly' }, httpsAgent: CONFIG.httpsAgent } ); log("Serverinformationen: " + JSON.stringify(response.data), 'info'); const serverInfo = response.data.data; await createAndSetObjects('huawei-smart-charger.Server', serverInfo); } catch (error) { log("Fehler beim Abrufen der Serverinformationen: " + error.message, 'error'); } } // Station-Liste abrufen async function getStationList() { try { const response = await axios.post( "https://" + authData.regionFloatIp + ":32800/rest/pvms/web/station/v1/station/station-list", { "locale": "de_DE", "sortId": "createTime", "timeZone": "2.00", "pageSize": "11", "supportMDevice": "1", "sortDir": "DESC", "curPage": 1 }, { headers: { 'roaRand': authData.roaRand, 'Cookie': 'locale=de-de;bspsession=' + authData.accessToken + ';dp-session=' + authData.accessToken + '; Secure; HttpOnly', "Content-Type": "application/json" }, httpsAgent: CONFIG.httpsAgent } ); log("Station-Liste: " + JSON.stringify(response.data), 'info'); const stationList = response.data.data.list[0]; await createAndSetObjects('huawei-smart-charger.Station', stationList); } catch (error) { log("Fehler beim Abrufen der Station-Liste: " + error.message, 'error'); } } // Wallbox-Informationen abrufen async function getWallboxInfo() { try { const parentDn = getState('huawei-smart-charger.Station.dn').val; if (!parentDn) { await getUserDetailInfo(); await getServerInfo(); await getStationList(); return await getWallboxInfo(); } const response = await axios.post( "https://" + authData.regionFloatIp + `:32800/rest/neteco/web/config/device/v1/device-list`, "conditionParams.curPage=0&conditionParams.mocTypes=60080&conditionParams.parentDn=" + parentDn + "&conditionParams.recordperpage=500", { headers: { 'roaRand': authData.roaRand, 'Cookie': 'locale=de-de;bspsession=' + authData.accessToken + ';dp-session=' + authData.accessToken + '; Secure; HttpOnly', "Content-Type": "application/x-www-form-urlencoded" }, httpsAgent: CONFIG.httpsAgent } ); log("Wallbox-Informationen: " + JSON.stringify(response.data), 'debug'); const wallboxInfo = response.data.data[0]; // Erstelle den Basisordner für die Wallbox-Informationen const basePath = `huawei-smart-charger.WallboxInfo`; // Speichere alle Daten in IoBroker, einschließlich paramValues for (const key in wallboxInfo) { if (key === 'paramValues') { // Speziell für paramValues const paramValuesPath = `${basePath}.paramValues`; for (const paramKey in wallboxInfo.paramValues) { const paramValue = wallboxInfo.paramValues[paramKey]; await setObject(`${paramValuesPath}.${paramKey}`, { type: 'state', common: { name: `Parameter ${paramKey}`, type: typeof paramValue, role: 'info', read: true, write: WRITABLE_IDS.includes(parseInt(paramKey)) // Schreibbare IDs berücksichtigen }, native: { id: paramKey, value: paramValue } }); await setState(`${paramValuesPath}.${paramKey}`, paramValue, true); } } else { // Speichere alle anderen Daten in IoBroker await setObject(`${basePath}.${key}`, { type: 'state', common: { name: key, type: typeof wallboxInfo[key], role: 'info', read: true, write: false }, native: { value: wallboxInfo[key] } }); await setState(`${basePath}.${key}`, wallboxInfo[key], true); } } // Speichere die dnId explizit im Basisordner const dnId = wallboxInfo.dnId; await setState(`${basePath}.dnId`, dnId, true); // Rufe die Wallbox-Konfigurationsinformationen auf, wenn paramValues vorhanden sind if (wallboxInfo.paramValues) { await getWallboxConfigInfo(dnId, wallboxInfo.paramValues); } } catch (error) { log("Fehler beim Abrufen der Wallbox-Informationen: " + error.message, 'error'); } } on({ id: new RegExp('^huawei-smart-charger\\.Wallbox\\.Settings\\.[^.]+$'), change: 'ne' }, async (obj) => { if (obj.state.ack) { return; // Wenn die Änderung von der API bestätigt wurde, ignoriere sie } const writableId = obj.native?.id; if (!writableId || !WRITABLE_IDS.includes(writableId)) { log(`Keine beschreibbare ID für ${obj.id} gefunden.`, 'warn'); return; } try { // Verwende die gespeicherte dnId aus dem Objekt const dnId = getState(`${obj.id.replace('.Settings.', '.Settings.dnId')}`).val; if (!authData || (authData && new Date().getTime() > authData.expiryTime)) { await authenticateAndScheduleRenewal(); } if (authData && dnId) { const requestData = { changeValues: [{ id: writableId, value: obj.state.val }], dnId: dnId }; log(`Sende Anfrage mit Daten: ${JSON.stringify(requestData)}`, 'debug'); const response = await axios.post( "https://" + authData.regionFloatIp + ":32800/rest/neteco/web/homemgr/v1/device/set-config-info", JSON.stringify(requestData), { headers: { 'roaRand': authData.roaRand, 'Cookie': `locale=de-de;bspsession=${authData.accessToken};dp-session=${authData.accessToken}; Secure; HttpOnly`, 'Content-Type': 'application/json', 'x-timezone-offset': '120', 'client-info': '_manufacturer=iPhone;_model=iPhone;_os_ver=18.0.1;_os=iOS;_app_ver=24.6.102006;_package_name=com.huawei.smartpvms;appClientId=86366133-B8B5-41FA-8EB9-E5A64229E3D5', 'x-requested-with': 'XMLHttpRequest', 'User-Agent': 'iCleanPower/24.6.102006 (iPhone; iOS 18.0.1; Scale/3.00)', 'Accept-Language': 'de-DE;q=1, en-DE;q=0.9, zh-Hans-DE;q=0.8', 'Accept': '*/*' }, httpsAgent: CONFIG.httpsAgent } ); log(`Antwort vom Server: ${JSON.stringify(response.data)}`, 'info'); if (response.data && response.data.code === 0) { await setState(obj.id, obj.state.val, true); } } } catch (error) { log(`Fehler beim Ändern des Werts für ${obj.id}: ${error.message}`, 'error'); if (error.response) { log(`Fehlerantwort vom Server: ${JSON.stringify(error.response.data)}`, 'error'); } } }); // Wallbox-Einstellungen abrufen und Konfigurationsinformationen anfordern async function getWallboxSettings() { const parentDn = getState('huawei-smart-charger.WallboxInfo.dn').val; try { if (!parentDn) { await getWallboxInfo(); return await getWallboxSettings(); } const response = await axios.get( "https://" + authData.regionFloatIp + `:32800/rest/neteco/web/config/device/v1/children-list?conditionParams.curPage=0&conditionParams.mocTypes=60081&conditionParams.parentDn=${encodeURIComponent(parentDn)}&conditionParams.recordperpage=1`, { headers: { 'roaRand': authData.roaRand, 'Cookie': 'locale=de-de;bspsession=' + authData.accessToken + ';dp-session=' + authData.accessToken + '; Secure; HttpOnly' }, httpsAgent: CONFIG.httpsAgent } ); log(`Wallbox-Einstellungen für ${parentDn}: ` + JSON.stringify(response.data), 'info'); const wallboxSettings = response.data.data[0]; // Speichern der dnId const dnId = wallboxSettings.dnId; //setState('huawei-smart-charger.WallboxSettings.dnId', dnId, true); // Erstelle die Struktur für paramValues und andere Daten const basePath = `huawei-smart-charger.WallboxSettings.${dnId}`; // Speichere alle Schlüssel von wallboxSettings in IoBroker, einschließlich paramValues for (const key in wallboxSettings) { if (key === 'paramValues') { // Speziell für paramValues const paramValuesPath = `${basePath}.paramValues`; for (const paramKey in wallboxSettings.paramValues) { const paramValue = wallboxSettings.paramValues[paramKey]; await setObject(`${paramValuesPath}.${paramKey}`, { type: 'state', common: { name: `Parameter ${paramKey}`, type: typeof paramValue, role: 'info', read: true, write: WRITABLE_IDS.includes(parseInt(paramKey)) // Schreibbare IDs berücksichtigen }, native: { id: paramKey, value: paramValue } }); await setState(`${paramValuesPath}.${paramKey}`, paramValue, true); } } else { // Speichere alle anderen Daten in IoBroker await setObject(`${basePath}.${key}`, { type: 'state', common: { name: key, type: typeof wallboxSettings[key], role: 'info', read: true, write: false }, native: { value: wallboxSettings[key] } }); await setState(`${basePath}.${key}`, wallboxSettings[key], true); } } // Nur beim ersten Mal die Wallbox-Konfigurationsinformationen abrufen if (!getWallboxConfigInfo.calledWallboxSettings) { await getWallboxConfigInfo(dnId, wallboxSettings.paramValues); getWallboxConfigInfo.calledWallboxSettings = true; } } catch (error) { log(`Fehler beim Abrufen der Wallbox-Einstellungen für ${parentDn}: ${error.message}`, 'info'); } } // Wallbox-Konfigurationsinformationen abrufen und in den entsprechenden Pfaden anlegen async function getWallboxConfigInfo(dnId, paramValues) { if (!paramValues) { log(`Keine Parameterwerte für dnId ${dnId} gefunden.`, 'info'); return; } for (const id of Object.keys(paramValues)) { try { const conditions = [{ queryAll: 0, signals: [id], dnId }]; const response = await axios.post( "https://" + authData.regionFloatIp + ":32800/rest/neteco/web/homemgr/v1/device/get-config-info", JSON.stringify({ conditions }), { headers: { 'roaRand': authData.roaRand, 'Cookie': `locale=de-de;bspsession=${authData.accessToken};dp-session=${authData.accessToken}; Secure; HttpOnly`, "Content-Type": "application/json" }, httpsAgent: CONFIG.httpsAgent } ); log(`Wallbox-Konfigurationsinformationen für ${dnId}, Signal ${id}: ` + JSON.stringify(response.data), 'debug'); const wallboxConfigInfo = response.data; if (wallboxConfigInfo && wallboxConfigInfo[dnId] && wallboxConfigInfo[dnId][0]) { const { name, realValue, value, enumMap, unit } = wallboxConfigInfo[dnId][0]; if (!name) continue; // Sanitize the object ID, but not the display name const sanitizedKey = name .replace(/ä/g, 'ae') .replace(/ö/g, 'oe') .replace(/ü/g, 'ue') .replace(/Ä/g, 'Ae') .replace(/Ö/g, 'Oe') .replace(/Ü/g, 'Ue') .replace(/ß/g, 'ss') .replace(/\s+/g, '_') .replace(/[^\w\d_]/g, ''); // Speichere in huawei-smart-charger.Wallbox.Values const key = sanitizedKey; const stateValue = enumMap && Object.keys(enumMap).length > 0 ? value : realValue; const valuePath = `huawei-smart-charger.Wallbox.Values.${key}`; const settingsPath = `huawei-smart-charger.Wallbox.Settings.${key}`; // Objekt in Values erstellen await setObject(valuePath, { type: 'state', common: { name: name, // Keep the original name here type: typeof stateValue, role: 'info', read: true, write: WRITABLE_IDS.includes(parseInt(id)), unit: unit || undefined, states: Object.keys(enumMap).length > 0 ? enumMap : undefined, dnId: dnId // Speichern der dnId in common }, native: wallboxConfigInfo[dnId][0] }); await setState(valuePath, stateValue, true); // Wenn das Objekt schreibbar ist, auch in Settings erstellen if (WRITABLE_IDS.includes(parseInt(id))) { await setObject(settingsPath, { type: 'state', common: { name: name, // Keep the original name here type: typeof stateValue, role: 'info', read: true, write: true, unit: unit || undefined, states: Object.keys(enumMap).length > 0 ? enumMap : undefined, dnId: dnId // Speichern der dnId in common }, native: wallboxConfigInfo[dnId][0] }); await setState(settingsPath, stateValue, true); } } } catch (error) { log(`Fehler beim Abrufen der Wallbox-Konfigurationsinformationen für ${dnId}, Signal ${id}: ${error.message}`, 'error'); } } } async function createAndSetObjects(basePath, data) { if (!data) { log(`Daten für ${basePath} sind null oder undefined.`, 'error'); return; } for (const key in data) { if (data.hasOwnProperty(key)) { const value = data[key]; // Ersetze Leerzeichen im Objektnamen durch '_' const sanitizedKey = key.replace(/\s+/g, '_'); const id = `${basePath}.${sanitizedKey}`; const objType = typeof value === 'object' && value !== null ? 'channel' : 'state'; log(`Versuche, Objekt ${id} zu erstellen. Typ: ${objType}, Wert: ${JSON.stringify(value)}`, 'info'); await setObject(id, { type: objType, common: { name: sanitizedKey, type: typeof value, role: objType === 'channel' ? 'folder' : 'state', read: true, write: WRITABLE_IDS.includes(parseInt(key)) }, native: typeof value === 'object' && value !== null ? value : { value: value } }); if (objType === 'state' && value !== null && value !== undefined) { await setState(id, value, true); } else if (objType === 'channel' && value !== null) { // Rekursiv verschachtelte Objekte verarbeiten await createAndSetObjects(id, value); } } } } // Aktualisierung des Verbindungsstatus function updateConnectionState(isConnected) { try { setState('huawei-smart-charger.info.connection', isConnected, true); } catch (error) { log(`Fehler beim Aktualisieren des Verbindungsstatus: ${error.message}`, 'error'); } } // Hauptlogik zur Authentifizierung und Datenabfrage async function main() { await authenticateAndScheduleRenewal(); if (!authData) return; await getUserDetailInfo(); await getServerInfo(); await getStationList(); await getWallboxInfo(); await getWallboxSettings(); await getChargeStatus(); } // Lausche auf Änderungen der Settings und sende PUT-Anfrage an die API on({ id: new RegExp('^huawei-smart-charger\\.Wallbox\\.Settings\\.[^.]+$'), change: 'ne' }, async (obj) => { if (obj.state.ack) { return; // Wenn die Änderung von der API bestätigt wurde, ignoriere sie } const writableId = obj.native?.id; if (!writableId || !WRITABLE_IDS.includes(writableId)) { log(`Keine beschreibbare ID für ${obj.id} gefunden.`, 'warn'); return; } try { // Lese die dnId aus dem Objekt in common const dnId = getObject(obj.id).common.dnId; if (!authData || (authData && new Date().getTime() > authData.expiryTime)) { await authenticateAndScheduleRenewal(); } if (authData && dnId) { const requestData = { changeValues: [{ id: writableId, value: obj.state.val }], dnId: dnId }; log(`Sende Anfrage mit Daten: ${JSON.stringify(requestData)}`, 'debug'); const response = await axios.post( "https://" + authData.regionFloatIp + ":32800/rest/neteco/web/homemgr/v1/device/set-config-info", JSON.stringify(requestData), { headers: { 'roaRand': authData.roaRand, 'Cookie': `locale=de-de;bspsession=${authData.accessToken};dp-session=${authData.accessToken}; Secure; HttpOnly`, 'Content-Type': 'application/json', 'x-timezone-offset': '120', 'client-info': '_manufacturer=iPhone;_model=iPhone;_os_ver=18.0.1;_os=iOS;_app_ver=24.6.102006;_package_name=com.huawei.smartpvms;appClientId=86366133-B8B5-41FA-8EB9-E5A64229E3D5', 'x-requested-with': 'XMLHttpRequest', 'User-Agent': 'iCleanPower/24.6.102006 (iPhone; iOS 18.0.1; Scale/3.00)', 'Accept-Language': 'de-DE;q=1, en-DE;q=0.9, zh-Hans-DE;q=0.8', 'Accept': '*/*' }, httpsAgent: CONFIG.httpsAgent } ); log(`Antwort vom Server: ${JSON.stringify(response.data)}`, 'debug'); if (response.data) { await setState(obj.id, obj.state.val, true); } await getWallboxInfo(); await getWallboxSettings(); } } catch (error) { log(`Fehler beim Ändern des Werts für ${obj.id}: ${error.message}`, 'error'); if (error.response) { log(`Fehlerantwort vom Server: ${JSON.stringify(error.response.data)}`, 'error'); } } }); // Monitor changes in WallboxSettings and WallboxInfo under paramValues on({ id: new RegExp('^huawei-smart-charger\\.Wallbox(Settings|Info)\\.[^.]+\\.paramValues\\.[^.]+$'), change: 'ne' }, async (obj) => { if (obj.state.ack) { return; // If the change was confirmed by the API, ignore it } try { // Extract the parameter ID from the changed paramValues instance const paramId = parseInt(obj.id.split('.').pop()); // Extract the parameter ID from the object path // Define a helper function to update corresponding objects const updateObjects = async (basePath) => { getObjectView('system', 'state', { startkey: basePath, endkey: basePath + "\u9999" }, async (err, allObjects) => { if (err) { log(`Error fetching objects from ${basePath}: ${err.message}`, 'error'); return; } for (const obj of allObjects.rows) { const nativeId = obj.value.native?.id; if (nativeId === paramId) { // Update the state in the corresponding object const newValue = obj.state.val; await setState(obj.id, newValue, true); log(`Updated value in ${obj.id} to: ${newValue}`, 'info'); } } }); }; // Update the corresponding objects in Wallbox.Values and Wallbox.Settings await updateObjects('huawei-smart-charger.Wallbox.Values.'); await updateObjects('huawei-smart-charger.Wallbox.Settings.'); } catch (error) { log(`Error while updating Wallbox.Values or Wallbox.Settings from paramValues change: ${error.message}`, 'error'); } }); // Ladestatus abfragen async function getChargeStatus() { try { // Fetch the dnId from the IoBroker state const dnId = getState('huawei-smart-charger.WallboxInfo.dnId').val; if (!dnId) { log('dnId not found for getChargeStatus', 'warn'); return; } // Construct the request payload based on the provided curl example const requestData = { gunNumber: 1, // Fixed value as per the example needRealTimeStatus: false, // This will be toggled based on the interval logic dnId: dnId }; // Perform the POST request const response = await axios.post( "https://" + authData.regionFloatIp + ":32800/rest/neteco/web/homemgr/v1/charger/status/charge-status", JSON.stringify(requestData), { headers: { 'roaRand': authData.roaRand, 'Cookie': `locale=de-de;bspsession=${authData.accessToken};dp-session=${authData.accessToken}; Secure; HttpOnly`, 'Content-Type': 'application/json', 'x-timezone-offset': '120', 'client-info': '_manufacturer=iPhone;_model=iPhone;_os_ver=18.0.1;_os=iOS;_app_ver=24.6.102006;_package_name=com.huawei.smartpvms;appClientId=86366133-B8B5-41FA-8EB9-E5A64229E3D5', 'x-requested-with': 'XMLHttpRequest', 'User-Agent': 'iCleanPower/24.6.102006 (iPhone; iOS 18.0.1; Scale/3.00)', 'Accept-Language': 'de-DE;q=1, en-DE;q=0.9, zh-Hans-DE;q=0.8', 'Accept': '*/*' }, httpsAgent: CONFIG.httpsAgent } ); // Log the charge status response for testing log(`Charge Status response: ${JSON.stringify(response.data)}`, 'info'); // Update interval based on the charge status if (response.data && response.data.chargeStatus !== undefined) { const status = response.data.chargeStatus; log(`Current charge status: ${status}`, 'info'); scheduleIntervals(status); // Update intervals based on the charge status } else { log(`Invalid response from getChargeStatus: ${JSON.stringify(response.data)}`, 'warn'); } } catch (error) { log(`Fehler beim Abrufen des Ladestatus: ${error.message}`, 'error'); if (error.response) { log(`Fehlerantwort vom Server: ${JSON.stringify(error.response.data)}`, 'error'); } } } // Schedule intervals based on the charge status function scheduleIntervals(chargeStatus) { let intervalTime = 300000; // Default interval: 5 minutes let needRealTimeStatus = false; // Define specific interval times based on the charge status if ([3, 4, 7, 8].includes(chargeStatus)) { intervalTime = 60000; // 30 seconds for these statuses needRealTimeStatus = true; // Enable real-time status when charging or in related states } else if ([1, 2, 6, 10, 11].includes(chargeStatus)) { intervalTime = 180000; // 3 minutes for these statuses } // Clear any existing intervals clearInterval(infoInterval); clearInterval(settingsInterval); // Schedule intervals with the appropriate time infoInterval = setInterval(async () => { await getWallboxInfo(needRealTimeStatus); // Fetch wallbox info at the set interval }, intervalTime); settingsInterval = setInterval(async () => { await getWallboxSettings(needRealTimeStatus); // Fetch wallbox settings at the set interval }, intervalTime); log(`Scheduled intervals: ${intervalTime / 1000} seconds`, 'info'); } // Initial call to getChargeStatus and start checking intervals let infoInterval, settingsInterval; async function checkChargeInterval() { await getChargeStatus(); // Check the charge status initially and set intervals setInterval(async () => { await getChargeStatus(); // Re-check the charge status at regular intervals }, CONFIG.interval); } main();
-
@stolly82 vielen Dank, ich warte bis du den Adapter soweit hast. Möchte ungerne auf meinem Produktionssystem testen.
Finde ich gut das du die Daten über die App/Cloud holst.Herzliche grüße
Fabio -
@stolly82 Danke! Ich werde mir den Code anschauen und testen
-
@stolly82 Ich habe es kurz getestet. Beim ersten Start werden viele Warnungen in der Art geworfen:
11.11.2024, 14:26:50.504 [warn ]: javascript.0 (19439) at getWallboxSettings (script.js.common.Wallbox:348:23) 11.11.2024, 14:26:50.504 [warn ]: javascript.0 (19439) at getWallboxSettings (script.js.common.Wallbox:291:20) 11.11.2024, 14:26:50.504 [warn ]: javascript.0 (19439) at main (script.js.common.Wallbox:510:5)
Aber die Daten kommen zu Beginn gut an. Die Logik der Objektstruktur habe ich aber noch nicht durchschaut. Auch habe ich es noch nicht geschafft die Wallbox-Einstellungen zu verändern. Super wäre es z.B. wenn ich den Arbeitsmodus (huawei-smart-charger.Wallbox.Settings.Arbeitsmodus) verändern könnte. Das setzen des "Manuellen Modus" scheint zu funktionieren. Das Setzen von "PV Überschuss" aber nicht.
Auch scheint es da noch ein Problem bei der regelmässigen Aktualisierung zu geben. Wenn ich in der FusionSolar-App von "PV Überschuss" nach "Manuellen Modus" wechsle, wird das irgendwie nicht vom Script in den Objekten aktualisiert. Dies auch nicht nach einem Sync ( 5min Scheduler).
Das gleiche Verhalten auch bei huawei-smart-charger.Wallbox.Settings.Wechsel_zwischen_ein_und_dreiphasig. Der lässt sich über das Script bei mir nicht setzen.
Welche Werte können in den Objekten verändert werden bzw. werden zurück auf die Wallbox geschrieben? Einfach jene unter huawei-smart-charger.Wallbox.Settings?
-
@baerengraben sagte in Huawei Wallbox - S Charger (7kw/h / 22kw/h) Adapter test:
@stolly82 Ich habe es kurz getestet. Beim ersten Start werden viele Warnungen in der Art geworfen:
11.11.2024, 14:26:50.504 [warn ]: javascript.0 (19439) at getWallboxSettings (script.js.common.Wallbox:348:23) 11.11.2024, 14:26:50.504 [warn ]: javascript.0 (19439) at getWallboxSettings (script.js.common.Wallbox:291:20) 11.11.2024, 14:26:50.504 [warn ]: javascript.0 (19439) at main (script.js.common.Wallbox:510:5)
Aber die Daten kommen zu Beginn gut an. Die Logik der Objektstruktur habe ich aber noch nicht durchschaut. Auch habe ich es noch nicht geschafft die Wallbox-Einstellungen zu verändern. Super wäre es z.B. wenn ich den Arbeitsmodus (huawei-smart-charger.Wallbox.Settings.Arbeitsmodus) verändern könnte. Das setzen des "Manuellen Modus" scheint zu funktionieren. Das Setzen von "PV Überschuss" aber nicht.
Auch scheint es da noch ein Problem bei der regelmässigen Aktualisierung zu geben. Wenn ich in der FusionSolar-App von "PV Überschuss" nach "Manuellen Modus" wechsle, wird das irgendwie nicht vom Script in den Objekten aktualisiert. Dies auch nicht nach einem Sync ( 5min Scheduler).
Das gleiche Verhalten auch bei huawei-smart-charger.Wallbox.Settings.Wechsel_zwischen_ein_und_dreiphasig. Der lässt sich über das Script bei mir nicht setzen.
Welche Werte können in den Objekten verändert werden bzw. werden zurück auf die Wallbox geschrieben? Einfach jene unter huawei-smart-charger.Wallbox.Settings?
hab auch kurz getestet, dachte es geht nicht da mein log übergegangen ist aber die Werte sind da
-
Die Fehlermeldung zum Start mache ich noch weg.
Also das Loggen der API Calls aus der App war nicht gerade das einfachste, bis ich die Calls raus hatte die da was machen und wann diese von der APP abgerufen und gesetzt werden,... und was da aus welchen Calls rein muss.
Es gibt in der App mehre API Calls, die nur beim öffnen der Wallbox aufgerufen werden (einmalig) und Live calls, die nur nach dem verstellen von Werten einmalig abgerufen werden.
Die Wallbox ist in 2 unterschiedlichen API Calls vorhanden und wird in den Wallbox.Settings und Wallbox.Values zusammengelegt (Die anderen WallboxInfo & WallboxSettings sind die Responses aus den APIs, so wie sie raus kommen).
WallboxInfo & WallboxSettings sind auch unbenannt in der API, werden also nur als ID ausgegeben, die man dann alle einzeln über eine API abfragen muss, wie diese heissen.
Die IDs die beschreibbar sind, habe ich durch das API logging recherchiert, ich glaube aber da habe ich irgendwas falsch aufgeschrieben, denn ich habe mit Garantie die IP Adresse in der App nicht geändert.
Vielleicht mal die IDs ausprobieren die euch noch fehlen, die dann einfach in die WRITABLE_IDS eintragen, theoretisch sollten die Datenpunkte dann in Wallbox.Settings erscheinen.
Nur in denen Werte ändert, darauf lauscht der listener.Die IDs findet Ihr dann in den Wallbox > Values > Objektdaten
-
@stolly82 Merci für die Infos Aber verstehe ich das richtig, dass du das Script nicht basierend auf einer API Beschreibung von Huawei sondern basierend auf einer Analyse der App Kommunikation zum Backend entwickelt hast? Also quasi "reverse engineered"?
-
,...nicht basierend auf einer API Beschreibung von Huawei
Das ist komplettes Reverse Engineering, Offiziell gibt es überhaupt nichts von Huawei und die werden es auch nicht einbinden.
Die wollen OCPP machen, release irgendwie Mitte 2023,...
Ich bin da ziemlich im Thema mit den HomeAssistant Junx, da ich denne geholfen habe Modbus und jetzt die App API mit einzubinden.
-
@stolly82 Alles Klar Wie möchtest du die Test-Feedbacks erhalten? Am besten als Issue in github?
-
@stolly82 OCPP meinst Mitte 2025 oder? Machst Du auch eine Adapter draus oder bleibts beim Script? Ich nehme an Du hast App API gemacht weil ja Modbus leider nicht geht wenn man schon den Virtuellen Zähler von Huawei mit der Wallbox hat, oder? Bei mir geht nämlich deswegen Modbus nicht....
Weiß ev. jemand ob Huawei jemals mehr als einen gleichzeitigen Modbus Client erlauben wird? -
@mhuber leider meinte ich nicht Mitte 2025, sondern Mitte 2023! Das war schon korrekt
Es sollte schon seit diversen FW Versionen drin sein, im HomeAssistant forum hatten Leute vor vor mehren Monaten eine Antwort von Huawei gepostet, dass die OCPP in der nächsten Firmware enthalten ist.
Danach gab es bereits 4 neue Versionen.
Die nerven die da bei Huawei auch richtig, aber das scheint denen wirklich egal zu sein.
Ich mache da dann einen Adapter aus dem Script (ist nur 10 min Arbeit das eben umzuschreiben).
Die Adapter sind super nervig beim Entwickeln, da schleichen sich bei mir immer Fehler ein die dann bei der Prüfung extrem aufwendig sind raus zu bekommen.
Das liegt irgendwie am Adapter Creator tool, daher schreibe ich sowas aktuell immer als javascript vor und wenn es fertig ist schiebe ich das in den Adapter rein.Sind in der Regel nur Konfigurationen und der Objekt-Pfad "Adaptername.(instanz)." statt "Adaptername" die ich dann ändern muss.
@baerengraben
GIT macht sinn, kannst aber auch hier machen.Wichtig sind dinge wie unter welchen bedingungen ggf. noch eine Abfrage schneller rein muss (Beispiel man ändert was, dann würde ja die Änderung gepusht zur WB aber der Adapter fragt erst im nächsten Zyklus wieder ab, ggf. will man das schneller im Adapter sehen, dass man nach einer Änderung den Zyklus minimiert oder so).
Oder wenn was nicht funktioniert.
Viel ist aus der API eh nicht raus zu holen, die dinge die was mit der WB zu tun haben, habe ich alle drin.
-
@stolly82 sagte in Huawei Wallbox - S Charger (7kw/h / 22kw/h) Adapter test:
@mhuber leider meinte ich nicht Mitte 2025, sondern Mitte 2023! Das war schon korrekt
Es sollte schon seit diversen FW Versionen drin sein, im HomeAssistant forum hatten Leute vor vor mehren Monaten eine Antwort von Huawei gepostet, dass die OCPP in der nächsten Firmware enthalten ist.
Danach gab es bereits 4 neue Versionen.
Die nerven die da bei Huawei auch richtig, aber das scheint denen wirklich egal zu sein.
Ich mache da dann einen Adapter aus dem Script (ist nur 10 min Arbeit das eben umzuschreiben).
Die Adapter sind super nervig beim Entwickeln, da schleichen sich bei mir immer Fehler ein die dann bei der Prüfung extrem aufwendig sind raus zu bekommen.
Das liegt irgendwie am Adapter Creator tool, daher schreibe ich sowas aktuell immer als javascript vor und wenn es fertig ist schiebe ich das in den Adapter rein.Sind in der Regel nur Konfigurationen und der Objekt-Pfad "Adaptername.(instanz)." statt "Adaptername" die ich dann ändern muss.
@baerengraben
GIT macht sinn, kannst aber auch hier machen.Wichtig sind dinge wie unter welchen bedingungen ggf. noch eine Abfrage schneller rein muss (Beispiel man ändert was, dann würde ja die Änderung gepusht zur WB aber der Adapter fragt erst im nächsten Zyklus wieder ab, ggf. will man das schneller im Adapter sehen, dass man nach einer Änderung den Zyklus minimiert oder so).
Oder wenn was nicht funktioniert.
Viel ist aus der API eh nicht raus zu holen, die dinge die was mit der WB zu tun haben, habe ich alle drin.
Super danke für die Infos. Aber für die Modbus Einbindung über einen Adapter gibts keinen Workaround oder? Wegen dem einen Client wenn man den Virtuellen Zähler nutzt oder?