Skip to content

Commit c525713

Browse files
authored
Prepare Release 2025.02.10
Merges branch development into master.
2 parents ac14ce5 + 3858004 commit c525713

38 files changed

+1333
-272
lines changed

.github/workflows/repo-maintenance.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,29 @@ jobs:
2020
steps:
2121
- uses: actions/stale@v9
2222
with:
23-
days-before-stale: 14
24-
days-before-close: 60
25-
any-of-labels: 'cant-reproduce,not a bug'
26-
stale-issue-label: stale
27-
stale-pr-label: stale
23+
start-date: 2023-04-26
24+
operations-per-run: 300
25+
remove-stale-when-updated: true
26+
days-before-issue-stale: 14
27+
days-before-issue-close: 14 # days after the issue is marked as stale
2828
stale-issue-message: >
2929
This issue has been automatically marked as stale because it has not had
3030
recent activity. It will be closed if no further activity occurs. Thank you
3131
for your contributions.
32+
close-issue-message: >
33+
This issue has been automatically closed because it has not had
34+
recent activity. Please add a comment if you want to get this issue re-opened. Thank you
35+
for your contributions.
36+
stale-issue-label: stale # adds a label
37+
exempt-issue-labels: 'acknowledged, contribution needed' # exclude issues with label
38+
days-before-pr-stale: 14
39+
days-before-pr-close: -1 # never close prs
40+
stale-pr-label: stale # adds a label
41+
any-of-pr-labels: 'changes requested' # only consider prs with this label
42+
stale-pr-message: >
43+
This pull-request has been automatically marked as stale because it has not had
44+
recent activity. It will be closed if no further activity occurs. Thank you
45+
for your contributions.
3246
3347
lock-threads:
3448
name: 'Lock Old Threads'

README.md

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
![GitHub tag (latest SemVer)](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/AndreasBoehm/856dda48c1cadac6ea495213340c612b/raw/openDTUcoreRelease.json)
55

66
- [OpenDTU-OnBattery](#opendtu-onbattery)
7+
- [About Hardware For Sale](#%EF%B8%8F--about-hardware-for-sale)
78
- [Getting Started](#getting-started)
89
- [Important Differences](#important-differences)
910
- [Documentation](#documentation)
@@ -16,8 +17,54 @@
1617
OpenDTU-OnBattery is a fork of [OpenDTU](https://github.com/tbnobody/OpenDTU),
1718
which adds support for battery chargers, battery management systems (BMS), and
1819
power meters on a single ESP32. Its Dynamic Power Limiter can adjust the
19-
inverter's power production to the actual houshold consumption. In this way, it
20-
is possible to implement a zero export policy.
20+
power production of one or more solar- and/or battery-powered inverter(s) to
21+
the actual houshold consumption. In this way, it is possible to implement a
22+
zero export policy.
23+
24+
## ⚠️ About Hardware For Sale
25+
26+
### Do It Yourself
27+
28+
You are always welcome to select and use your own set of parts to run
29+
OpenDTU-OnBattery. Our [documentation](https://opendtu-onbattery.net/hardware/)
30+
should give you the information you need. There are also [case
31+
designs](https://opendtu-onbattery.net/3rd_party/cases/) available for you to
32+
print yourself.
33+
34+
### Ready-To-Use
35+
36+
If you are interested in ready-to-use hardware available for sale, the
37+
OpenDTU-OnBattery project endorses the **[OpenDTU Fusion
38+
board](https://opendtu-onbattery.net/3rd_party/opendtu_fusion/)**.
39+
40+
### Distribution Channels
41+
42+
OpenDTU Fusion boards and accessories (cases in particular) are available
43+
exclusively through:
44+
45+
- [AllianceApps](https://shop.allianceapps.io/)
46+
- [Semy3D](https://shop.semy3d.de/)
47+
48+
Please note that we do not have any connections to hardware sold on platforms
49+
like eBay, Kleinanzeigen, or similar marketplaces. Items sold there may not
50+
meet the quality and compatibility standards expected by our community.
51+
52+
### Why Purchase Through the Endorsed Channels?
53+
54+
Buying from the trusted sources listed above ensures:
55+
56+
1. Access to reliable, well-tested hardware that meets the project’s requirements.
57+
2. Support for suppliers who align with the values of the OpenDTU community.
58+
59+
To allow for a good overall experience, we encourage users to avoid unverified
60+
hardware sources.
61+
62+
### 🚨 Misleading Claims
63+
64+
We are aware that some sellers falsely advertise that purchasing their hardware
65+
supports the OpenDTU-OnBattery, OpenDTU, and/or AhoyDTU developers financially.
66+
**This is not true.** The OpenDTU-OnBattery project does **not** receive any
67+
financial benefit from the sale of hardware.
2168

2269
## Getting Started
2370

include/Configuration.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
#define CONFIG_FILENAME "/config.json"
1212
#define CONFIG_VERSION 0x00011d00 // 0.1.29 // make sure to clean all after change
13-
#define CONFIG_VERSION_ONBATTERY 4
13+
#define CONFIG_VERSION_ONBATTERY 5
1414

1515
#define WIFI_MAX_SSID_STRLEN 32
1616
#define WIFI_MAX_PASSWORD_STRLEN 64
@@ -136,11 +136,13 @@ struct POWERLIMITER_INVERTER_CONFIG_T {
136136
uint64_t Serial;
137137
bool IsGoverned;
138138
bool IsBehindPowerMeter;
139-
bool IsSolarPowered;
140139
bool UseOverscaling;
141140
uint16_t LowerPowerLimit;
142141
uint16_t UpperPowerLimit;
143142
uint8_t ScalingThreshold;
143+
144+
enum InverterPowerSource { Battery = 0, Solar = 1, SmartBuffer = 2 };
145+
InverterPowerSource PowerSource;
144146
};
145147
using PowerLimiterInverterConfig = struct POWERLIMITER_INVERTER_CONFIG_T;
146148

include/PowerLimiter.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ class PowerLimiterClass {
5252
void setMode(Mode m) { _mode = m; }
5353
Mode getMode() const { return _mode; }
5454
bool usesBatteryPoweredInverter();
55-
bool isGovernedInverterProducing();
55+
bool usesSmartBufferPoweredInverter();
56+
57+
// used to interlock Huawei R48xx grid charger against battery-powered inverters
58+
bool isGovernedBatteryPoweredInverterProducing();
5659

5760
private:
5861
void loop();
@@ -83,7 +86,7 @@ class PowerLimiterClass {
8386
std::pair<float, char const*> getInverterDcVoltage();
8487
float getBatteryVoltage(bool log = false);
8588
uint16_t dcPowerBusToInverterAc(uint16_t dcPower);
86-
void fullSolarPassthrough(PowerLimiterClass::Status reason);
89+
void unconditionalFullSolarPassthrough();
8790
int16_t calcConsumption();
8891
using inverter_filter_t = std::function<bool(PowerLimiterInverter const&)>;
8992
uint16_t updateInverterLimits(uint16_t powerRequested, inverter_filter_t filter, std::string const& filterExpression);
@@ -102,6 +105,7 @@ class PowerLimiterClass {
102105
bool isStopThresholdReached();
103106
bool isBelowStopThreshold();
104107
void calcNextInverterRestart();
108+
bool isSolarPassThroughEnabled();
105109
bool isFullSolarPassthroughActive();
106110
};
107111

include/PowerLimiterBatteryInverter.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class PowerLimiterBatteryInverter : public PowerLimiterInverter {
1212
uint16_t applyReduction(uint16_t reduction, bool allowStandby) final;
1313
uint16_t applyIncrease(uint16_t increase) final;
1414
uint16_t standby() final;
15-
bool isSolarPowered() const final { return false; }
1615

1716
private:
1817
void setAcOutput(uint16_t expectedOutputWatts) final;

include/PowerLimiterInverter.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ class PowerLimiterInverter {
6868
uint64_t getSerial() const { return _config.Serial; }
6969
char const* getSerialStr() const { return _serialStr; }
7070
bool isBehindPowerMeter() const { return _config.IsBehindPowerMeter; }
71-
virtual bool isSolarPowered() const = 0;
71+
72+
bool isBatteryPowered() const { return _config.PowerSource == PowerLimiterInverterConfig::InverterPowerSource::Battery; }
73+
bool isSolarPowered() const { return _config.PowerSource == PowerLimiterInverterConfig::InverterPowerSource::Solar; }
74+
bool isSmartBufferPowered() const { return _config.PowerSource == PowerLimiterInverterConfig::InverterPowerSource::SmartBuffer; }
7275

7376
void debug() const;
7477

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
#pragma once
3+
4+
#include "PowerLimiterInverter.h"
5+
6+
class PowerLimiterOverscalingInverter : public PowerLimiterInverter {
7+
public:
8+
PowerLimiterOverscalingInverter(bool verboseLogging, PowerLimiterInverterConfig const& config);
9+
10+
uint16_t applyIncrease(uint16_t increase) final;
11+
12+
protected:
13+
void setAcOutput(uint16_t expectedOutputWatts) final;
14+
15+
private:
16+
uint16_t scaleLimit(uint16_t expectedOutputWatts);
17+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
#pragma once
3+
4+
#include "PowerLimiterOverscalingInverter.h"
5+
6+
class PowerLimiterSmartBufferInverter : public PowerLimiterOverscalingInverter {
7+
public:
8+
PowerLimiterSmartBufferInverter(bool verboseLogging, PowerLimiterInverterConfig const& config);
9+
10+
uint16_t getMaxReductionWatts(bool allowStandby) const final;
11+
uint16_t getMaxIncreaseWatts() const final;
12+
uint16_t applyReduction(uint16_t reduction, bool allowStandby) final;
13+
uint16_t standby() final;
14+
};

include/PowerLimiterSolarInverter.h

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
#pragma once
33

4-
#include "PowerLimiterInverter.h"
4+
#include "PowerLimiterOverscalingInverter.h"
55

6-
class PowerLimiterSolarInverter : public PowerLimiterInverter {
6+
class PowerLimiterSolarInverter : public PowerLimiterOverscalingInverter {
77
public:
88
PowerLimiterSolarInverter(bool verboseLogging, PowerLimiterInverterConfig const& config);
99

1010
uint16_t getMaxReductionWatts(bool allowStandby) const final;
1111
uint16_t getMaxIncreaseWatts() const final;
1212
uint16_t applyReduction(uint16_t reduction, bool allowStandby) final;
13-
uint16_t applyIncrease(uint16_t increase) final;
1413
uint16_t standby() final;
15-
bool isSolarPowered() const final { return true; }
16-
17-
private:
18-
uint16_t scaleLimit(uint16_t expectedOutputWatts);
19-
void setAcOutput(uint16_t expectedOutputWatts) final;
2014
};

include/WebApi_network.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66

77
class WebApiNetworkClass {
88
public:
9+
WebApiNetworkClass();
910
void init(AsyncWebServer& server, Scheduler& scheduler);
1011

1112
private:
1213
void onNetworkStatus(AsyncWebServerRequest* request);
1314
void onNetworkAdminGet(AsyncWebServerRequest* request);
1415
void onNetworkAdminPost(AsyncWebServerRequest* request);
16+
17+
Task _applyDataTask;
18+
void applyDataTaskCb();
1519
};

include/defaults.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@
131131
#define POWERLIMITER_CONDUCTION_LOSSES 3
132132
#define POWERLIMITER_BATTERY_ALWAYS_USE_AT_NIGHT false
133133
#define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true
134-
#define POWERLIMITER_IS_INVERTER_SOLAR_POWERED false
135134
#define POWERLIMITER_USE_OVERSCALING false
136135
#define POWERLIMITER_INVERTER_CHANNEL_ID 0
137136
#define POWERLIMITER_TARGET_POWER_CONSUMPTION 0

include/solarcharger/DummyStats.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ class DummyStats : public Stats {
1313
std::optional<uint16_t> getPanelPowerWatts() const final { return std::nullopt; }
1414
std::optional<float> getYieldTotal() const final { return std::nullopt; }
1515
std::optional<float> getYieldDay() const final { return std::nullopt; }
16+
std::optional<StateOfOperation> getStateOfOperation() const final { return std::nullopt; }
17+
std::optional<float> getFloatVoltage() const final { return std::nullopt; }
18+
std::optional<float> getAbsorptionVoltage() const final { return std::nullopt; }
1619
void getLiveViewData(JsonVariant& root, const boolean fullUpdate, const uint32_t lastPublish) const final {}
1720
void mqttPublish() const final {}
1821
void mqttPublishSensors(const boolean forcePublish) const final {}

include/solarcharger/Stats.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ class Stats {
2626
// sum of today's yield of all MPPT charge controllers in Wh
2727
virtual std::optional<float> getYieldDay() const;
2828

29+
// state of operation from the first available controller
30+
enum class StateOfOperation : uint8_t { Off = 0, Bulk = 1, Absorption = 2, Float = 3, Various = 255 };
31+
virtual std::optional<Stats::StateOfOperation> getStateOfOperation() const;
32+
33+
// float voltage from the first available charge controller
34+
virtual std::optional<float> getFloatVoltage() const;
35+
36+
// absorption voltage from the first available charge controller
37+
virtual std::optional<float> getAbsorptionVoltage() const;
38+
2939
// convert stats to JSON for web application live view
3040
virtual void getLiveViewData(JsonVariant& root, const boolean fullUpdate, const uint32_t lastPublish) const;
3141

include/solarcharger/mqtt/Stats.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ friend class Provider;
1717
std::optional<uint16_t> getPanelPowerWatts() const final { return std::nullopt; }
1818
std::optional<float> getYieldTotal() const final { return std::nullopt; }
1919
std::optional<float> getYieldDay() const final { return std::nullopt; }
20+
std::optional<StateOfOperation> getStateOfOperation() const final { return std::nullopt; }
21+
std::optional<float> getFloatVoltage() const final { return std::nullopt; }
22+
std::optional<float> getAbsorptionVoltage() const final { return std::nullopt; }
2023

2124
void getLiveViewData(JsonVariant& root, const boolean fullUpdate, const uint32_t lastPublish) const final;
2225

include/solarcharger/victron/Stats.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ class Stats : public ::SolarChargers::Stats {
1616
std::optional<uint16_t> getPanelPowerWatts() const final;
1717
std::optional<float> getYieldTotal() const final;
1818
std::optional<float> getYieldDay() const final;
19+
std::optional<StateOfOperation> getStateOfOperation() const final;
20+
std::optional<float> getFloatVoltage() const final;
21+
std::optional<float> getAbsorptionVoltage() const final;
1922

2023
void getLiveViewData(JsonVariant& root, const boolean fullUpdate, const uint32_t lastPublish) const final;
2124
void mqttPublish() const final;

0 commit comments

Comments
 (0)