Skip to content

[Bug/Feature Request] :Marstek battery stalls at 0W after firmware update; HBC fails to re-trigger setpoint (and solution that works for me) #155

Description

@MikedaSpike

Since upgrading my Marstek Venus firmware from V3 v146 to v148, I am experiencing intermittent battery stalls during the "Sell" strategy.

When the battery is operating at high discharge power, it occasionally enters standby mode (likely due to internal hardware/BMS protection triggered by the firmware update). When this happens, the physical discharge power drops to 0W.
When this happens, the physical discharge power drops to 0W and the Home Assistant number.marstek_mX_forcible_discharge_power entity correctly updates to 0W.

HBC fails to recover from this state. Even though the sensor is 0W, the HBC flow does not trigger a new update to force the battery back to its intended setpoint. The system only recovers if the strategy switches to "Self-consumption", where values are updated frequently enough to force a re-sync.

I am running this on a dual-battery setup (2x Marstek Venus).

Reproduction Steps:

  • Run "Sell" strategy with high discharge power.
  • for me the battery enters internal standby/reboot due to firmware/hardware constraints.
    • Physical discharge power and the HA number.marstek_mX_forcible_discharge_power entity drop to 0W.
  • To reproduce it setting number.marstek_mX_forcible_discharge_power to 0w
  • HBC logic fails to re-issue the setpoint because the flow does not detect the need for a re-transmission, leaving the battery stuck at 0W.

The issue occurs because the Set Batteries logic and the subsequent on change (RBE) node do not detect a discrepancy between the HA state and the intended setpoint after a hardware stall.

I have implemented a fix/workaround in the Set Batteries function node (01 start-flow.json) that:

  • Performs a proactive check comparing the current Home Assistant state against the calculated target setpoint.
  • Detects if a battery has physically stalled (0W) while a target setpoint is active.
  • If a mismatch is detected, it triggers a forced update by appending a _force_update_timestamp to the payload, bypassing the on change (RBE) filter to ensure the command is sent to Home Assistant.

Code snippet for Set Batteries node (replace from //Set power to end).

// Set | Power
const serviceCallPower = {
    "action": "number.set_value",
    "target": {
        "entity_id": [
            `number.${target}_forcible_charge_power`,
            `number.${target}_forcible_discharge_power`
            ]
    },
    "data": {
        "value": Number(solution.power)
    } 
};

let needsUpdate = false;
let targetVal = Number(solution.power);
let logReason = "";

// 1. Verify HA dashboard values
const haContext = global.get('homeassistant');
if (haContext && haContext.homeAssistant && haContext.homeAssistant.states) {
    const states = haContext.homeAssistant.states;
    const currentCharge = states[`number.${target}_forcible_charge_power`];
    const currentDischarge = states[`number.${target}_forcible_discharge_power`];

    if (currentCharge && currentDischarge) {
        let haChargeVal = Number(currentCharge.state);
        let haDischargeVal = Number(currentDischarge.state);

        if (haChargeVal !== targetVal || haDischargeVal !== targetVal) {
            needsUpdate = true;
            logReason = `HA values incorrect (${haChargeVal}W/${haDischargeVal}W). Forcing update to ${targetVal}W.`;
        }
    }
} else {
    // Fallback if global context is unavailable
    needsUpdate = true;
}

// 2. Verify physical battery stall (after reboot/standby)
let livePower = Math.abs(parseInt(battery.power, 10));
if (targetVal > 0 && livePower === 0) {
    needsUpdate = true;
    logReason = `Battery stall detected (Physical: 0W). Forcing wake-up with ${targetVal}W.`;
}

// Route payload to Output 2 (or bypass if correct)
if (needsUpdate) {
    if (logReason !== "") log(this, logReason, "warn");
    
    // Add timestamp to bypass 'on change' RBE filter
    serviceCallPower._force_update_timestamp = Date.now();
    outputArray.push({payload: serviceCallPower, topic: solution.id});
} else {
    outputArray.push(null);
}

// Store service calls in msg to allow checking of loop results
msg.loop_result = { target, serviceCallMode, serviceCallPower };

// Return msg as third output
outputArray.push(msg);

// Explain
log(this, `${target} ${String(solution.mode)} @ ${Number(solution.power)} Watt`);

// Output
return outputArray;

this will give a small warning in the HBC insight log
Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions