Prepare Release 2024.06.03 (#1024)

This commit is contained in:
Bernhard Kirchen 2024-06-03 20:10:56 +02:00 committed by GitHub
commit 5ab6fe913b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 393 additions and 299 deletions

View File

@ -1,23 +1,23 @@
- [OpenDTU-onBattery](#opendtu-onbattery)
- [What is OpenDTU-onBattery](#what-is-opendtu-onbattery)
- [OpenDTU-OnBattery](#opendtu-onbattery)
- [What is OpenDTU-OnBattery](#what-is-opendtu-onbattery)
- [History of the project](#history-of-the-project)
- [Highlights of OpenDTU-onBattery](#highlights-of-opendtu-onbattery)
- [Highlights of OpenDTU-OnBattery](#highlights-of-opendtu-onbattery)
- [Documentation](#documentation)
- [Acknowledgment](#acknowledgment)
# OpenDTU-onBattery
# OpenDTU-OnBattery
This is a fork from the Hoymiles project [OpenDTU](https://github.com/tbnobody/OpenDTU).
![GitHub tag (latest SemVer)](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/helgeerbe/68b47cc8c8994d04ab3a4fa9d8aee5e6/raw/openDTUcoreRelease.json)
[![OpenDTU-onBattery Build](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml)
[![OpenDTU-OnBattery Build](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml)
[![cpplint](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/cpplint.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/cpplint.yml)
[![Yarn Linting](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/yarnlint.yml)
## What is OpenDTU-onBattery
## What is OpenDTU-OnBattery
OpenDTU-onBattery is an extension of the original OpenDTU to support battery chargers, battery management systems (BMS) and power meters on a single esp32. With the help of a dynamic power limiter, the power production can be adjusted to the actual consumption. In this way, it is possible to come as close as possible to the goal of zero feed-in.
OpenDTU-OnBattery is an extension of the original OpenDTU to support battery chargers, battery management systems (BMS) and power meters on a single esp32. With the help of a dynamic power limiter, the power production can be adjusted to the actual consumption. In this way, it is possible to come as close as possible to the goal of zero feed-in.
## History of the project
@ -25,7 +25,7 @@ The original OpenDTU project was started from [this](https://www.mikrocontroller
Summer 2022 I bought my Victron MPPT battery charger, and didn't like the idea to set up a separate esp32 to recieve the charger data. I decided to fork OpenDTU and extend it with battery charger support and a dynamic power limitter to my own needs. Hoping someone can make use of it.
## Highlights of OpenDTU-onBattery
## Highlights of OpenDTU-OnBattery
This project is still under development and adds following features:
@ -40,11 +40,11 @@ This project is still under development and adds following features:
## Documentation
[Full documentation of OpenDTU-onBattery extensions can be found at the project's wiki](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki).
Documentation of OpenDTU-OnBattery extensions can be found in [the project's wiki](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki).
For documentation of openDTU core functionality I refer to the original [repo](https://github.com/tbnobody/OpenDTU) and its [documentation](https://tbnobody.github.io/OpenDTU-docs/).
For documentation of OpenDTU core functionality refer to the original [repo](https://github.com/tbnobody/OpenDTU) and its [documentation](https://opendtu.solar).
Please note that openDTU-onBattery may change significantly during its development.
Please note that OpenDTU-OnBattery may change significantly during its development.
Bug reports, comments, feature requests and fixes are most welcome!
To find out what's new or improved have a look at the [changelog](https://github.com/helgeerbe/OpenDTU-OnBattery/releases).
@ -53,4 +53,4 @@ To find out what's new or improved have a look at the [changelog](https://github
A special Thank to Thomas Basler (tbnobody) the author of the original [OpenDTU](https://github.com/tbnobody/OpenDTU) project. You are doing a great job!
Last but not least, I would like to thank all the contributors. With your ideas and enhancements, you have made OpenDTU-onBattery much more than I originally had in mind.
Last but not least, I would like to thank all the contributors. With your ideas and enhancements, you have made OpenDTU-OnBattery much more than I originally had in mind.

View File

@ -6,7 +6,7 @@ This is a fork from the Hoymiles project [OpenDTU](https://github.com/tbnobody/O
> **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.
> 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.
## Features
@ -106,7 +106,7 @@ Some points for consideration are:
#### Operation modes
openDTU-onBattery supports three operation modes for the Huawei PSU:
openDTU-OnBattery supports three operation modes for the Huawei PSU:
1. Fully manual - In this mode the PSU needs to be turned on/off externally using MQTT and voltage and current limits need to be provided. See [MQTT Documentation](docs/MQTT_Topics.md) for details on these commands
2. Manual with auto power on / off - In this mode the PSU is turned on when a current limit > 1A is set. If the current limit is < 1A for some time the PSU is turned off. Current and voltage limits need to be provided externally using MQTT. See [MQTT Documentation](docs/MQTT_Topics.md) for details on these commands.
3. Automatic - In this mode the PSU power is controlled by the Power Meter and information provided in the web-interface. If excess power is present the PSU is turned on. The voltage limit is set as per web-interface and the current limit is set so that the maximum PSU output power equals the Power Meter value. Minium and maximum PSU power levels as configured in the web-interface are respected in this process. The PSU is turned off if the output current is limited and the output power drops below the minium power level. This will disable automatic mode until the battery is discharged below the start voltage level (set in the web-interface). This mode can be enabled using the web-interface and MQTT. See [MQTT Documentation](docs/MQTT_Topics.md)
@ -135,7 +135,7 @@ It was the goal to replace the original Hoymiles DTU (Telemetry Gateway) with th
### Status
[![OpenDTU-onBattery Build](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml)
[![OpenDTU-OnBattery Build](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/build.yml)
[![cpplint](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/cpplint.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/cpplint.yml)
[![Yarn Linting](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/helgeerbe/OpenDTU-OnBattery/actions/workflows/yarnlint.yml)

View File

@ -1,91 +1,3 @@
# Device Profiles
This documentation will has been moved and can be found here: <https://tbnobody.github.io/OpenDTU-docs/firmware/device_profiles/>
## Structure of the json file for openDTU-onBattery (outdated example)
```json
[
{
"name": "Generic NodeMCU 38 pin",
"nrf24": {
"miso": 19,
"mosi": 23,
"clk": 18,
"irq": 16,
"en": 4,
"cs": 5
},
"victron": {
"rx": 22,
"tx": 21
},
"battery": {
"rx": 27,
"tx": 14
},
"huawei": {
"miso": 12,
"mosi": 13,
"clk": 26,
"irq": 25,
"power": 33,
"cs": 15
},
"eth": {
"enabled": false,
"phy_addr": -1,
"power": -1,
"mdc": -1,
"mdio": -1,
"type": -1,
"clk_mode": -1
}
},
{
"name": "Generic NodeMCU 38 pin with SSD1306",
"nrf24": {
"miso": 19,
"mosi": 23,
"clk": 18,
"irq": 16,
"en": 4,
"cs": 5
},
"eth": {
"enabled": false,
"phy_addr": -1,
"power": -1,
"mdc": -1,
"mdio": -1,
"type": -1,
"clk_mode": -1
},
"display": {
"type": 2,
"data": 21,
"clk": 22
}
},
{
"name": "Olimex ESP32-POE",
"nrf24": {
"miso": 15,
"mosi": 2,
"clk": 14,
"irq": 13,
"en": 16,
"cs": 5
},
"eth": {
"enabled": true,
"phy_addr": 0,
"power": 12,
"mdc": 23,
"mdio": 18,
"type": 0,
"clk_mode": 3
}
}
]
```
This documentation has been [moved to the wiki](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki/Device-Profiles).

View File

@ -2,15 +2,28 @@
## Hardware you need
For a highly integrated board with most peripherals already included, have a
look at the [OpenDTU Fusion](https://shop.allianceapps.io/products/opendtu-fusion-community-edition)
board. There are also [OpenDTU-OnBattery-specific addons](https://github.com/markusdd/OpenDTUFusionDocs/blob/main/CANIso.md).
Otherwise, read on to assemble your own components.
### ESP32 board
For ease of use, buy a "ESP32 DEVKIT DOIT" or "ESP32 NodeMCU Development Board" with an ESP32-S3 or ESP-WROOM-32 chipset on it.
For ease of use, buy a "ESP32 DEVKIT DOIT" or "ESP32 NodeMCU Development Board"
with an ESP32-S3 chipset on it, **with at least 8 MB of flash memory**.
Preferrably, the ESP32 should also have some embedded PSRAM. Look out for
labels like "N8R2", "N16R8", where the number after the N is the amount of
flash in Megabytes, and the number after the R is the amount of PSRAM in
Megabytes.
Sample Picture:
![NodeMCU-ESP32](nodemcu-esp32.png)
Also supported: Board with Ethernet-Connector and Power-over-Ethernet [Olimex ESP32-POE](https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware)
Also supported: Board with Ethernet-Connector and Power-over-Ethernet (PoE)
(select version with 8 MB of flash memory or more)
[Olimex ESP32-POE](https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware)
### NRF24L01+ radio board (See inverter table above for supported inverters)
@ -41,7 +54,10 @@ The CMT2300A uses 3-Wire half duplex SPI communication. Due to this fact it curr
### 3.3V / 5V logic level converter
The logic level converter is used to interface with the Victron MPPT charge controller and the relay board. It converts the 3.3V logic level used by the ESP32 to 5V logic used by the other devices.
The logic level converter is used to interface with the Victron MPPT charge
controller and the relay board. It converts the 3.3V logic level used by the
ESP32 to 5V logic used by the other devices. A commonly used digital isolator
is the ADUM1201.
### SN65HVD230 CAN bus transceiver
@ -49,7 +65,7 @@ The SN65HVD230 CAN bus transceiver is used to interface with the Pylontech batte
### MCP2515 CAN bus module
See [Wiki](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki/Huawei-AC-PSU) for details.
Used to connect to the Huawei PSU (AC charger). See [the Wiki](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki/Huawei-AC-PSU) for details.
### Relay module
@ -71,30 +87,8 @@ Use a power supply with 5 V and 1 A. The USB cable connected to your PC/Notebook
### Change pin assignment
Its possible to change all the pins of the NRF24L01+ module, the Display, the LED etc.
The recommend way to change the pin assignment is by creating a custom [device profile](DeviceProfiles.md).
It is also possible to create a custom environment and compile the source yourself. This can be achieved by copying one of the [env:....] sections from 'platformio.ini' to 'platformio_override.ini' and editing the 'platformio_override.ini' file and add/change one or more of the following lines to the 'build_flags' parameter:
```makefile
-DHOYMILES_PIN_MISO=19
-DHOYMILES_PIN_MOSI=23
-DHOYMILES_PIN_SCLK=18
-DHOYMILES_PIN_IRQ=16
-DHOYMILES_PIN_CE=4
-DHOYMILES_PIN_CS=5
-DVICTRON_PIN_TX=21
-DVICTRON_PIN_RX=22
-DPYLONTECH_PIN_RX=27
-DPYLONTECH_PIN_TX=14
-DHUAWEI_PIN_MISO=12
-DHUAWEI_PIN_MOSI=13
-DHUAWEI_PIN_SCLK=26
-DHUAWEI_PIN_IRQ=25
-DHUAWEI_PIN_CS=15
-DHUAWEI_PIN_POWER=33
```
It is recommended to make all changes only in the 'platformio_override.ini', this is your personal copy.
It is possible to change all the pins of the NRF24L01+ module, the Display, the LED etc.
The way to change the pin assignment is by creating a custom [device profile](https://github.com/helgeerbe/OpenDTU-OnBattery/wiki/Device-Profiles).
## Flashing and starting up

View File

@ -14,7 +14,6 @@ public:
virtual void deinit() = 0;
virtual void loop() = 0;
virtual std::shared_ptr<BatteryStats> getStats() const = 0;
virtual bool usesHwPort2() const { return false; }
};
class BatteryClass {

View File

@ -58,6 +58,8 @@ class BatteryStats {
}
String _manufacturer = "unknown";
String _hwversion = "";
String _fwversion = "";
uint32_t _lastUpdate = 0;
private:
@ -157,8 +159,9 @@ class VictronSmartShuntStats : public BatteryStats {
uint32_t _timeToGo;
float _chargedEnergy;
float _dischargedEnergy;
String _modelName;
int32_t _instantaneousPower;
float _midpointVoltage;
float _midpointDeviation;
float _consumedAmpHours;
int32_t _lastFullCharge;

View File

@ -7,6 +7,8 @@
#include "Battery.h"
#include "JkBmsSerialMessage.h"
//#define JKBMS_DUMMY_SERIAL
class DataPointContainer;
namespace JkBms {
@ -19,11 +21,16 @@ class Controller : public BatteryProvider {
void deinit() final;
void loop() final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
bool usesHwPort2() const final {
return ARDUINO_USB_CDC_ON_BOOT != 1;
}
private:
static char constexpr _serialPortOwner[] = "JK BMS";
#ifdef JKBMS_DUMMY_SERIAL
std::unique_ptr<DummySerial> _upSerial;
#else
std::unique_ptr<HardwareSerial> _upSerial;
#endif
enum class Status : unsigned {
Initializing,
Timeout,

View File

@ -45,6 +45,8 @@ struct PinMapping_t {
int8_t victron_rx;
int8_t victron_tx2;
int8_t victron_rx2;
int8_t victron_tx3;
int8_t victron_rx3;
int8_t battery_rx;
int8_t battery_rxen;
int8_t battery_tx;

View File

@ -60,6 +60,8 @@ private:
mutable std::mutex _mutex;
static char constexpr _sdmSerialPortOwner[] = "SDM power meter";
std::unique_ptr<HardwareSerial> _upSdmSerial = nullptr;
std::unique_ptr<SDM> _upSdm = nullptr;
std::unique_ptr<SoftwareSerial> _upSmlSerial = nullptr;

View File

@ -1,27 +1,21 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <array>
#include <optional>
#include <string>
class SerialPortManagerClass {
public:
bool allocateMpptPort(int port);
bool allocateBatteryPort(int port);
void invalidateBatteryPort();
void invalidateMpptPorts();
void init();
std::optional<uint8_t> allocatePort(std::string const& owner);
void freePort(std::string const& owner);
private:
enum Owner {
BATTERY,
MPPT
};
std::map<uint8_t, Owner> allocatedPorts;
bool allocatePort(uint8_t port, Owner owner);
void invalidate(Owner owner);
static const char* print(Owner owner);
// the amount of hardare UARTs available on supported ESP32 chips
static size_t constexpr _num_controllers = 3;
std::array<std::string, _num_controllers> _ports = { "" };
};
extern SerialPortManagerClass SerialPortManager;

View File

@ -55,7 +55,9 @@ private:
using controller_t = std::unique_ptr<VeDirectMpptController>;
std::vector<controller_t> _controllers;
bool initController(int8_t rx, int8_t tx, bool logging, int hwSerialPort);
std::vector<String> _serialPortOwners;
bool initController(int8_t rx, int8_t tx, bool logging,
uint8_t instance);
};
extern VictronMpptClass VictronMppt;

View File

@ -6,14 +6,13 @@
class VictronSmartShunt : public BatteryProvider {
public:
bool init(bool verboseLogging) final;
void deinit() final { }
void deinit() final;
void loop() final;
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
bool usesHwPort2() const final {
return ARDUINO_USB_CDC_ON_BOOT != 1;
}
private:
static char constexpr _serialPortOwner[] = "SmartShunt";
uint32_t _lastUpdate = 0;
std::shared_ptr<VictronSmartShuntStats> _stats =
std::make_shared<VictronSmartShuntStats>();

View File

@ -137,6 +137,51 @@ frozen::string const& veStruct::getPidAsString() const
return getAsString(values, productID_PID);
}
/*
* This function returns the firmware version as an integer, disregarding
* release candidate marks.
*/
uint32_t veStruct::getFwVersionAsInteger() const
{
char const* strVersion = firmwareVer_FW;
// VE.Direct protocol manual states that the first char can be a non-digit,
// in which case that char represents a release candidate version
if (strVersion[0] < '0' || strVersion[0] > '9') { ++strVersion; }
return static_cast<uint32_t>(strtoul(strVersion, nullptr, 10));
}
/*
* This function returns the firmware version as readable text.
*/
String veStruct::getFwVersionFormatted() const
{
char const* strVersion = firmwareVer_FW;
char rc = 0;
// VE.Direct protocol manual states that the first char can be a non-digit,
// in which case that char represents a release candidate version
if (strVersion[0] < '0' || strVersion[0] > '9') {
rc = strVersion[0];
++strVersion;
}
// SmartShunt firmware version is transmitted with leading zero(es)
while (strVersion[0] == '0') { ++strVersion; }
String res(strVersion[0]);
res += ".";
res += strVersion + 1;
if (rc != 0) {
res += "-rc-";
res += rc;
}
return res;
}
/*
* This function returns the state of operations (CS) as readable text.
*/

View File

@ -2,6 +2,7 @@
#include <frozen/string.h>
#include <frozen/map.h>
#include <Arduino.h>
#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0
#define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer
@ -9,12 +10,14 @@
typedef struct {
uint16_t productID_PID = 0; // product id
char serialNr_SER[VE_MAX_VALUE_LEN]; // serial number
char firmwareNr_FW[VE_MAX_VALUE_LEN]; // firmware release number
char firmwareVer_FW[VE_MAX_VALUE_LEN]; // firmware release number
uint32_t batteryVoltage_V_mV = 0; // battery voltage in mV
int32_t batteryCurrent_I_mA = 0; // battery current in mA (can be negative)
float mpptEfficiency_Percent = 0; // efficiency in percent (calculated, moving average)
frozen::string const& getPidAsString() const; // product ID as string
uint32_t getFwVersionAsInteger() const;
String getFwVersionFormatted() const;
} veStruct;
struct veMpptStruct : veStruct {
@ -79,6 +82,8 @@ struct veShuntStruct : veStruct {
int32_t H16; // Maximum auxiliary (battery) voltage
int32_t H17; // Amount of discharged energy
int32_t H18; // Amount of charged energy
int32_t VM; // Mid-point voltage of the battery bank
int32_t DM; // Mid-point deviation of the battery bank
int8_t dcMonitorMode_MON; // DC monitor mode
};

View File

@ -62,7 +62,8 @@ VeDirectFrameHandler<T>::VeDirectFrameHandler() :
}
template<typename T>
void VeDirectFrameHandler<T>::init(char const* who, int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort)
void VeDirectFrameHandler<T>::init(char const* who, int8_t rx, int8_t tx,
Print* msgOut, bool verboseLogging, uint8_t hwSerialPort)
{
_vedirectSerial = std::make_unique<HardwareSerial>(hwSerialPort);
_vedirectSerial->end(); // make sure the UART will be re-initialized
@ -242,12 +243,12 @@ void VeDirectFrameHandler<T>::processTextData(std::string const& name, std::stri
}
if (name == "SER") {
strcpy(_tmpFrame.serialNr_SER, value.c_str());
strncpy(_tmpFrame.serialNr_SER, value.c_str(), sizeof(_tmpFrame.serialNr_SER));
return;
}
if (name == "FW") {
strcpy(_tmpFrame.firmwareNr_FW, value.c_str());
strncpy(_tmpFrame.firmwareVer_FW, value.c_str(), sizeof(_tmpFrame.firmwareVer_FW));
return;
}

View File

@ -30,7 +30,8 @@ public:
protected:
VeDirectFrameHandler();
void init(char const* who, int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort);
void init(char const* who, int8_t rx, int8_t tx, Print* msgOut,
bool verboseLogging, uint8_t hwSerialPort);
virtual bool hexDataHandler(VeDirectHexData const &data) { return false; } // handles the disassembeled hex response
bool _verboseLogging;

View File

@ -12,9 +12,11 @@
//#define PROCESS_NETWORK_STATE
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort)
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut,
bool verboseLogging, uint8_t hwSerialPort)
{
VeDirectFrameHandler::init("MPPT", rx, tx, msgOut, verboseLogging, hwSerialPort);
VeDirectFrameHandler::init("MPPT", rx, tx, msgOut,
verboseLogging, hwSerialPort);
}
bool VeDirectMpptController::processTextDataDerived(std::string const& name, std::string const& value)
@ -110,7 +112,7 @@ void VeDirectMpptController::frameValidEvent() {
// charger periodically sends human readable (TEXT) data to the serial port. For firmware
// versions v1.53 and above, the charger always periodically sends TEXT data to the serial port.
// --> We just use hex commandes for firmware >= 1.53 to keep text messages alive
if (atoi(_tmpFrame.firmwareNr_FW) < 153) { return; }
if (_tmpFrame.getFwVersionAsInteger() < 153) { return; }
using Command = VeDirectHexCommand;
using Register = VeDirectHexRegister;

View File

@ -40,7 +40,8 @@ class VeDirectMpptController : public VeDirectFrameHandler<veMpptStruct> {
public:
VeDirectMpptController() = default;
void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort);
void init(int8_t rx, int8_t tx, Print* msgOut,
bool verboseLogging, uint8_t hwSerialPort);
using data_t = veMpptStruct;

View File

@ -3,10 +3,11 @@
VeDirectShuntController VeDirectShunt;
void VeDirectShuntController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging)
void VeDirectShuntController::init(int8_t rx, int8_t tx, Print* msgOut,
bool verboseLogging, uint8_t hwSerialPort)
{
VeDirectFrameHandler::init("SmartShunt", rx, tx, msgOut, verboseLogging,
((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0));
VeDirectFrameHandler::init("SmartShunt", rx, tx, msgOut,
verboseLogging, hwSerialPort);
}
bool VeDirectShuntController::processTextDataDerived(std::string const& name, std::string const& value)
@ -108,6 +109,14 @@ bool VeDirectShuntController::processTextDataDerived(std::string const& name, st
_tmpFrame.H17 = atoi(value.c_str());
return true;
}
if (name == "VM") {
_tmpFrame.VM = atoi(value.c_str());
return true;
}
if (name == "DM") {
_tmpFrame.DM = atoi(value.c_str());
return true;
}
if (name == "H18") {
_tmpFrame.H18 = atoi(value.c_str());
return true;

View File

@ -8,7 +8,8 @@ class VeDirectShuntController : public VeDirectFrameHandler<veShuntStruct> {
public:
VeDirectShuntController() = default;
void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging);
void init(int8_t rx, int8_t tx, Print* msgOut,
bool verboseLogging, uint8_t hwSerialPort);
using data_t = veShuntStruct;

View File

@ -21,7 +21,7 @@ Import("env")
platform = env.PioPlatform()
import sys
from os.path import join
from os.path import join, getsize
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
@ -60,6 +60,14 @@ def esp32_create_combined_bin(source, target, env):
flash_size,
]
# platformio estimates the amount of flash used to store the firmware. this
# estimate is not accurate. we perform a final check on the firmware bin
# size by comparing it against the respective partition size.
max_size = env.BoardConfig().get("upload.maximum_size", 1)
fw_size = getsize(firmware_name)
if (fw_size > max_size):
raise Exception("firmware binary too large: %d > %d" % (fw_size, max_size))
print(" Offset | File")
for section in sections:
sect_adr, sect_file = section.split(" ", 1)

View File

@ -5,7 +5,6 @@
#include "JkBmsController.h"
#include "VictronSmartShunt.h"
#include "MqttBattery.h"
#include "SerialPortManager.h"
BatteryClass Battery;
@ -39,7 +38,6 @@ void BatteryClass::updateSettings()
_upProvider->deinit();
_upProvider = nullptr;
}
SerialPortManager.invalidateBatteryPort();
CONFIG_T& config = Configuration.get();
if (!config.Battery.Enabled) { return; }
@ -60,22 +58,11 @@ void BatteryClass::updateSettings()
_upProvider = std::make_unique<VictronSmartShunt>();
break;
default:
MessageOutput.printf("Unknown battery provider: %d\r\n", config.Battery.Provider);
MessageOutput.printf("[Battery] Unknown provider: %d\r\n", config.Battery.Provider);
return;
}
if(_upProvider->usesHwPort2()) {
if (!SerialPortManager.allocateBatteryPort(2)) {
MessageOutput.printf("[Battery] Serial port %d already in use. Initialization aborted!\r\n", 2);
_upProvider = nullptr;
return;
}
}
if (!_upProvider->init(verboseLogging)) {
SerialPortManager.invalidateBatteryPort();
_upProvider = nullptr;
}
if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; }
}
void BatteryClass::loop()

View File

@ -62,6 +62,12 @@ bool BatteryStats::updateAvailable(uint32_t since) const
void BatteryStats::getLiveViewData(JsonVariant& root) const
{
root["manufacturer"] = _manufacturer;
if (!_fwversion.isEmpty()) {
root["fwversion"] = _fwversion;
}
if (!_hwversion.isEmpty()) {
root["hwversion"] = _hwversion;
}
root["data_age"] = getAgeSeconds();
addLiveViewValue(root, "SoC", _soc, "%", _socPrecision);
@ -375,22 +381,42 @@ void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp)
_cellVoltageTimestamp = millis();
}
auto oVersion = _dataPoints.get<Label::BmsSoftwareVersion>();
if (oVersion.has_value()) {
// raw: "11.XW_S11.262H_"
// => Hardware "V11.XW" (displayed in Android app)
// => Software "V11.262H" (displayed in Android app)
auto first = oVersion->find('_');
if (first != std::string::npos) {
_hwversion = oVersion->substr(0, first).c_str();
auto second = oVersion->find('_', first + 1);
// the 'S' seems to be merely an indicator for "software"?
if (oVersion->at(first + 1) == 'S') { first++; }
_fwversion = oVersion->substr(first + 1, second - first - 1).c_str();
}
}
_lastUpdate = millis();
}
void VictronSmartShuntStats::updateFrom(VeDirectShuntController::data_t const& shuntData) {
BatteryStats::setVoltage(shuntData.batteryVoltage_V_mV / 1000.0, millis());
BatteryStats::setSoC(static_cast<float>(shuntData.SOC) / 10, 1/*precision*/, millis());
_fwversion = shuntData.getFwVersionFormatted();
_current = static_cast<float>(shuntData.batteryCurrent_I_mA) / 1000;
_modelName = shuntData.getPidAsString().data();
_chargeCycles = shuntData.H4;
_timeToGo = shuntData.TTG / 60;
_chargedEnergy = static_cast<float>(shuntData.H18) / 100;
_dischargedEnergy = static_cast<float>(shuntData.H17) / 100;
_manufacturer = "Victron " + _modelName;
_manufacturer = String("Victron ") + shuntData.getPidAsString().data();
_temperature = shuntData.T;
_tempPresent = shuntData.tempPresent;
_midpointVoltage = static_cast<float>(shuntData.VM) / 1000;
_midpointDeviation = static_cast<float>(shuntData.DM) / 10;
_instantaneousPower = shuntData.P;
_consumedAmpHours = static_cast<float>(shuntData.CE) / 1000;
_lastFullCharge = shuntData.H9 / 60;
@ -414,6 +440,8 @@ void VictronSmartShuntStats::getLiveViewData(JsonVariant& root) const {
addLiveViewValue(root, "dischargedEnergy", _dischargedEnergy, "kWh", 2);
addLiveViewValue(root, "instantaneousPower", _instantaneousPower, "W", 0);
addLiveViewValue(root, "consumedAmpHours", _consumedAmpHours, "Ah", 3);
addLiveViewValue(root, "midpointVoltage", _midpointVoltage, "V", 2);
addLiveViewValue(root, "midpointDeviation", _midpointDeviation, "%", 1);
addLiveViewValue(root, "lastFullCharge", _lastFullCharge, "min", 0);
if (_tempPresent) {
addLiveViewValue(root, "temperature", _temperature, "°C", 0);
@ -436,4 +464,6 @@ void VictronSmartShuntStats::mqttPublish() const {
MqttSettings.publish("battery/instantaneousPower", String(_instantaneousPower));
MqttSettings.publish("battery/consumedAmpHours", String(_consumedAmpHours));
MqttSettings.publish("battery/lastFullCharge", String(_lastFullCharge));
MqttSettings.publish("battery/midpointVoltage", String(_midpointVoltage));
MqttSettings.publish("battery/midpointDeviation", String(_midpointDeviation));
}

View File

@ -389,8 +389,10 @@ void HuaweiCanClass::loop()
MessageOutput.printf("[HuaweiCanClass::loop] newPowerLimit: %f, output_power: %f \r\n", newPowerLimit, _rp.output_power);
}
// Check whether the battery SoC limit setting is enabled
if (config.Battery.Enabled && config.Huawei.Auto_Power_BatterySoC_Limits_Enabled) {
uint8_t _batterySoC = Battery.getStats()->getSoC();
// Sets power limit to 0 if the BMS reported SoC reaches or exceeds the user configured value
if (_batterySoC >= config.Huawei.Auto_Power_Stop_BatterySoC_Threshold) {
newPowerLimit = 0;
if (verboseLogging) {
@ -427,7 +429,7 @@ void HuaweiCanClass::loop()
float calculatedCurrent = efficiency * (newPowerLimit / _rp.output_voltage);
// Limit output current to value requested by BMS
float permissableCurrent = stats->getChargeCurrentLimitation() - (stats->getChargeCurrent() - _rp.output_current); // BMS current limit - current from other sources
float permissableCurrent = stats->getChargeCurrentLimitation() - (stats->getChargeCurrent() - _rp.output_current); // BMS current limit - current from other sources, e.g. Victron MPPT charger
float outputCurrent = std::min(calculatedCurrent, permissableCurrent);
outputCurrent= outputCurrent > 0 ? outputCurrent : 0;

View File

@ -5,9 +5,10 @@
#include "MessageOutput.h"
#include "JkBmsDataPoints.h"
#include "JkBmsController.h"
#include "SerialPortManager.h"
#include <frozen/map.h>
//#define JKBMS_DUMMY_SERIAL
namespace JkBms {
#ifdef JKBMS_DUMMY_SERIAL
class DummySerial {
@ -196,13 +197,8 @@ class DummySerial {
size_t _msg_idx = 0;
size_t _byte_idx = 0;
};
DummySerial HwSerial;
#else
HardwareSerial HwSerial((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0);
#endif
namespace JkBms {
bool Controller::init(bool verboseLogging)
{
_verboseLogging = verboseLogging;
@ -220,9 +216,18 @@ bool Controller::init(bool verboseLogging)
return false;
}
HwSerial.end(); // make sure the UART will be re-initialized
HwSerial.begin(115200, SERIAL_8N1, pin.battery_rx, pin.battery_tx);
HwSerial.flush();
#ifdef JKBMS_DUMMY_SERIAL
_upSerial = std::make_unique<DummySerial>();
#else
auto oHwSerialPort = SerialPortManager.allocatePort(_serialPortOwner);
if (!oHwSerialPort) { return false; }
_upSerial = std::make_unique<HardwareSerial>(*oHwSerialPort);
#endif
_upSerial->end(); // make sure the UART will be re-initialized
_upSerial->begin(115200, SERIAL_8N1, pin.battery_rx, pin.battery_tx);
_upSerial->flush();
if (Interface::Transceiver != getInterface()) { return true; }
@ -242,10 +247,12 @@ bool Controller::init(bool verboseLogging)
void Controller::deinit()
{
HwSerial.end();
_upSerial->end();
if (_rxEnablePin > 0) { pinMode(_rxEnablePin, INPUT); }
if (_txEnablePin > 0) { pinMode(_txEnablePin, INPUT); }
SerialPortManager.freePort(_serialPortOwner);
}
Controller::Interface Controller::getInterface() const
@ -296,7 +303,7 @@ void Controller::sendRequest(uint8_t pollInterval)
return announceStatus(Status::WaitingForPollInterval);
}
if (!HwSerial.availableForWrite()) {
if (!_upSerial->availableForWrite()) {
return announceStatus(Status::HwSerialNotAvailableForWrite);
}
@ -307,10 +314,10 @@ void Controller::sendRequest(uint8_t pollInterval)
digitalWrite(_txEnablePin, HIGH); // enable transmission
}
HwSerial.write(readAll.data(), readAll.size());
_upSerial->write(readAll.data(), readAll.size());
if (Interface::Transceiver == getInterface()) {
HwSerial.flush();
_upSerial->flush();
digitalWrite(_rxEnablePin, LOW); // enable reception
digitalWrite(_txEnablePin, LOW); // disable transmission (free the bus)
}
@ -326,8 +333,8 @@ void Controller::loop()
CONFIG_T& config = Configuration.get();
uint8_t pollInterval = config.Battery.JkBmsPollingInterval;
while (HwSerial.available()) {
rxData(HwSerial.read());
while (_upSerial->available()) {
rxData(_upSerial->read());
}
sendRequest(pollInterval);

View File

@ -92,10 +92,10 @@ void MqttHandleVedirectHassClass::publishConfig()
publishSensor("VE.Smart network total DC input power", "mdi:solar-power", "NetworkTotalDcInputPower", "power", "measurement", "W", *optMpptData);
}
if (optMpptData->MpptTemperatureMilliCelsius.first != 0) {
publishSensor("MPPT temperature", "mdi:temperature-celsius", "MpptTemperature", "temperature", "measurement", "W", *optMpptData);
publishSensor("MPPT temperature", "mdi:temperature-celsius", "MpptTemperature", "temperature", "measurement", "°C", *optMpptData);
}
if (optMpptData->SmartBatterySenseTemperatureMilliCelsius.first != 0) {
publishSensor("Smart Battery Sense temperature", "mdi:temperature-celsius", "SmartBatterySenseTemperature", "temperature", "measurement", "W", *optMpptData);
publishSensor("Smart Battery Sense temperature", "mdi:temperature-celsius", "SmartBatterySenseTemperature", "temperature", "measurement", "°C", *optMpptData);
}
}

View File

@ -87,6 +87,8 @@ void MqttHandleBatteryHassClass::loop()
publishSensor("Current", "mdi:current-dc", "BatteryCurrentMilliAmps", "current", "measurement", "mA");
publishSensor("BMS Temperature", "mdi:thermometer", "BmsTempCelsius", "temperature", "measurement", "°C");
publishSensor("Cell Voltage Diff", "mdi:battery-alert", "CellDiffMilliVolt", "voltage", "measurement", "mV");
publishSensor("Battery Temperature 1", "mdi:thermometer", "BatteryTempOneCelsius", "temperature", "measurement", "°C");
publishSensor("Battery Temperature 2", "mdi:thermometer", "BatteryTempTwoCelsius", "temperature", "measurement", "°C");
publishSensor("Charge Cycles", "mdi:counter", "BatteryCycles");
publishSensor("Cycle Capacity", "mdi:battery-sync", "BatteryCycleCapacity");
@ -120,6 +122,8 @@ void MqttHandleBatteryHassClass::loop()
publishSensor("Charge Cycles", "mdi:counter", "chargeCycles");
publishSensor("Consumed Amp Hours", NULL, "consumedAmpHours", NULL, "measurement", "Ah");
publishSensor("Last Full Charge", "mdi:timelapse", "lastFullCharge", NULL, NULL, "min");
publishSensor("Midpoint Voltage", NULL, "midpointVoltage", "voltage", "measurement", "V");
publishSensor("Midpoint Deviation", NULL, "midpointDeviation", "battery", "measurement", "%");
break;
}

View File

@ -110,7 +110,7 @@ void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::da
PUBLISH(productID_PID, "PID", currentData.getPidAsString().data());
PUBLISH(serialNr_SER, "SER", currentData.serialNr_SER);
PUBLISH(firmwareNr_FW, "FW", currentData.firmwareNr_FW);
PUBLISH(firmwareVer_FW, "FW", currentData.firmwareVer_FW);
PUBLISH(loadOutputState_LOAD, "LOAD", (currentData.loadOutputState_LOAD ? "ON" : "OFF"));
PUBLISH(currentState_CS, "CS", currentData.getCsAsString().data());
PUBLISH(errorCode_ERR, "ERR", currentData.getErrAsString().data());

View File

@ -100,6 +100,14 @@
#define VICTRON_PIN_RX2 -1
#endif
#ifndef VICTRON_PIN_TX3
#define VICTRON_PIN_TX3 -1
#endif
#ifndef VICTRON_PIN_RX3
#define VICTRON_PIN_RX3 -1
#endif
#ifndef BATTERY_PIN_RX
#define BATTERY_PIN_RX -1
#endif
@ -207,8 +215,11 @@ PinMappingClass::PinMappingClass()
_pinMapping.victron_rx = VICTRON_PIN_RX;
_pinMapping.victron_tx = VICTRON_PIN_TX;
_pinMapping.victron_rx2 = VICTRON_PIN_RX;
_pinMapping.victron_tx2 = VICTRON_PIN_TX;
_pinMapping.victron_rx2 = VICTRON_PIN_RX2;
_pinMapping.victron_tx2 = VICTRON_PIN_TX2;
_pinMapping.victron_rx3 = VICTRON_PIN_RX3;
_pinMapping.victron_tx3 = VICTRON_PIN_TX3;
_pinMapping.battery_rx = BATTERY_PIN_RX;
_pinMapping.battery_rxen = BATTERY_PIN_RXEN;
@ -292,6 +303,8 @@ bool PinMappingClass::init(const String& deviceMapping)
_pinMapping.victron_tx = doc[i]["victron"]["tx"] | VICTRON_PIN_TX;
_pinMapping.victron_rx2 = doc[i]["victron"]["rx2"] | VICTRON_PIN_RX2;
_pinMapping.victron_tx2 = doc[i]["victron"]["tx2"] | VICTRON_PIN_TX2;
_pinMapping.victron_rx3 = doc[i]["victron"]["rx3"] | VICTRON_PIN_RX3;
_pinMapping.victron_tx3 = doc[i]["victron"]["tx3"] | VICTRON_PIN_TX3;
_pinMapping.battery_rx = doc[i]["battery"]["rx"] | BATTERY_PIN_RX;
_pinMapping.battery_rxen = doc[i]["battery"]["rxen"] | BATTERY_PIN_RXEN;

View File

@ -9,6 +9,7 @@
#include "MqttSettings.h"
#include "NetworkSettings.h"
#include "MessageOutput.h"
#include "SerialPortManager.h"
#include <ctime>
#include <SMA_HM.h>
@ -57,16 +58,22 @@ void PowerMeterClass::init(Scheduler& scheduler)
}
case Source::SDM1PH:
case Source::SDM3PH:
case Source::SDM3PH: {
if (pin.powermeter_rx < 0 || pin.powermeter_tx < 0) {
MessageOutput.println("[PowerMeter] invalid pin config for SDM power meter (RX and TX pins must be defined)");
return;
}
_upSdm = std::make_unique<SDM>(Serial2, 9600, pin.powermeter_dere,
auto oHwSerialPort = SerialPortManager.allocatePort(_sdmSerialPortOwner);
if (!oHwSerialPort) { return; }
_upSdmSerial = std::make_unique<HardwareSerial>(*oHwSerialPort);
_upSdmSerial->end(); // make sure the UART will be re-initialized
_upSdm = std::make_unique<SDM>(*_upSdmSerial, 9600, pin.powermeter_dere,
SERIAL_8N1, pin.powermeter_rx, pin.powermeter_tx);
_upSdm->begin();
break;
}
case Source::HTTP:
HttpPowerMeter.init();

View File

@ -2,59 +2,46 @@
#include "SerialPortManager.h"
#include "MessageOutput.h"
#define MAX_CONTROLLERS 3
SerialPortManagerClass SerialPortManager;
bool SerialPortManagerClass::allocateBatteryPort(int port)
void SerialPortManagerClass::init()
{
return allocatePort(port, Owner::BATTERY);
}
bool SerialPortManagerClass::allocateMpptPort(int port)
{
return allocatePort(port, Owner::MPPT);
}
bool SerialPortManagerClass::allocatePort(uint8_t port, Owner owner)
{
if (port >= MAX_CONTROLLERS) {
MessageOutput.printf("[SerialPortManager] Invalid serial port = %d \r\n", port);
return false;
}
return allocatedPorts.insert({port, owner}).second;
}
void SerialPortManagerClass::invalidateBatteryPort()
{
invalidate(Owner::BATTERY);
}
void SerialPortManagerClass::invalidateMpptPorts()
{
invalidate(Owner::MPPT);
}
void SerialPortManagerClass::invalidate(Owner owner)
{
for (auto it = allocatedPorts.begin(); it != allocatedPorts.end();) {
if (it->second == owner) {
MessageOutput.printf("[SerialPortManager] Removing port = %d, owner = %s \r\n", it->first, print(owner));
it = allocatedPorts.erase(it);
} else {
++it;
}
if (ARDUINO_USB_CDC_ON_BOOT != 1) {
_ports[0] = "Serial Console";
MessageOutput.println("[SerialPortManager] HW UART port 0 now in use "
"by 'Serial Console'");
}
}
const char* SerialPortManagerClass::print(Owner owner)
std::optional<uint8_t> SerialPortManagerClass::allocatePort(std::string const& owner)
{
switch (owner) {
case BATTERY:
return "BATTERY";
case MPPT:
return "MPPT";
for (size_t i = 0; i < _ports.size(); ++i) {
if (_ports[i] != "") {
MessageOutput.printf("[SerialPortManager] HW UART %d already "
"in use by '%s'\r\n", i, _ports[i].c_str());
continue;
}
_ports[i] = owner;
MessageOutput.printf("[SerialPortManager] HW UART %d now in use "
"by '%s'\r\n", i, owner.c_str());
return i;
}
MessageOutput.printf("[SerialPortManager] Cannot assign another HW "
"UART port to '%s'\r\n", owner.c_str());
return std::nullopt;
}
void SerialPortManagerClass::freePort(std::string const& owner)
{
for (size_t i = 0; i < _ports.size(); ++i) {
if (_ports[i] != owner) { continue; }
MessageOutput.printf("[SerialPortManager] Freeing HW UART %d, owner "
"was '%s'\r\n", i, owner.c_str());
_ports[i] = "";
}
return "unknown";
}

View File

@ -22,39 +22,46 @@ void VictronMpptClass::updateSettings()
std::lock_guard<std::mutex> lock(_mutex);
_controllers.clear();
SerialPortManager.invalidateMpptPorts();
for (auto const& o: _serialPortOwners) {
SerialPortManager.freePort(o.c_str());
}
_serialPortOwners.clear();
CONFIG_T& config = Configuration.get();
if (!config.Vedirect.Enabled) { return; }
const PinMapping_t& pin = PinMapping.get();
int hwSerialPort = 1;
bool initSuccess = initController(pin.victron_rx, pin.victron_tx, config.Vedirect.VerboseLogging, hwSerialPort);
if (initSuccess) {
hwSerialPort++;
initController(pin.victron_rx, pin.victron_tx,
config.Vedirect.VerboseLogging, 1);
initController(pin.victron_rx2, pin.victron_tx2,
config.Vedirect.VerboseLogging, 2);
initController(pin.victron_rx3, pin.victron_tx3,
config.Vedirect.VerboseLogging, 3);
}
initController(pin.victron_rx2, pin.victron_tx2, config.Vedirect.VerboseLogging, hwSerialPort);
}
bool VictronMpptClass::initController(int8_t rx, int8_t tx, bool logging, int hwSerialPort)
bool VictronMpptClass::initController(int8_t rx, int8_t tx, bool logging,
uint8_t instance)
{
MessageOutput.printf("[VictronMppt] rx = %d, tx = %d, hwSerialPort = %d\r\n", rx, tx, hwSerialPort);
MessageOutput.printf("[VictronMppt Instance %d] rx = %d, tx = %d\r\n",
instance, rx, tx);
if (rx < 0) {
MessageOutput.printf("[VictronMppt] invalid pin config rx = %d, tx = %d\r\n", rx, tx);
MessageOutput.printf("[VictronMppt Instance %d] invalid pin config\r\n", instance);
return false;
}
if (!SerialPortManager.allocateMpptPort(hwSerialPort)) {
MessageOutput.printf("[VictronMppt] Serial port %d already in use. Initialization aborted!\r\n",
hwSerialPort);
return false;
}
String owner("Victron MPPT ");
owner += String(instance);
auto oHwSerialPort = SerialPortManager.allocatePort(owner.c_str());
if (!oHwSerialPort) { return false; }
_serialPortOwners.push_back(owner);
auto upController = std::make_unique<VeDirectMpptController>();
upController->init(rx, tx, &MessageOutput, logging, hwSerialPort);
upController->init(rx, tx, &MessageOutput, logging, *oHwSerialPort);
_controllers.push_back(std::move(upController));
return true;
}

View File

@ -3,7 +3,12 @@
#include "Configuration.h"
#include "PinMapping.h"
#include "MessageOutput.h"
#include "SerialPortManager.h"
void VictronSmartShunt::deinit()
{
SerialPortManager.freePort(_serialPortOwner);
}
bool VictronSmartShunt::init(bool verboseLogging)
{
@ -21,7 +26,10 @@ bool VictronSmartShunt::init(bool verboseLogging)
auto tx = static_cast<gpio_num_t>(pin.battery_tx);
auto rx = static_cast<gpio_num_t>(pin.battery_rx);
VeDirectShunt.init(rx, tx, &MessageOutput, verboseLogging);
auto oHwSerialPort = SerialPortManager.allocatePort(_serialPortOwner);
if (!oHwSerialPort) { return false; }
VeDirectShunt.init(rx, tx, &MessageOutput, verboseLogging, *oHwSerialPort);
return true;
}

View File

@ -91,6 +91,8 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
victronPinObj["tx"] = pin.victron_tx;
victronPinObj["rx2"] = pin.victron_rx2;
victronPinObj["tx2"] = pin.victron_tx2;
victronPinObj["rx3"] = pin.victron_rx3;
victronPinObj["tx3"] = pin.victron_tx3;
auto batteryPinObj = curPin["battery"].to<JsonObject>();
batteryPinObj["rx"] = pin.battery_rx;

View File

@ -48,6 +48,7 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
root["chiprevision"] = ESP.getChipRevision();
root["chipmodel"] = ESP.getChipModel();
root["chipcores"] = ESP.getChipCores();
root["flashsize"] = ESP.getFlashChipSize();
String reason;
reason = ResetReason::get_reset_reason_verbose(0);

View File

@ -139,7 +139,7 @@ void WebApiWsVedirectLiveClass::generateCommonJsonResponse(JsonVariant& root, bo
void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDirectMpptController::data_t &mpptData) {
root["product_id"] = mpptData.getPidAsString();
root["firmware_version"] = String(mpptData.firmwareNr_FW);
root["firmware_version"] = mpptData.getFwVersionFormatted();
const JsonObject values = root["values"].to<JsonObject>();

View File

@ -8,6 +8,7 @@
#include "InverterSettings.h"
#include "Led_Single.h"
#include "MessageOutput.h"
#include "SerialPortManager.h"
#include "VictronMppt.h"
#include "Battery.h"
#include "Huawei_can.h"
@ -96,6 +97,8 @@ void setup()
const auto& pin = PinMapping.get();
MessageOutput.println("done");
SerialPortManager.init();
// Initialize WiFi
MessageOutput.print("Initialize Network... ");
NetworkSettings.init(scheduler);

View File

@ -7,7 +7,7 @@
<link rel="apple-touch-icon" href="/favicon.png">
<link rel="manifest" href='/site.webmanifest' />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenDTU-onBattery</title>
<title>OpenDTU-OnBattery</title>
</head>
<body>
<noscript>

View File

@ -18,6 +18,12 @@
<div style="padding-right: 2em;">
{{ $t('battery.battery') }}: {{ batteryData.manufacturer }}
</div>
<div style="padding-right: 2em;" v-if="'fwversion' in batteryData">
{{ $t('battery.FwVersion') }}: {{ batteryData.fwversion }}
</div>
<div style="padding-right: 2em;" v-if="'hwversion' in batteryData">
{{ $t('battery.HwVersion') }}: {{ batteryData.hwversion }}
</div>
<div style="padding-right: 2em;">
{{ $t('battery.DataAge') }} {{ $t('battery.Seconds', { 'val': batteryData.data_age }) }}
</div>

View File

@ -19,6 +19,13 @@
<th>{{ $t('hardwareinfo.CpuFrequency') }}</th>
<td>{{ systemStatus.cpufreq }} {{ $t('hardwareinfo.Mhz') }}</td>
</tr>
<tr>
<th>{{ $t('hardwareinfo.FlashSize') }}</th>
<td>
{{ systemStatus.flashsize }} {{ $t('hardwareinfo.Bytes') }}
({{ systemStatus.flashsize / 1024 / 1024 }} {{ $t('hardwareinfo.MegaBytes') }})
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -9,7 +9,7 @@
<BIconSun v-else width="30" height="30" class="d-inline-block align-text-top text-warning" />
<span style="margin-left: .5rem">
OpenDTU-onBattery
OpenDTU-OnBattery
</span>
<span class="text-info">
<BIconBatteryCharging width="20" height="20" class="d-inline-block align-text-center" />

View File

@ -21,13 +21,13 @@
{{ item.product_id }}
</div>
<div style="padding-right: 2em;">
{{ $t('vedirecthome.SerialNumber') }} {{ serial }}
{{ $t('vedirecthome.SerialNumber') }}: {{ serial }}
</div>
<div style="padding-right: 2em;">
{{ $t('vedirecthome.FirmwareNumber') }} {{ item.firmware_version }}
{{ $t('vedirecthome.FirmwareVersion') }}: {{ item.firmware_version }}
</div>
<div style="padding-right: 2em;">
{{ $t('vedirecthome.DataAge') }} {{ $t('vedirecthome.Seconds', {'val': Math.floor(item.data_age_ms / 1000)}) }}
{{ $t('vedirecthome.DataAge') }}: {{ $t('vedirecthome.Seconds', {'val': Math.floor(item.data_age_ms / 1000)}) }}
</div>
</div>
</div>

View File

@ -152,9 +152,9 @@
"LoadingInverter": "Warte auf Daten... (kann bis zu 10 Sekunden dauern)"
},
"vedirecthome": {
"SerialNumber": "Seriennummer: ",
"FirmwareNumber": "Firmware Version: ",
"DataAge": "letzte Aktualisierung: ",
"SerialNumber": "Seriennummer",
"FirmwareVersion": "Firmware-Version",
"DataAge": "letzte Aktualisierung",
"Seconds": "vor {val} Sekunden",
"Property": "Eigenschaft",
"Value": "Wert",
@ -250,7 +250,10 @@
"ChipRevision": "Chip-Revision",
"ChipCores": "Chip-Kerne",
"CpuFrequency": "CPU-Frequenz",
"Mhz": "MHz"
"Mhz": "MHz",
"FlashSize": "Flash-Speichergröße",
"Bytes": "Bytes",
"MegaBytes": "MB"
},
"memoryinfo": {
"MemoryInformation": "Speicherinformationen",
@ -569,7 +572,10 @@
"sdmaddress": "Modbus Adresse",
"HTTP": "HTTP(S) + JSON - Allgemeine Konfiguration",
"httpIndividualRequests": "Individuelle HTTP requests pro Phase",
"httpUrlDescription": "Die URL muss mit http:// oder https:// beginnen. Manche Zeichen wie Leerzeichen und = müssen mit URL-Kodierung kodiert werden (%xx). Achtung: Ein Überprüfung von SSL Server Zertifikaten ist nicht implementiert (MITM-Attacken sind möglich)! Beispiele gibt es unten.",
"urlExamplesHeading": "Beispiele für URLs",
"jsonPathExamplesHeading": "Beispiele für JSON Pfade",
"jsonPathExamplesExplanation": "Die folgenden Pfade finden jeweils den Wert '123.4' im jeweiligen Beispiel-JSON.",
"httpUrlDescription": "Die URL muss mit http:// oder https:// beginnen. Manche Zeichen wie Leerzeichen und = müssen mit URL-Kodierung kodiert werden (%xx). Achtung: Ein Überprüfung von SSL Server Zertifikaten ist nicht implementiert (MITM-Attacken sind möglich)!.",
"httpPhase": "HTTP(S) + JSON Konfiguration - Phase {phaseNumber}",
"httpEnabled": "Phase aktiviert",
"httpUrl": "URL",
@ -628,7 +634,8 @@
"VoltageSolarPassthroughStopThreshold": "Full-Solar-Passthrough Stop-Schwellwert",
"VoltageLoadCorrectionFactor": "Lastkorrekturfaktor",
"BatterySocInfo": "<b>Hinweis:</b> Die Akku SoC (State of Charge) Werte werden nur benutzt, wenn die Batterie-Kommunikationsschnittstelle innerhalb der letzten Minute gültige Werte geschickt hat. Andernfalls werden als Fallback-Option die Spannungseinstellungen verwendet.",
"InverterIsBehindPowerMeter": "Stromzählermessung beinhaltet Wechselrichterleistung",
"InverterIsBehindPowerMeter": "Stromzählermessung beinhaltet Wechselrichter",
"InverterIsBehindPowerMeterHint": "Aktivieren falls sich der Stromzähler-Messwert um die Ausgangsleistung des Wechselrichters verringert, wenn dieser Strom produziert. Normalerweise ist das zutreffend.",
"InverterIsSolarPowered": "Wechselrichter wird von Solarmodulen gespeist",
"VoltageThresholds": "Batterie Spannungs-Schwellwerte ",
"VoltageLoadCorrectionInfo": "<b>Hinweis:</b> Wenn Leistung von der Batterie abgegeben wird, bricht ihre Spannung etwas ein. Der Spannungseinbruch skaliert mit dem Entladestrom. Damit nicht vorzeitig der Wechselrichter ausgeschaltet wird sobald der Stop-Schwellenwert unterschritten wurde, wird der hier angegebene Korrekturfaktor mit einberechnet um die Spannung zu errechnen die der Akku in Ruhe hätte. Korrigierte Spannung = DC Spannung + (Aktuelle Leistung (W) * Korrekturfaktor).",
@ -867,6 +874,8 @@
},
"battery": {
"battery": "Batterie",
"FwVersion": "Firmware-Version",
"HwVersion": "Hardware-Version",
"DataAge": "letzte Aktualisierung: ",
"Seconds": "vor {val} Sekunden",
"status": "Status",
@ -935,6 +944,8 @@
"dischargedEnergy": "Entladene Energie",
"instantaneousPower": "Aktuelle Leistung",
"consumedAmpHours": "Verbrauchte Amperestunden",
"midpointVoltage": "Mittelpunktspannung",
"midpointDeviation": "Mittelpunktsabweichung",
"lastFullCharge": "Letztes mal Vollgeladen"
}
}

View File

@ -152,9 +152,9 @@
"LoadingInverter": "Waiting for data... (can take up to 10 seconds)"
},
"vedirecthome": {
"SerialNumber": "Serial Number: ",
"FirmwareNumber": "Firmware Number: ",
"DataAge": "Data Age: ",
"SerialNumber": "Serial Number",
"FirmwareVersion": "Firmware Version",
"DataAge": "Data Age",
"Seconds": "{val} seconds",
"Property": "Property",
"Value": "Value",
@ -251,7 +251,10 @@
"ChipRevision": "Chip Revision",
"ChipCores": "Chip Cores",
"CpuFrequency": "CPU Frequency",
"Mhz": "MHz"
"Mhz": "MHz",
"FlashSize": "Flash Memory Size",
"Bytes": "Bytes",
"MegaBytes": "MB"
},
"memoryinfo": {
"MemoryInformation": "Memory Information",
@ -571,10 +574,13 @@
"sdmaddress": "Modbus Address",
"HTTP": "HTTP(S) + Json - General configuration",
"httpIndividualRequests": "Individual HTTP requests per phase",
"urlExamplesHeading": "URL Examples",
"jsonPathExamplesHeading": "JSON Path Examples",
"jsonPathExamplesExplanation": "The following paths each find the value '123.4' in the respective example JSON.",
"httpPhase": "HTTP(S) + Json configuration - Phase {phaseNumber}",
"httpEnabled": "Phase enabled",
"httpUrl": "URL",
"httpUrlDescription": "URL must start with http:// or https://. Some characters like spaces and = have to be encoded with URL encoding (%xx). Warning: SSL server certificate check is not implemented (MITM attacks are possible)! See below for some examples.",
"httpUrlDescription": "URL must start with http:// or https://. Some characters like spaces and = have to be encoded with URL encoding (%xx). Warning: SSL server certificate check is not implemented (MITM attacks are possible)!",
"httpAuthorization": "Authorization Type",
"httpUsername": "Username",
"httpPassword": "Password",
@ -635,6 +641,7 @@
"VoltageLoadCorrectionFactor": "Load correction factor",
"BatterySocInfo": "<b>Hint:</b> The battery SoC (State of Charge) values are only used if the battery communication interface reported SoC updates in the last minute. Otherwise the voltage thresholds will be used as fallback.",
"InverterIsBehindPowerMeter": "PowerMeter reading includes inverter output",
"InverterIsBehindPowerMeterHint": "Enable this option if the power meter reading is reduced by the inverter's output when it produces power. This is typically true.",
"InverterIsSolarPowered": "Inverter is powered by solar modules",
"VoltageThresholds": "Battery Voltage Thresholds",
"VoltageLoadCorrectionInfo": "<b>Hint:</b> When the battery is discharged, its voltage drops. The voltage drop scales with the discharge current. In order to not stop the inverter too early (stop threshold), this load correction factor can be specified to calculate the battery voltage if it was idle. Corrected voltage = DC Voltage + (Current power * correction factor).",
@ -874,6 +881,8 @@
},
"battery": {
"battery": "Battery",
"FwVersion": "Firmware Version",
"HwVersion": "Hardware Version",
"DataAge": "Data Age: ",
"Seconds": " {val} seconds",
"status": "Status",
@ -942,6 +951,8 @@
"dischargedEnergy": "Discharged energy",
"instantaneousPower": "Instantaneous Power",
"consumedAmpHours": "Consumed Amp Hours",
"midpointVoltage": "Midpoint Voltage",
"midpointDeviation": "Midpoint Deviation",
"lastFullCharge": "Last full Charge"
}
}

View File

@ -152,10 +152,10 @@
"LoadingInverter": "Waiting for data... (can take up to 10 seconds)"
},
"vedirecthome": {
"SerialNumber": "Serial Number: ",
"FirmwareNumber": "Firmware Number: ",
"DataAge": "Data Age: ",
"Seconds": "{val} seconds",
"SerialNumber": "Numéro de série",
"FirmwareVersion": "Version du Firmware",
"DataAge": "Âge des données",
"Seconds": "{val} secondes",
"Property": "Property",
"Value": "Value",
"Unit": "Unit",
@ -285,7 +285,10 @@
"ChipRevision": "Révision de la puce",
"ChipCores": "Nombre de cœurs",
"CpuFrequency": "Fréquence du CPU",
"Mhz": "MHz"
"Mhz": "MHz",
"FlashSize": "Taille de la mémoire flash",
"Bytes": "octets",
"MegaBytes": "Mo"
},
"memoryinfo": {
"MemoryInformation": "Informations sur la mémoire",
@ -714,6 +717,7 @@
"VoltageLoadCorrectionFactor": "Load correction factor",
"BatterySocInfo": "<b>Hint:</b> The battery SoC (State of Charge) values are only used if the battery communication interface reported SoC updates in the last minute. Otherwise the voltage thresholds will be used as fallback.",
"InverterIsBehindPowerMeter": "PowerMeter reading includes inverter output",
"InverterIsBehindPowerMeterHint": "Enable this option if the power meter reading is reduced by the inverter's output when it produces power. This is typically true.",
"InverterIsSolarPowered": "Inverter is powered by solar modules",
"VoltageThresholds": "Battery Voltage Thresholds",
"VoltageLoadCorrectionInfo": "<b>Hint:</b> When the battery is discharged, its voltage drops. The voltage drop scales with the discharge current. In order to not stop the inverter too early (stop threshold), this load correction factor can be specified to calculate the battery voltage if it was idle. Corrected voltage = DC Voltage + (Current power * correction factor)."
@ -862,6 +866,8 @@
},
"battery": {
"battery": "Battery",
"FwVersion": "Firmware Version",
"HwVersion": "Hardware Version",
"DataAge": "Data Age: ",
"Seconds": " {val} seconds",
"status": "Status",
@ -930,6 +936,8 @@
"dischargedEnergy": "Discharged energy",
"instantaneousPower": "Instantaneous Power",
"consumedAmpHours": "Consumed Amp Hours",
"midpointVoltage": "Midpoint Voltage",
"midpointDeviation": "Midpoint Deviation",
"lastFullCharge": "Last full Charge"
}
}

View File

@ -4,6 +4,8 @@ type BatteryData = (ValueObject | string)[];
export interface Battery {
manufacturer: string;
fwversion: string;
hwversion: string;
data_age: number;
values: BatteryData[];
issues: number[];

View File

@ -4,6 +4,7 @@ export interface SystemStatus {
chiprevision: number;
chipcores: number;
cpufreq: number;
flashsize: number;
// FirmwareInfo
hostname: string;
sdkversion: string;

View File

@ -112,7 +112,7 @@
<div class="input-group">
<input type="number" class="form-control" id="stopBatterySoCThreshold"
placeholder="95" v-model="acChargerConfigList.stop_batterysoc_threshold"
aria-describedby="stopBatterySoCThresholdDescription" min="2" max="100" required/>
aria-describedby="stopBatterySoCThresholdDescription" min="2" max="99" required/>
<span class="input-group-text" id="stopBatterySoCThresholdDescription">%</span>
</div>
</div>

View File

@ -102,6 +102,7 @@
<InputElement v-show="hasPowerMeter()"
:label="$t('powerlimiteradmin.InverterIsBehindPowerMeter')"
v-model="powerLimiterConfigList.is_inverter_behind_powermeter"
:tooltip="$t('powerlimiteradmin.InverterIsBehindPowerMeterHint')"
type="checkbox" wide/>
<div class="row mb-3" v-if="!powerLimiterConfigList.is_inverter_solar_powered">

View File

@ -106,7 +106,7 @@
</CardElement>
<div class="alert alert-secondary mt-5" role="alert">
<h2>URL examples:</h2>
<h2>{{ $t('powermeteradmin.urlExamplesHeading') }}:</h2>
<ul>
<li>http://admin:secret@shelly3em.home/status</li>
<li>https://admin:secret@shelly3em.home/status</li>
@ -114,10 +114,12 @@
<li>http://12.34.56.78/emeter/0</li>
</ul>
<h2>JSON path examples:</h2>
<h2>{{ $t('powermeteradmin.jsonPathExamplesHeading') }}:</h2>
{{ $t('powermeteradmin.jsonPathExamplesExplanation') }}
<ul>
<li><code>power/total/watts</code> - Finds 123.4 in <code>{ "power": { "phase1": { "factor": 0.98, "watts": 42 }, "total": { "watts": 123.4 } } }</code></li>
<li><code>total</code> - Finds 123.4 in <code>{ "othervalue": 66, "total": 123.4 }</code></li>
<li><code>power/total/watts</code> &mdash; <code>{ "power": { "phase1": { "factor": 0.98, "watts": 42 }, "total": { "watts": 123.4 } } }</code></li>
<li><code>data/[1]/power</code> &mdash; <code>{ "data": [ { "factor": 0.98, "power": 42 }, { "factor": 1.0, "power": 123.4 } ] } }</code></li>
<li><code>total</code> &mdash; <code>{ "othervalue": 66, "total": 123.4 }</code></li>
</ul>
</div>