Skip to content

Zendure: Multiple Updates and DPL feature#2447

Closed
vaterlangen wants to merge 12 commits intohoylabs:developmentfrom
vaterlangen:development
Closed

Zendure: Multiple Updates and DPL feature#2447
vaterlangen wants to merge 12 commits intohoylabs:developmentfrom
vaterlangen:development

Conversation

@vaterlangen
Copy link
Copy Markdown

@vaterlangen vaterlangen commented Mar 31, 2026

This PR adds multiple changes on the Zendure integration and also adds #2176

DPL

Allow to prioritize one or more SmartBufferBattery (SBB) powered Inverters over non-priorized. This is usefull if one uses multiple SBBs with different solar input power. The DPL always try to fulfill the requested output power with priorized SBB-powered inverters before using unpriorized SBB-powered inverters.

Also see #2176

Zendure

Added battery protection mode

This protects single batteries from deep discharge due to BMS only looks at overall SoC and not the SoC reported by every pack. So if BMS of one or more battery packs has an offset, one pack may be discharged below lower threshold.

This solves #2370

Added support for Offical Zendure API

The integration has been split into one part working with local MQTT broker and one connecting to the offical Zendure Servers (as I do not use them, this part receives no in-depth testing)

Show password used by Zendure to connect to MQTT broker

BatteryAdmin now also shows the password used by the Zendure device to connect to the broker

Updated FullCharge

FullCharge now stays on 100% for a configurable time to ensure proper calibrations

@vaterlangen vaterlangen changed the title Zendure: Multiple Updates and Zendure: Multiple Updates and DPL feature Mar 31, 2026
@github-actions
Copy link
Copy Markdown

Build Artifacts

Firmware built from this pull request's code:

Notice

  • These artifacts are ZIP files containing the factory update binary as well as the OTA update binary.
    Extract the binaries from the ZIP files first. Do not use the ZIP files themselves to perform an update.
  • These links point to artifacts of the latest successful build run.
  • The linked artifacts were built from ea61af9.

@AndreasBoehm
Copy link
Copy Markdown
Member

Thanks a lot for your contributions @vaterlangen!

Please split this into multiple smaller PRs. The review of a massive PR takes a lot of time and in case of review comments its a lot harder to get back into it after a couple of days have passed.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e4161128-b51f-4307-bca9-5c477577424f

📥 Commits

Reviewing files that changed from the base of the PR and between 6fa43ef and 3430298.

📒 Files selected for processing (24)
  • include/Configuration.h
  • include/PowerLimiterInverter.h
  • include/battery/zendure/Constants.h
  • include/battery/zendure/LocalMqttProvider.h
  • include/battery/zendure/Provider.h
  • include/battery/zendure/Stats.h
  • include/battery/zendure/ZendureMqttProvider.h
  • include/defaults.h
  • src/Configuration.cpp
  • src/PowerLimiter.cpp
  • src/battery/Controller.cpp
  • src/battery/zendure/HassIntegration.cpp
  • src/battery/zendure/LocalMqttProvider.cpp
  • src/battery/zendure/Provider.cpp
  • src/battery/zendure/Stats.cpp
  • src/battery/zendure/ZendureMqttProvider.cpp
  • webapp/src/locales/de.json
  • webapp/src/locales/en.json
  • webapp/src/locales/fr.json
  • webapp/src/types/BatteryConfig.ts
  • webapp/src/types/PowerLimiterConfig.ts
  • webapp/src/views/BatteryAdminView.vue
  • webapp/src/views/PowerLimiterAdminView.vue
  • webapp/vite.config.ts

Walkthrough

This PR refactors the Zendure battery provider architecture to support multiple connection types (LocalMqtt, ZendureMqtt, Bluetooth), introduces virtual provider methods for subclass customization, expands Stats with optional types and new tracking fields, adds inverter priority support to PowerLimiter for smarter power allocation, and updates configuration/UI for the new connectivity options.

Changes

Cohort / File(s) Summary
Configuration & Constants
include/Configuration.h, include/defaults.h, include/battery/zendure/Constants.h, src/Configuration.cpp
Added Zendure string-length macros, replaced ChargeThroughResetLevel with ChargeThroughKeepMinutes, added connection type selector and server/credential/protection fields to BATTERY_ZENDURE_CONFIG_T. Extended default constants and persistent-settings keys. Updated serialization/deserialization logic for new fields and charge-through timing semantics.
Provider Base Class Refactoring
include/battery/zendure/Provider.h, src/battery/zendure/Provider.cpp
Removed final qualifiers from lifecycle methods init() and deinit() to enable subclass overrides. Introduced protected pure virtual hooks (timesync(), shutdown(), writeSettings(), processPackDataJson(), setBypassMode()). Refactored MQTT handling: removed direct log/report/timesync subscriptions, replaced with persistent-settings parser. Added control-state management (setControlState(), checkBatteryProtection()), optional field defaults, and output-limit rescheduling logic.
Zendure Provider Implementations
include/battery/zendure/LocalMqttProvider.h, src/battery/zendure/LocalMqttProvider.cpp, include/battery/zendure/ZendureMqttProvider.h, src/battery/zendure/ZendureMqttProvider.cpp
Added two new concrete providers: LocalMqttProvider for local MQTT to Zendure devices (411 lines, includes JSON parsing, command helpers, MQTT callbacks) and ZendureMqttProvider for cloud MQTT (364 lines, includes connection management, fragmented message reassembly, credentials validation). Both implement provider virtual methods and handle device-specific message processing.
Stats Data Model
include/battery/zendure/Stats.h, src/battery/zendure/Stats.cpp
Added ControlState enum and state mappings. Extended Stats with optional types for capacity, SoC, limits, power, and timestamps. Changed version/state setters to accept parsed numbers. Added solar input power summation. Refactored bypass-mode string conversion from lookup table to switch statement. Updated UI/MQTT publishing to include new control-state, keep-until, remaining-time, and protection fields.
Power Limiter Priority Support
include/PowerLimiterInverter.h, src/PowerLimiter.cpp
Added HasPriority boolean field to PowerLimiterInverterConfig and public accessor. Updated power allocation logic to split smart-buffer-powered inverters into primary (with priority) and secondary phases, enabling prioritized distribution before remainder allocation.
Battery Integration & Control
src/battery/Controller.cpp, src/battery/zendure/HassIntegration.cpp
Updated provider instantiation to branch on ConnectionType and create either LocalMqttProvider or ZendureMqttProvider. Extended Home Assistant integration to publish separate BMS/control state sensors, keep-until duration, remaining charge/discharge times, and pack-level SoC metrics.
Web API Type Definitions
webapp/src/types/BatteryConfig.ts, webapp/src/types/PowerLimiterConfig.ts
Updated BatteryZendureConfig interface to remove charge_through_reset and add charge_through_keep_minutes, connection_type, server, port, client_id, app_key, secret, battery_protection_enable, min_soc_hysteresis. Added has_priority: boolean to PowerLimiterInverterConfig.
Web Localization
webapp/src/locales/de.json, webapp/src/locales/en.json, webapp/src/locales/fr.json
Updated UI strings for inverter priority checkbox and hints. Changed ProviderZendureMqtt label to generic "Zendure". Removed device-type top-level keys and reorganized under batteryadmin.zendure namespace. Changed control-mode keys from PascalCase to lowercase (full/once/readOnly). Added new state labels for control-state and protection settings. Removed French translations for battery provider/Zendure config.
Web UI Components
webapp/src/views/BatteryAdminView.vue, webapp/src/views/PowerLimiterAdminView.vue
Added connection-type dropdown to conditionally show device-type/device-id/polling fields based on connection mode. Extended output-control section with battery-protection and charge-through-minutes fields. Added credential display alert for LocalMqtt and server/port/client-id/app-key/secret fields for ZendureMqtt. Added per-inverter priority checkbox for SmartBuffer-powered inverters. Imported spark-md5 for credential computation.
Build Configuration
webapp/vite.config.ts
Increased build.chunkSizeWarningLimit from 1024 to 2048 to accommodate expanded web bundle size.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Web UI
    participant Backend as OpenDTU Backend
    participant LocalMQTT as Local MQTT Broker
    participant ZendureCloud as Zendure Cloud MQTT
    participant Device as Zendure Device

    rect rgba(0, 150, 200, 0.5)
        Note over UI,Backend: LocalMqttProvider Connection Type

        UI->>Backend: POST config (connection_type=LocalMqtt, device_id=ABC12345)
        Backend->>Backend: Instantiate LocalMqttProvider
        LocalMqttProvider->>LocalMQTT: Subscribe to device topics (report, log, timesync)
        LocalMqttProvider->>LocalMQTT: Send device config/control messages

        Device->>LocalMQTT: Publish report (pack data, state, SoC)
        LocalMQTT->>LocalMqttProvider: Deliver report payload
        LocalMqttProvider->>LocalMqttProvider: Parse JSON, extract pack data
        LocalMqttProvider->>Backend: Update Stats (SoC, voltage, power, state)

        LocalMqttProvider->>LocalMqttProvider: Check battery protection (SoC thresholds)
        alt SoC triggers protection
            LocalMqttProvider->>Backend: setControlState(BatteryProtection)
            Backend->>LocalMqttProvider: Publish persistent settings
        end
    end

    rect rgba(200, 100, 50, 0.5)
        Note over UI,ZendureCloud: ZendureMqttProvider Connection Type

        UI->>Backend: POST config (connection_type=ZendureMqtt, server, app_key, secret, client_id)
        Backend->>Backend: Instantiate ZendureMqttProvider
        ZendureMqttProvider->>ZendureMqtt: Create client with credentials
        ZendureMqttProvider->>ZendureCloud: Connect & authenticate
        ZendureMqttProvider->>ZendureCloud: Subscribe to report topic (derived from app_key)

        Device->>ZendureCloud: Publish device metrics (via cloud)
        ZendureCloud->>ZendureMqttProvider: Deliver encrypted/fragmented report
        ZendureMqttProvider->>ZendureMqttProvider: Reassemble fragments & deserialize JSON
        ZendureMqttProvider->>ZendureMqttProvider: Extract pack count, serial, power metrics
        ZendureMqttProvider->>Backend: Update Stats with cloud data
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

Poem

🐰 Through MQTT forests, both near and far,
LocalMqtt whispers, Cloud's a star,
Control states bloom, protection so keen,
Priorities queue in the power between,
Optional fields now hold their place,
Battery keeps time with measured grace!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants