Zendure App : please repair : Hyper2000 charge level + status

Hello,

End of 2025 I was perfectly using my Flow with Zendure App, saying that : When My hyper2000 charge level is 90%, then start charging my car, until Hyper2000 charge level is 10%, then stop charging.
Received even a notification on my smartphone, when charge was starting. Perfect.

But this winter, not enough sun, so the Flow was never activated.
Sun is back, but flow isn’t working anymore : The Zendure app has a bug, Hyper2000 stays at 0% charge Level, and charging Status stays “Inactive”, even when the Hyper2000 is charging…

I tried almost everything… removed the Hyper 2000 from Homey, remove/reinstall the Zendure App, change My Zendure Region from Global to France, totally remove and reinstall the Hyper2000 on my smartphone, etc…

In Homey it always stays at 0%.

I tried in MQTT Explorer, I correctly get the “ElectricLevel”.
So it seems definitively Zendure App bug.

Does anyone has it working ?

1 Like

The “official” Homey Zendure app was last updated in July 2025:

So no changes have been made to the app in the meantime.

Perhaps something has changed in the communication with the Zendure Cloud on the side of Zendure?
My recommendation is to contact Zendure support if no other user comes up with a solution. You can find the contact details in the Homey App Store.

1 Like

Same problem here since 10 feb.
No data from the Hypers, SF800Pro works fine.

same here

Zendure replied I need to disconnect, reconnect and re-add the battery into Homey.

Have you tried this?

Thank you both for this confirmation.

Yes @sebmar , I did it, and everything possible.

Here’s a quick list of what I did :

  • desactivate then reactivate Zendure app in Homey,
  • deinstall / reinstall of the Zendure app in Homey,
  • Remove / re add Hyper2000 multiple times in Homey,
  • multiple reboot of my Homey
    And in Zendure app on my iphone :
  • remove / add Hyper2000,
  • Change of location from Global to France,
  • then re-add my full system : hyper2000, SolarFlow800, and CT Monitor

Regarding the MQTT, I switched to the EU one, and it’s working with MQTT Explorer.

I’ve send a full Summarize of all my actions to the support, waiting for their answer.

1 Like

Yes, it’s one of the first things I tried already in february.
No result.


Hello. Same problem, and I’ve tried several methods, but the results are the same.

After contacting Zendure, here is their response: it seems to be due to the API.

Thank you for your patience.

Regarding the issue you reported where the battery level information cannot be retrieved in Homey Pro and appears as “not activated,” we have submitted the case to our technical team for further investigation. According to the current check results, the system can correctly recognize both AB batteries, and the connection between the devices is functioning normally. Therefore, no abnormalities have been found in the device’s own communication or recognition.

As you mentioned that some control commands (such as setting the AC output) can be executed normally through the Zendure 1.05 app in Homey Pro, but an issue occurs when reading the battery data, this situation is more likely related to data retrieval or API compatibility on the Homey platform application side. To further confirm the situation, we recommend that you also contact Homey’s customer support team so that they can check the data reading behavior of this app on the Homey Pro platform.

In addition, if you receive any feedback from Homey later or if you need our assistance to further investigate the issue, please feel free to contact us by email. We will do our best to cooperate and help move the troubleshooting process forward.

If you have any other questions, please do not hesitate to contact us. We will be happy to assist you.

Kind regards,

Zendure Support Team

Same here.

Now there is a test version from 10.03.26. But the hyper ist still not working. I found my hyper, but no updated data.

Same hare since Feb 10.

Tried everything, even switching to Homey SHS did not change anything.

This must be a change on the zendure API side because the homey app is not updated.

Very annoying for a vendor build app.

2 Likes

The zendure Support gave me the advice to copy the Cloud Key and use this in the zendure App for Homey. But there ist no Option for this.

Hello,

After a lot of testing, debugging, and a few mail to Zendure Support, they just sent me this today :
———
Hello,

Thank you for providing such detailed information and for your efforts in troubleshooting.

The good news is that our development team is targeting a fix for the next Homey app update, expected around June. Once this update is released, it should restore proper SOC and status reporting for your Hyper2000 and SolarFlow800 devices.

In the meantime, you can continue to rely on the official Zendure mobile app for accurate SOC and device status.

Best regards,

Zendure Support Team

———–
Hoping this will be the good one :slight_smile:

1 Like

June? Realy? Sounds that they have bigger problems.

I received a message from Zendure confirming the June update of their application.

I use MQTT to read out all the values I can get and send the values via the flows to charge and discharge my 3 Zendure Hyper 2000’s (and 1 Ace 1500).

Here is the script I’m using:

/**
 * TITAN_MQTT_PARSER.JS V3.44 - BYPASS DISPLAY FIX
 * - FIX: Überschreibt Werte NIE wieder mit 0, wenn sie im JSON fehlen (Stateful).
 * - NEW: Bat_Net (Netto-Batterieleistung) berechnet für virtuelle Geräte.
 * - PURE: Stabiles Dashboard mit echten, validierten Werten.
 * - DISPLAY: Erzwingt "PV-Bypass" bei SoC >= 99% und aktiver Sonne.
 */

const TITAN = {
    DEVICES: {
        HYPER_1: { displayName: "HY1", varPrefix: "Hyper_1", sn: "EE1HYMCFM200629" },
        HYPER_2: { displayName: "HY2", varPrefix: "Hyper_2", sn: "EE1LYMGJM320918" },
        HYPER_3: { displayName: "HY3", varPrefix: "Hyper_3", sn: "EE1LHMGHM312110" },
        ACE1500: { displayName: "ACE", varPrefix: "ACE1500", sn: "FE1HTMG4M350376" }
    }
};

async function updateVariable(name, value) {
    const vars = await Homey.logic.getVariables();
    const cleanName = name.replace(/[-_ ]/g, '').toLowerCase();
    const variable = Object.values(vars).find(v => v.name.replace(/[-_ ]/g, '').toLowerCase() === cleanName);
    
    if (variable && variable.value !== value) {
        await Homey.logic.updateVariable({ id: variable.id, variable: { value } });
    }
}

function getViennaTimeString(date = new Date()) {
    return new Intl.DateTimeFormat('de-AT', {
        timeZone: 'Europe/Vienna',
        year: 'numeric', month: '2-digit', day: '2-digit',
        hour: '2-digit', minute: '2-digit', second: '2-digit',
        hour12: false
    }).format(date).replace(',', ' -');
}

const rawPayload = args[0];
const isManualTest = (!rawPayload || typeof rawPayload !== 'string');

// ==========================================
// 🌟 DIAGNOSE-DASHBOARD (Manueller Test)
// ==========================================
if (isManualTest) {
    console.log(" ");
    console.log("=".repeat(120));
    console.log(` 🛠️ TITAN DIAGNOSE DASHBOARD | Alle Homey-Werte | ${getViennaTimeString()} `);
    console.log("=".repeat(120));
    
    const currentVars = await Homey.logic.getVariables();
    const getVal = (prefix, suffix) => {
        const v = Object.values(currentVars).find(varObj => varObj.name === `${prefix}_${suffix}`);
        return (v && v.value !== null) ? v.value : "---";
    };
    
    const allDevices = [TITAN.DEVICES.HYPER_1, TITAN.DEVICES.HYPER_2, TITAN.DEVICES.HYPER_3, TITAN.DEVICES.ACE1500];
    const metrics = [
        { key: "State", icon: "📊", name: "Status         ", unit: "" },
        { key: "SoC", icon: "🔋", name: "SoC            ", unit: "%" },
        { key: "Temp", icon: "🌡️", name: "Temp           ", unit: "°C" },
        { key: "Bat_Net", icon: "⚖️", name: "Bat_Net        ", unit: "W" },
        { key: "Bat_In", icon: "⬇️", name: "Bat_In         ", unit: "W" },
        { key: "Bat_Out", icon: "⬆️", name: "Bat_Out        ", unit: "W" },
        { key: "AC_Out", icon: "🏠", name: "AC_Out         ", unit: "W" },
        { key: "Grid_In", icon: "🔌", name: "Grid_In        ", unit: "W" },
        { key: "Power_PV", icon: "☀️", name: "PV_Power       ", unit: "W" },
        { key: "Time_Left", icon: "⏱️", name: "Time_Left      ", unit: "" },
        { key: "AC_Mode", icon: "⚙️", name: "AC_Mode        ", unit: "" }
    ];
    
    let headerRow = " 🎚️ Parameter      |".padEnd(19);
    for (const d of allDevices) {
        headerRow += ` ${d.displayName} (${d.sn.substring(10)}) |`.padStart(25);
    }
    console.log(headerRow);
    console.log("-".repeat(120));
    
    for (const m of metrics) {
        let row = ` ${m.icon} ${m.name}|`;
        for (const d of allDevices) {
            let val = getVal(d.varPrefix, m.key);
            let unitDisp = m.unit;
            
            if (m.key === "Temp" && val !== "---") {
                let tVal = Number(val);
                val = tVal > 1000 ? (tVal / 100).toFixed(1) : (tVal > 100 ? (tVal / 10).toFixed(1) : tVal.toFixed(1));
            }
            
            if (m.key === "Time_Left" && val !== "---") {
                let mins = parseInt(val);
                if (isNaN(mins) || mins === 0 || mins >= 59000) val = ">99h";
                else val = `${Math.floor(mins / 60)}h ${mins % 60}m`;
                unitDisp = ""; 
            }

            if (m.key === "AC_Mode" && val !== "---") {
                const modeNum = parseInt(val);
                if (modeNum === 1) val = "1 (Inverter)";
                else if (modeNum === 2) val = "2 (AC Charge)";
                else val = `Standby (${modeNum})`;
            }

            let displayVal = `${val} ${unitDisp}`.trim();
            row += displayVal.padStart(23) + " |";
        }
        console.log(row);
    }
    console.log("=".repeat(120));
    return true;
}

// ==========================================
// 🚀 REGULÄRER MQTT PARSER (STATEFUL)
// ==========================================
if (rawPayload.includes('"unique_id":') || rawPayload.includes('"device_class":')) return false; 

let payload;
try { payload = JSON.parse(rawPayload); } catch (err) { return false; }

const device = Object.values(TITAN.DEVICES).find(d => payload.sn === d.sn);
if (!device) return false;

const prefix = device.varPrefix;
const timeStr = getViennaTimeString();

// Watchdog Update (gekürzt, um die DB nicht zu fluten)
await updateVariable('MQTT_Last_Raw_JSON', `${timeStr} | ${device.displayName} | Payload empfangen`);

// --- 1. SICHERES AUSLESEN DER WERTE (NUR WENN VORHANDEN) ---

// Bat_In (Laden)
let newBatIn = undefined;
if (payload.outputPackPower !== undefined) {
    newBatIn = Number(payload.outputPackPower);
    await updateVariable(`${prefix}_Bat_In`, newBatIn);
}

// Bat_Out (Entladen)
let newBatOut = undefined;
if (payload.packInputPower !== undefined) {
    newBatOut = Number(payload.packInputPower);
    await updateVariable(`${prefix}_Bat_Out`, newBatOut);
}

// AC_Out (Haus)
if (payload.outputHomePower !== undefined) {
    await updateVariable(`${prefix}_AC_Out`, Number(payload.outputHomePower));
} else if (payload.acOutputPower !== undefined) {
    await updateVariable(`${prefix}_AC_Out`, Number(payload.acOutputPower));
}

// Grid_In (Netzbezug am Hub)
if (payload.gridInputPower !== undefined) {
    await updateVariable(`${prefix}_Grid_In`, Number(payload.gridInputPower));
}

// PV Power (Nur updaten, wenn wirklich PV-Daten im Paket sind!)
let hasPvData = false;
let pvPower = 0;

if (payload.solarInputPower !== undefined) {
    pvPower = Number(payload.solarInputPower);
    hasPvData = true;
} else if (payload.solarPower1 !== undefined || payload.solarPower2 !== undefined) {
    pvPower = Number(payload.solarPower1 || 0) + Number(payload.solarPower2 || 0);
    hasPvData = true;
}

if (hasPvData) {
    await updateVariable(`${prefix}_Power_PV`, pvPower);
}

// SoC
let soc = null;
if (payload.electricLevel !== undefined) {
    soc = Number(payload.electricLevel);
} else if (payload.packData && Array.isArray(payload.packData)) {
    let sum = 0, count = 0;
    payload.packData.forEach(p => { 
        if (p.socLevel != null) { sum += Number(p.socLevel); count++; } 
    });
    if (count > 0) soc = sum / count;
}
if (soc !== null) await updateVariable(`${prefix}_SoC`, Math.round(soc * 10) / 10);

// Temp
if (payload.hyperTmp !== undefined) {
    await updateVariable(`${prefix}_Temp`, Number(payload.hyperTmp));
} else if (payload.packData && Array.isArray(payload.packData)) {
    const packWithTemp = payload.packData.find(p => p.maxTemp !== undefined);
    if (packWithTemp) await updateVariable(`${prefix}_Temp`, Number(packWithTemp.maxTemp));
}

// Time Left
if (payload.remainOutTime != null || payload.remainInputTime != null) {
    let rt = (payload.remainOutTime || payload.remainInputTime || 0);
    await updateVariable(`${prefix}_Time_Left`, rt >= 59000 ? 0 : rt);
}

// AC-Mode
if (payload.acMode !== undefined) {
    await updateVariable(`${prefix}_AC_Mode`, Number(payload.acMode));
}

// ==========================================
// 🧠 LOGIK: LIVE-STATUS BERECHNUNG & BAT_NET
// ==========================================
const currentVars = await Homey.logic.getVariables();

const getValSafe = (suffix) => {
    const v = Object.values(currentVars).find(varObj => varObj.name === `${prefix}_${suffix}`);
    return (v && v.value !== null) ? Number(v.value) : 0;
};

// Wir holen uns die frisch geschriebenen oder die alten Variablen zur Berechnung
let curBatOut = (newBatOut !== undefined) ? newBatOut : getValSafe('Bat_Out');
let curBatIn  = (newBatIn !== undefined) ? newBatIn : getValSafe('Bat_In');
let curPv     = hasPvData ? pvPower : getValSafe('Power_PV');
let curAcOut  = getValSafe('AC_Out');
let curAcMode = getValSafe('AC_Mode');
let curSoc    = (soc !== null) ? soc : getValSafe('SoC');

// --- NEU: Bat_Net (Netto-Leistung) ---
// Positiv = Laden (Bat_In), Negativ = Entladen (Bat_Out)
let curBatNet = curBatIn - curBatOut;
await updateVariable(`${prefix}_Bat_Net`, curBatNet);
// -------------------------------------

let devState = "Standby";

if (curBatOut > 30) {
    devState = (curPv > 20) ? "Entladen (PV+Akku)" : "Entladen (Akku)";
} else if (curSoc >= 99 && curPv > 20) {
    // ERZWUNGENER BYPASS-STATUS ab 99% (Ignoriert Balancing-BatIn)
    devState = "PV-Bypass";
} else if (curAcMode === 2 && curBatIn > 30) {
    devState = "Netzladung";
} else if (curBatIn > 30 && curPv > 20) {
    devState = "PV-Laden";
} else if (curAcOut > 30 && curPv > 20 && curBatIn < 30 && curBatOut < 30) {
    devState = "PV-Bypass"; 
} else if (curPv > 20) {
    devState = "PV-Standby";
} else {
    devState = "Standby";
}

await updateVariable(`${prefix}_State`, devState);

return true;

Find out your serial numbers and credentials, then replace mine with them!
Ask Google how to get the cloud credentials for MQTT login.

The script even generates a nice LOG when you “TEST“ the script:

You’ll need the MQTT Client to connect to the Zendure Cloud like that:

And you need two flows to read from and write to Zendure Hyper 2000:
Read:

Write (is a bit more complicated :wink:)

Sorry, it’s a mix of german and english… just translate it into your language.
And of course you need to setup alle the variables (at least those in the flows)…
Maybe I have forgotten something.

I’ve created a complete BMS based on Homey with Flows/Scripts/MQTT in the last months with Gemini (mostly the PRO version, the others suck!).
I have several more scripts for optimizing charging/discharging based on my other smart home devices like PV, Heatpump and so on, but that would be breaking the mold here.

If you have questions for the Zendure MQTT Integration, I’ll try to help you.
If you want quicker help, ask Gemini Pro! :wink:

Good luck and have fun!

This is a really good guide for controlling the Hyper2000, even for someone who is not quite fit in the topics MQTT and scripts. Thank You!

Unfortunately, the current Zendure app is hardly usable. The Hyper2000 can be controlled. However, you do not get any values from the Hyper2000 (as with the charge level).

1 Like

That’s why I built this MQTT “workaround“ :wink:
But also for other reasons…

Hallo Markus Haselböck leider kommt bei mir nicht wirklich was beim Testen heraus:

========================================================================================================================

 🛠️ TITAN DIAGNOSE DASHBOARD | Alle Homey-Werte | 14.04.2026 - 19:31:49 
========================================================================================================================
 🎚️ Parameter      |            HY3 (15827) |
------------------------------------------------------------------------------------------------------------------------
 📊 Status         |                    --- |
 🔋 SoC            |                  --- % |
 🌡️ Temp           |                 --- °C |
 ⚖️ Bat_Net        |                  --- W |
 ⬇️ Bat_In         |                  --- W |
 ⬆️ Bat_Out        |                  --- W |
 🏠 AC_Out         |                  --- W |
 🔌 Grid_In        |                  --- W |
 ☀️ PV_Power       |                  --- W |
 ⏱️ Time_Left      |                    --- |
 ⚙️ AC_Mode        |                    --- |
========================================================================================================================

@boeselhack leider darf ich nicht alle Bilder in ein Post packen…

und in der übersicht ist der hyper flow nicht aktive: