Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Support for Solarflow Batteries without Cloud #1221

Open
wants to merge 44 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
da8ca14
Implemented basic support for Zendure Solarflow batteries that are co…
vaterlangen Aug 29, 2024
67df55b
fixed typo
vaterlangen Aug 29, 2024
78e0318
fixed typos in translation
vaterlangen Aug 29, 2024
69d41d9
making linter happy
vaterlangen Aug 29, 2024
9e05fd7
added missing translation for config option
vaterlangen Aug 29, 2024
f992c42
added dropdown setting
vaterlangen Aug 30, 2024
d6c2c43
allow non-translated section captions
vaterlangen Aug 30, 2024
d94b229
useing multiple cards and added basic battery type detection
vaterlangen Aug 30, 2024
770dc43
fixed pack current calculation
vaterlangen Aug 30, 2024
1826245
fixed setting battery current twice
vaterlangen Aug 30, 2024
5853b90
Use _ as indicator for no translate to keep API clean
vaterlangen Aug 31, 2024
7082076
Rearranged some status data
vaterlangen Aug 31, 2024
9bcea56
Added missing translation
vaterlangen Aug 31, 2024
0769ee0
added more values read from battery
vaterlangen Sep 3, 2024
05c8b89
added translations
vaterlangen Sep 3, 2024
a90a855
minor improvements and added HASS auto discovery
vaterlangen Sep 8, 2024
1be687a
replaced sprintf by snprintf
vaterlangen Sep 8, 2024
97439dc
reworked to use log data, as this contains more data
vaterlangen Sep 13, 2024
d1fd4d3
adding more configuration options and reworked pack handling
vaterlangen Sep 14, 2024
bea83d8
Merge branch 'development' into feature/zendure_solarflow
vaterlangen Sep 14, 2024
b2cef7d
Making linter happy
vaterlangen Sep 14, 2024
8583316
moved to battery provider with index 7 to prevent collision with #119…
vaterlangen Sep 14, 2024
82d0649
removed some leftovers from development
vaterlangen Sep 14, 2024
0e31e5e
fixed reading master version number
vaterlangen Sep 15, 2024
d65be30
Merge branch 'development' into feature/zendure_solarflow
vaterlangen Sep 16, 2024
e51b641
Merge branch 'development' into feature/zendure_solarflow
vaterlangen Sep 25, 2024
a7aa5e3
apply yarn prettier
vaterlangen Sep 25, 2024
cc601d2
added configuration option to set static output limit
vaterlangen Sep 26, 2024
88852eb
fixed bug in configprocessing and removed deprecated function
vaterlangen Sep 26, 2024
1148669
Merge branch 'development' into feature/zendure_solarflow
vaterlangen Sep 28, 2024
a4fc4c6
Merge branch 'development' into feature/zendure_solarflow
vaterlangen Sep 30, 2024
bbbb020
* hopefully fixed unintended inverter restarts due to HUB driving out…
vaterlangen Sep 30, 2024
f2c6027
hide empty versions
vaterlangen Oct 1, 2024
8c40c69
do not prevent HUB from shutdown
vaterlangen Oct 1, 2024
9ce8ae7
implemented schedule mode to set output power
vaterlangen Oct 1, 2024
69e2d78
fixed data type in JSON request
vaterlangen Oct 1, 2024
50ee059
fixed timesync listening on wrong topic and added messageId counter f…
vaterlangen Oct 4, 2024
646efd3
* use epoch instead of local time
vaterlangen Oct 4, 2024
7b0bac3
implemented charge through for battery housekeeping
vaterlangen Oct 9, 2024
18c9cd4
Merge remote-tracking branch 'origin/development' into feature/zendur…
vaterlangen Oct 9, 2024
80fa03f
make it prettier
vaterlangen Oct 9, 2024
a2e71cb
Merge branch 'development' into feature/zendure_solarflow
vaterlangen Oct 18, 2024
0856e40
Merge remote-tracking branch 'origin/development' into feature/zendur…
vaterlangen Oct 21, 2024
5aa1a3a
Merge branch 'development' into feature/zendure_solarflow
vaterlangen Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions include/BatteryStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "JbdBmsDataPoints.h"
#include "VeDirectShuntController.h"
#include <cfloat>
#include <utility>

// mandatory interface for all kinds of batteries
class BatteryStats {
Expand Down Expand Up @@ -352,3 +353,205 @@ class MqttBatteryStats : public BatteryStats {

bool supportsAlarmsAndWarnings() const final { return false; }
};

class ZendureBatteryStats : public BatteryStats {
friend class ZendureBattery;

enum class State : uint8_t {
Idle = 0,
Charging = 1,
Discharging = 2,
Invalid = 255
};

enum class BypassMode : uint8_t {
Automatic = 0,
AlwaysOff = 1,
AlwaysOn = 2,
Invalid = 255
};

template <typename T>
static T stateToString(State state){
switch (state) {
case State::Idle:
return "idle";
case State::Charging:
return "charging";
case State::Discharging:
return "discharging";
default:
return "invalid";
}
}
template <typename T>
static T bypassModeToString(BypassMode state){
switch (state) {
case BypassMode::Automatic:
return "automatic";
case BypassMode::AlwaysOff:
return "alwaysoff";
case BypassMode::AlwaysOn:
return "alwayson";
default:
return "invalid";
}
}
inline static bool isDischarging(State state){
return state == State::Discharging;
}
inline static bool isCharging(State state){
return state == State::Charging;
}

class PackStats {
friend class ZendureBatteryStats;
friend class ZendureBattery;

public:
PackStats() {}
explicit PackStats(String serial) : _serial(serial) {}
virtual ~PackStats(){ }

String getSerial() const { return _serial; }

inline uint8_t getCellCount() const { return _cellCount; }
inline uint16_t getCapacity() const { return _capacity; }
inline uint16_t getAvailableCapacity() const { return _capacity_avail; };
inline String getName() const { return _name; }

static std::shared_ptr<PackStats> fromSerial(String serial){
if (serial.length() == 15) {
if (serial.startsWith("AO4H")){
return std::make_shared<PackStats>(PackStats(serial, "AB1000", 960));
}
if (serial.startsWith("CO4H")){
return std::make_shared<PackStats>(PackStats(serial, "AB2000", 1920));
}
if (serial.startsWith("R04Y")){
return std::make_shared<PackStats>(PackStats(serial, "AIO2400", 2400));
}
return std::make_shared<PackStats>(PackStats(serial));
}
return nullptr;
};

protected:
explicit PackStats(String serial, String name, uint16_t capacity, uint8_t cellCount = 15) :
_serial(serial), _name(name), _capacity(capacity), _cellCount(cellCount){}
void setSerial(String serial) { _serial = serial; }
void setHwVersion(String&& version) { _hwversion = std::move(version); }
void setFwVersion(String&& version) { _fwversion = std::move(version); }

private:
String _serial = String();
String _name = String("UNKOWN");
uint16_t _capacity = 0;
uint8_t _cellCount = 15;

String _fwversion = String();
String _hwversion = String();

uint16_t _cell_voltage_min = 0;
uint16_t _cell_voltage_max = 0;
uint16_t _cell_voltage_spread = 0;
uint16_t _cell_voltage_avg = 0;
int16_t _cell_temperature_max = 0;

float _state_of_health = 1;
uint16_t _capacity_avail = 0;

float _voltage_total = 0.0;
float _current = 0.0;
int16_t _power = 0;
float _soc_level = 0.0;
State _state = State::Invalid;

uint32_t _lastUpdate = 0;
};

public:
ZendureBatteryStats() { setManufacturer("Zendure"); }

void mqttPublish() const;
void getLiveViewData(JsonVariant& root) const;

std::map<size_t, std::shared_ptr<PackStats>> getPackDataList() const { return _packData; }

bool supportsAlarmsAndWarnings() const final { return false; }

protected:
std::optional<std::shared_ptr<ZendureBatteryStats::PackStats> > getPackData(size_t index) const;
std::optional<std::shared_ptr<ZendureBatteryStats::PackStats> > addPackData(size_t index, String serial);

uint16_t getCapacity() const { return _capacity; };
uint16_t getUseableCapacity() const { return _capacity_avail * (static_cast<float>(_soc_max - _soc_min) / 100.0); };

private:
void setHwVersion(String&& version) {
if (!version.isEmpty()){
_hwversion = _device + " (" + std::move(version) + ")";
}else{
_hwversion = _device;
}
}
void setFwVersion(String&& version) { _fwversion = std::move(version); }

void setSerial(String serial) {
_serial = serial;
}
void setSerial(std::optional<String> serial) {
if (serial.has_value()){
setSerial(*serial);
}
}

void setDevice(String&& device) {
_device = std::move(device);
}

String _device = String("Unkown");

std::map<size_t, std::shared_ptr<PackStats>> _packData = std::map<size_t, std::shared_ptr<PackStats> >();

int16_t _cellTemperature = 0;
uint16_t _cellMinMilliVolt = 0;
uint16_t _cellMaxMilliVolt = 0;
uint16_t _cellDeltaMilliVolt = 0;
uint16_t _cellAvgMilliVolt = 0;

float _soc_max = 0.0;
float _soc_min = 0.0;

uint16_t _inverse_max = 0;
uint16_t _input_limit = 0;
uint16_t _output_limit = 0;

float _efficiency = 0.0;
uint16_t _capacity = 0;
uint16_t _capacity_avail = 0;

uint16_t _charge_power = 0;
uint16_t _discharge_power = 0;
uint16_t _output_power = 0;
uint16_t _input_power = 0;
uint16_t _solar_power_1 = 0;
uint16_t _solar_power_2 = 0;

int16_t _remain_out_time = 0;
int16_t _remain_in_time = 0;

State _state = State::Invalid;
uint8_t _num_batteries = 0;
BypassMode _bypass_mode = BypassMode::Invalid;
bool _bypass_state = false;
bool _auto_recover = false;
bool _heat_state = false;
bool _auto_shutdown = false;
bool _buzzer = false;

std::optional<uint64_t> _last_full_timestamp = std::nullopt;
std::optional<uint32_t> _last_full_charge_hours = std::nullopt;
std::optional<uint64_t> _last_empty_timestamp = std::nullopt;
std::optional<bool> _charge_through_state = std::nullopt;
};
20 changes: 20 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
#define POWERMETER_HTTP_JSON_MAX_PATH_STRLEN 256
#define BATTERY_JSON_MAX_PATH_STRLEN 128

#define ZENDURE_MAX_SERIAL_STRLEN 8

struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower;
char Name[CHAN_MAX_NAME_STRLEN];
Expand Down Expand Up @@ -174,6 +176,8 @@ enum BatteryVoltageUnit { Volts = 0, DeciVolts = 1, CentiVolts = 2, MilliVolts =

enum BatteryAmperageUnit { Amps = 0, MilliAmps = 1 };

enum ZendureBatteryOutputControl { ControlNone = 0, ControlFixed = 1, ControlSchedule = 2 };

struct BATTERY_CONFIG_T {
bool Enabled;
bool VerboseLogging;
Expand All @@ -193,6 +197,22 @@ struct BATTERY_CONFIG_T {
char MqttDischargeCurrentTopic[MQTT_MAX_TOPIC_STRLEN + 1];
char MqttDischargeCurrentJsonPath[BATTERY_JSON_MAX_PATH_STRLEN + 1];
BatteryAmperageUnit MqttAmperageUnit;
uint8_t ZendureDeviceType;
char ZendureDeviceId[ZENDURE_MAX_SERIAL_STRLEN + 1];
uint8_t ZendurePollingInterval;
uint8_t ZendureMinSoC;
uint8_t ZendureMaxSoC;
uint8_t ZendureBypassMode;
uint16_t ZendureMaxOutput;
bool ZendureAutoShutdown;
uint16_t ZendureOutputLimit;
ZendureBatteryOutputControl ZendureOutputControl;
int16_t ZendureSunriseOffset;
int16_t ZendureSunsetOffset;
uint16_t ZendureOutputLimitDay;
uint16_t ZendureOutputLimitNight;
bool ZendureChargeThroughEnable;
uint16_t ZendureChargeThroughInterval;
};
using BatteryConfig = struct BATTERY_CONFIG_T;

Expand Down
5 changes: 5 additions & 0 deletions include/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ class Utils {
template <typename T>
static std::optional<T> getNumericValueFromMqttPayload(char const* client,
std::string const& src, char const* topic, char const* jsonPath);

template<typename T>
static std::optional<T> getJsonElement(JsonObjectConst root, char const* key, size_t nesting = 0);

static bool getEpoch(time_t* epoch, uint32_t ms);
};
Loading