Skip to content

Commit

Permalink
Merge development into master to prepare release 2024.03.23
Browse files Browse the repository at this point in the history
Prepare release 2024.03.23
  • Loading branch information
schlimmchen authored Mar 23, 2024
2 parents 490a38f + 06f39f8 commit 0169b29
Show file tree
Hide file tree
Showing 103 changed files with 3,354 additions and 1,962 deletions.
1 change: 0 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"DavidAnson.vscode-markdownlint",
"EditorConfig.EditorConfig",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"platformio.platformio-ide"
],
"unwantedRecommendations": [
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ Summer 2022 I bought my Victron MPPT battery charger, and didn't like the idea t

This project is still under development and adds following features:

> **Warning**
>
> In contrast to the original openDTU, with release 2023.05.23.post1 openDTU-onBattery supports only 5 inverters. Otherwise, there is not enough memory for the liveData view.
* Support Victron's Ve.Direct protocol on the same chip (cable based serial interface!). Additional information about Ve.direct can be downloaded directly from [Victron's website](https://www.victronenergy.com/support-and-downloads/technical-information).
* Dynamically sets the Hoymiles power limited according to the currently used energy in the household. Needs an HTTP JSON based power meter (e.g. Tasmota), an MQTT based power meter like Shelly 3EM or an SDM power meter.
* Battery support: Read the voltage from Victron MPPT charge controller or from the Hoymiles DC inputs and starts/stops the power producing based on configurable voltage thresholds
Expand Down
6 changes: 2 additions & 4 deletions docs/hardware_flash.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ The SN65HVD230 CAN bus transceiver is used to interface with the Pylontech batte

### MCP2515 CAN bus module

The MCP2515 CAN bus module consists of a CAN bus controller and a CAN bus transceiver and is used to interface with the Huawei AC charger. This CAN bus operates at 125kbit/s. The module is connected via SPI and currently requires a separate SPI bus. If you want to use the Huawei AC charger make sure to get an ESP which supports 2 SPI busses. Currently the SPI bus host is hardcoded to number 2. This may change in future. Please note: Using the Huawei AC charger in combination with the CMT2300A radio board is not supported at the moment.

MCP2515 CAN bus modules that are widely available are designed for 5V supply voltage. To make them work with 3.3V / the ESP32 a modification is required. [This modification is described here.](https://forums.raspberrypi.com/viewtopic.php?t=141052)
See [Wiki](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki/Huawei-AC-PSU) for details.

### Relay module

Expand Down Expand Up @@ -188,4 +186,4 @@ After the successful upload, the OpenDTU immediately restarts into the new firmw
## Builds

Different builds from existing installations can be found here [Builds](builds/README.md)
Like to show your own build? Just send me a Pull Request.
Like to show your own build? Just send me a Pull Request.
33 changes: 16 additions & 17 deletions include/Battery.h
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <stdint.h>
#include <memory>
#include <mutex>
#include <TaskSchedulerDeclarations.h>

#include "BatteryStats.h"

class BatteryProvider {
public:
// returns true if the provider is ready for use, false otherwise
virtual bool init(bool verboseLogging) = 0;

virtual void deinit() = 0;
virtual void loop() = 0;
virtual std::shared_ptr<BatteryStats> getStats() const = 0;
public:
// returns true if the provider is ready for use, false otherwise
virtual bool init(bool verboseLogging) = 0;
virtual void deinit() = 0;
virtual void loop() = 0;
virtual std::shared_ptr<BatteryStats> getStats() const = 0;
virtual bool usesHwPort2() const { return false; }
};

class BatteryClass {
public:
void init(Scheduler&);
void updateSettings();
public:
void init(Scheduler&);
void updateSettings();

std::shared_ptr<BatteryStats const> getStats() const;
private:
void loop();
std::shared_ptr<BatteryStats const> getStats() const;

Task _loopTask;
private:
void loop();

mutable std::mutex _mutex;
std::unique_ptr<BatteryProvider> _upProvider = nullptr;
Task _loopTask;
mutable std::mutex _mutex;
std::unique_ptr<BatteryProvider> _upProvider = nullptr;
};

extern BatteryClass Battery;
12 changes: 10 additions & 2 deletions include/BatteryStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,22 @@ class BatteryStats {
bool isSoCValid() const { return _lastUpdateSoC > 0; }
bool isVoltageValid() const { return _lastUpdateVoltage > 0; }

// returns true if the battery reached a critically low voltage/SoC,
// such that it is in need of charging to prevent degredation.
virtual bool needsCharging() const { return false; }

protected:
virtual void mqttPublish() const;

void setSoC(float soc, uint8_t precision, uint32_t timestamp) {
_soc = soc;
_socPrecision = precision;
_lastUpdateSoC = timestamp;
_lastUpdateSoC = _lastUpdate = timestamp;
}

void setVoltage(float voltage, uint32_t timestamp) {
_voltage = voltage;
_lastUpdateVoltage = timestamp;
_lastUpdateVoltage = _lastUpdate = timestamp;
}

String _manufacturer = "unknown";
Expand All @@ -67,6 +71,7 @@ class PylontechBatteryStats : public BatteryStats {
public:
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
bool needsCharging() const final { return _chargeImmediately; }

private:
void setManufacturer(String&& m) { _manufacturer = std::move(m); }
Expand Down Expand Up @@ -147,6 +152,9 @@ class VictronSmartShuntStats : public BatteryStats {
float _chargedEnergy;
float _dischargedEnergy;
String _modelName;
int32_t _instantaneousPower;
float _consumedAmpHours;
int32_t _lastFullCharge;

bool _alarmLowVoltage;
bool _alarmHighVoltage;
Expand Down
14 changes: 8 additions & 6 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#define MQTT_MAX_CERT_STRLEN 2560

#define INV_MAX_NAME_STRLEN 31
#define INV_MAX_COUNT 5
#define INV_MAX_COUNT 10
#define INV_MAX_CHAN_COUNT 6

#define CHAN_MAX_NAME_STRLEN 31
Expand Down Expand Up @@ -198,16 +198,17 @@ struct CONFIG_T {
bool HttpIndividualRequests;
POWERMETER_HTTP_PHASE_CONFIG_T Http_Phase[POWERMETER_MAX_PHASES];
} PowerMeter;

struct {
bool Enabled;
bool VerboseLogging;
bool SolarPassThroughEnabled;
uint8_t SolarPassThroughLosses;
uint8_t BatteryDrainStategy;
bool BatteryAlwaysUseAtNight;
uint32_t Interval;
bool IsInverterBehindPowerMeter;
uint8_t InverterId;
bool IsInverterSolarPowered;
uint64_t InverterId;
uint8_t InverterChannelId;
int32_t TargetPowerConsumption;
int32_t TargetPowerConsumptionHysteresis;
Expand All @@ -224,7 +225,7 @@ struct CONFIG_T {
float FullSolarPassThroughStartVoltage;
float FullSolarPassThroughStopVoltage;
} PowerLimiter;

struct {
bool Enabled;
bool VerboseLogging;
Expand All @@ -242,7 +243,7 @@ struct CONFIG_T {
float Auto_Power_Voltage_Limit;
float Auto_Power_Enable_Voltage_Limit;
float Auto_Power_Lower_Power_Limit;
float Auto_Power_Upper_Power_Limit;
float Auto_Power_Upper_Power_Limit;
} Huawei;


Expand All @@ -260,6 +261,7 @@ class ConfigurationClass {

INVERTER_CONFIG_T* getFreeInverterSlot();
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
void deleteInverterById(const uint8_t id);
};

extern ConfigurationClass Configuration;
2 changes: 1 addition & 1 deletion include/HttpPowerMeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class HttpPowerMeterClass {
String extractParam(String& authReq, const String& param, const char delimit);
String getcNonce(const int len);
String getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter);
bool tryGetFloatValueForPhase(int phase, int httpCode, const char* jsonPath);
bool tryGetFloatValueForPhase(int phase, const char* jsonPath);
void prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue);
String sha256(const String& data);
};
Expand Down
1 change: 1 addition & 0 deletions include/JkBmsController.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Controller : public BatteryProvider {
void deinit() final;
void loop() final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
bool usesHwPort2() const final { return true; }

private:
enum class Status : unsigned {
Expand Down
32 changes: 16 additions & 16 deletions include/MqttBattery.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
#include <espMqttClient.h>

class MqttBattery : public BatteryProvider {
public:
MqttBattery() = default;
public:
MqttBattery() = default;

bool init(bool verboseLogging) final;
void deinit() final;
void loop() final { return; } // this class is event-driven
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
bool init(bool verboseLogging) final;
void deinit() final;
void loop() final { return; } // this class is event-driven
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }

private:
bool _verboseLogging = false;
String _socTopic;
String _voltageTopic;
std::shared_ptr<MqttBatteryStats> _stats = std::make_shared<MqttBatteryStats>();
private:
bool _verboseLogging = false;
String _socTopic;
String _voltageTopic;
std::shared_ptr<MqttBatteryStats> _stats = std::make_shared<MqttBatteryStats>();

std::optional<float> getFloat(std::string const& src, char const* topic);
void onMqttMessageSoC(espMqttClientTypes::MessageProperties const& properties,
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total);
void onMqttMessageVoltage(espMqttClientTypes::MessageProperties const& properties,
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total);
std::optional<float> getFloat(std::string const& src, char const* topic);
void onMqttMessageSoC(espMqttClientTypes::MessageProperties const& properties,
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total);
void onMqttMessageVoltage(espMqttClientTypes::MessageProperties const& properties,
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total);
};
16 changes: 14 additions & 2 deletions include/MqttHandlePowerLimiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@ class MqttHandlePowerLimiterClass {

private:
void loop();
void onCmdMode(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);

enum class MqttPowerLimiterCommand : unsigned {
Mode,
BatterySoCStartThreshold,
BatterySoCStopThreshold,
FullSolarPassthroughSoC,
VoltageStartThreshold,
VoltageStopThreshold,
FullSolarPassThroughStartVoltage,
FullSolarPassThroughStopVoltage
};

void onMqttCmd(MqttPowerLimiterCommand command, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);

Task _loopTask;

Expand All @@ -28,4 +40,4 @@ class MqttHandlePowerLimiterClass {
std::deque<std::function<void()>> _mqttCallbacks;
};

extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
26 changes: 26 additions & 0 deletions include/MqttHandlePowerLimiterHass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ArduinoJson.h>
#include <TaskSchedulerDeclarations.h>

class MqttHandlePowerLimiterHassClass {
public:
void init(Scheduler& scheduler);
void publishConfig();
void forceUpdate();

private:
void loop();
void publish(const String& subtopic, const String& payload);
void publishNumber(const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min, const int16_t max);
void publishSelect(const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic);
void createDeviceInfo(JsonObject& object);

Task _loopTask;

bool _wasConnected = false;
bool _updateForced = false;
};

extern MqttHandlePowerLimiterHassClass MqttHandlePowerLimiterHass;
8 changes: 6 additions & 2 deletions include/MqttHandleVedirect.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "VeDirectMpptController.h"
#include "Configuration.h"
#include <Arduino.h>
#include <map>
#include <TaskSchedulerDeclarations.h>

#ifndef VICTRON_PIN_RX
Expand All @@ -20,7 +21,7 @@ class MqttHandleVedirectClass {
void forceUpdate();
private:
void loop();
VeDirectMpptController::veMpptStruct _kvFrame{};
std::map<std::string, VeDirectMpptController::veMpptStruct> _kvFrames;

Task _loopTask;

Expand All @@ -31,6 +32,9 @@ class MqttHandleVedirectClass {
uint32_t _nextPublishFull = 1;

bool _PublishFull;

void publish_mppt_data(const VeDirectMpptController::spData_t &spMpptData,
VeDirectMpptController::veMpptStruct &frame) const;
};

extern MqttHandleVedirectClass MqttHandleVedirect;
extern MqttHandleVedirectClass MqttHandleVedirect;
14 changes: 10 additions & 4 deletions include/MqttHandleVedirectHass.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ class MqttHandleVedirectHassClass {
private:
void loop();
void publish(const String& subtopic, const String& payload);
void publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off);
void publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass = NULL, const char* stateClass = NULL, const char* unitOfMeasurement = NULL);
void createDeviceInfo(JsonObject& object);
void publishBinarySensor(const char *caption, const char *icon, const char *subTopic,
const char *payload_on, const char *payload_off,
const VeDirectMpptController::spData_t &spMpptData);
void publishSensor(const char *caption, const char *icon, const char *subTopic,
const char *deviceClass, const char *stateClass,
const char *unitOfMeasurement,
const VeDirectMpptController::spData_t &spMpptData);
void createDeviceInfo(JsonObject &object,
const VeDirectMpptController::spData_t &spMpptData);

Task _loopTask;

bool _wasConnected = false;
bool _updateForced = false;
};

extern MqttHandleVedirectHassClass MqttHandleVedirectHass;
extern MqttHandleVedirectHassClass MqttHandleVedirectHass;
11 changes: 9 additions & 2 deletions include/PinMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,13 @@ struct PinMapping_t {
uint8_t display_clk;
uint8_t display_cs;
uint8_t display_reset;
int8_t led[PINMAPPING_LED_COUNT];

// OpenDTU-OnBattery-specific pins below
int8_t victron_tx;
int8_t victron_rx;
int8_t victron_tx2;
int8_t victron_rx2;
int8_t battery_rx;
int8_t battery_rxen;
int8_t battery_tx;
Expand All @@ -50,7 +55,9 @@ struct PinMapping_t {
int8_t huawei_irq;
int8_t huawei_cs;
int8_t huawei_power;
int8_t led[PINMAPPING_LED_COUNT];
int8_t powermeter_rx;
int8_t powermeter_tx;
int8_t powermeter_dere;
};

class PinMappingClass {
Expand All @@ -63,7 +70,7 @@ class PinMappingClass {
bool isValidCmt2300Config() const;
bool isValidEthConfig() const;
bool isValidHuaweiConfig() const;

private:
PinMapping_t _pinMapping;
};
Expand Down
Loading

0 comments on commit 0169b29

Please sign in to comment.