Skip to content

Commit 5a1c3af

Browse files
committed
Feature: add support for a third Victron MPPT
only on ESP32-S3-USB. this fiddles with the available hardware UARTs to make it possible to use a third Victron MPPT. if three MPPTs are defined int the pin mapping, you will not be able to use the SmartShunt and JK BMS battery interfaces. note that using a second MPPT will also conflict with the SDM power meter, and that conflict is not detected, yet.
1 parent 90aafe2 commit 5a1c3af

20 files changed

+111
-59
lines changed

include/Battery.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class BatteryProvider {
1414
virtual void deinit() = 0;
1515
virtual void loop() = 0;
1616
virtual std::shared_ptr<BatteryStats> getStats() const = 0;
17-
virtual bool usesHwPort2() const { return false; }
17+
virtual int usedHwUart() const { return -1; } // -1 => no HW UART used
1818
};
1919

2020
class BatteryClass {

include/JkBmsController.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class DataPointContainer;
1111

1212
namespace JkBms {
1313

14+
uint8_t constexpr HwSerialPort = ((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0);
15+
1416
class Controller : public BatteryProvider {
1517
public:
1618
Controller() = default;
@@ -19,9 +21,7 @@ class Controller : public BatteryProvider {
1921
void deinit() final;
2022
void loop() final;
2123
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
22-
bool usesHwPort2() const final {
23-
return ARDUINO_USB_CDC_ON_BOOT != 1;
24-
}
24+
int usedHwUart() const final { return HwSerialPort; }
2525

2626
private:
2727
enum class Status : unsigned {

include/PinMapping.h

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ struct PinMapping_t {
4545
int8_t victron_rx;
4646
int8_t victron_tx2;
4747
int8_t victron_rx2;
48+
int8_t victron_tx3;
49+
int8_t victron_rx3;
4850
int8_t battery_rx;
4951
int8_t battery_rxen;
5052
int8_t battery_tx;

include/SerialPortManager.h

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55

66
class SerialPortManagerClass {
77
public:
8-
bool allocateMpptPort(int port);
9-
bool allocateBatteryPort(int port);
8+
void init();
9+
bool allocateMpptPort(uint8_t port);
10+
bool allocateBatteryPort(uint8_t port);
1011
void invalidateBatteryPort();
1112
void invalidateMpptPorts();
1213

1314
private:
14-
enum Owner {
15-
BATTERY,
15+
enum class Owner {
16+
Console,
17+
Battery,
1618
MPPT
1719
};
1820

include/VictronMppt.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ class VictronMpptClass {
5555
using controller_t = std::unique_ptr<VeDirectMpptController>;
5656
std::vector<controller_t> _controllers;
5757

58-
bool initController(int8_t rx, int8_t tx, bool logging, int hwSerialPort);
58+
bool initController(int8_t rx, int8_t tx, bool logging,
59+
uint8_t instance, uint8_t hwSerialPort);
5960
};
6061

6162
extern VictronMpptClass VictronMppt;

include/VictronSmartShunt.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ class VictronSmartShunt : public BatteryProvider {
99
void deinit() final { }
1010
void loop() final;
1111
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
12-
bool usesHwPort2() const final {
13-
return ARDUINO_USB_CDC_ON_BOOT != 1;
14-
}
12+
int usedHwUart() const final { return _hwSerialPort; }
1513

1614
private:
15+
static uint8_t constexpr _hwSerialPort = ((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0);
1716
uint32_t _lastUpdate = 0;
1817
std::shared_ptr<VictronSmartShuntStats> _stats =
1918
std::make_shared<VictronSmartShuntStats>();

lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ VeDirectFrameHandler<T>::VeDirectFrameHandler() :
6262
}
6363

6464
template<typename T>
65-
void VeDirectFrameHandler<T>::init(char const* who, int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort)
65+
void VeDirectFrameHandler<T>::init(char const* who, int8_t rx, int8_t tx,
66+
Print* msgOut, bool verboseLogging, uint8_t hwSerialPort)
6667
{
6768
_vedirectSerial = std::make_unique<HardwareSerial>(hwSerialPort);
6869
_vedirectSerial->end(); // make sure the UART will be re-initialized

lib/VeDirectFrameHandler/VeDirectFrameHandler.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ class VeDirectFrameHandler {
3030

3131
protected:
3232
VeDirectFrameHandler();
33-
void init(char const* who, int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort);
33+
void init(char const* who, int8_t rx, int8_t tx, Print* msgOut,
34+
bool verboseLogging, uint8_t hwSerialPort);
3435
virtual bool hexDataHandler(VeDirectHexData const &data) { return false; } // handles the disassembeled hex response
3536

3637
bool _verboseLogging;

lib/VeDirectFrameHandler/VeDirectMpptController.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212

1313
//#define PROCESS_NETWORK_STATE
1414

15-
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort)
15+
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut,
16+
bool verboseLogging, uint8_t hwSerialPort)
1617
{
17-
VeDirectFrameHandler::init("MPPT", rx, tx, msgOut, verboseLogging, hwSerialPort);
18+
VeDirectFrameHandler::init("MPPT", rx, tx, msgOut,
19+
verboseLogging, hwSerialPort);
1820
}
1921

2022
bool VeDirectMpptController::processTextDataDerived(std::string const& name, std::string const& value)

lib/VeDirectFrameHandler/VeDirectMpptController.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class VeDirectMpptController : public VeDirectFrameHandler<veMpptStruct> {
4040
public:
4141
VeDirectMpptController() = default;
4242

43-
void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort);
43+
void init(int8_t rx, int8_t tx, Print* msgOut,
44+
bool verboseLogging, uint8_t hwSerialPort);
4445

4546
using data_t = veMpptStruct;
4647

lib/VeDirectFrameHandler/VeDirectShuntController.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
VeDirectShuntController VeDirectShunt;
55

6-
void VeDirectShuntController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging)
6+
void VeDirectShuntController::init(int8_t rx, int8_t tx, Print* msgOut,
7+
bool verboseLogging, uint8_t hwSerialPort)
78
{
8-
VeDirectFrameHandler::init("SmartShunt", rx, tx, msgOut, verboseLogging,
9-
((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0));
9+
VeDirectFrameHandler::init("SmartShunt", rx, tx, msgOut,
10+
verboseLogging, hwSerialPort);
1011
}
1112

1213
bool VeDirectShuntController::processTextDataDerived(std::string const& name, std::string const& value)

lib/VeDirectFrameHandler/VeDirectShuntController.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class VeDirectShuntController : public VeDirectFrameHandler<veShuntStruct> {
88
public:
99
VeDirectShuntController() = default;
1010

11-
void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging);
11+
void init(int8_t rx, int8_t tx, Print* msgOut,
12+
bool verboseLogging, uint8_t hwSerialPort);
1213

1314
using data_t = veShuntStruct;
1415

src/Battery.cpp

+7-7
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,16 @@ void BatteryClass::updateSettings()
6060
_upProvider = std::make_unique<VictronSmartShunt>();
6161
break;
6262
default:
63-
MessageOutput.printf("Unknown battery provider: %d\r\n", config.Battery.Provider);
63+
MessageOutput.printf("[Battery] Unknown provider: %d\r\n", config.Battery.Provider);
6464
return;
6565
}
6666

67-
if(_upProvider->usesHwPort2()) {
68-
if (!SerialPortManager.allocateBatteryPort(2)) {
69-
MessageOutput.printf("[Battery] Serial port %d already in use. Initialization aborted!\r\n", 2);
70-
_upProvider = nullptr;
71-
return;
72-
}
67+
// port is -1 if provider is neither JK BMS nor SmartShunt. otherwise, port
68+
// is 2, unless running on ESP32-S3 with USB CDC, then port is 0.
69+
int port = _upProvider->usedHwUart();
70+
if (port >= 0 && !SerialPortManager.allocateBatteryPort(port)) {
71+
_upProvider = nullptr;
72+
return;
7373
}
7474

7575
if (!_upProvider->init(verboseLogging)) {

src/JkBmsController.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include "JkBmsController.h"
88
#include <frozen/map.h>
99

10+
namespace JkBms {
11+
1012
//#define JKBMS_DUMMY_SERIAL
1113

1214
#ifdef JKBMS_DUMMY_SERIAL
@@ -198,11 +200,9 @@ class DummySerial {
198200
};
199201
DummySerial HwSerial;
200202
#else
201-
HardwareSerial HwSerial((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0);
203+
HardwareSerial HwSerial(HwSerialPort);
202204
#endif
203205

204-
namespace JkBms {
205-
206206
bool Controller::init(bool verboseLogging)
207207
{
208208
_verboseLogging = verboseLogging;

src/PinMapping.cpp

+15-2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@
100100
#define VICTRON_PIN_RX2 -1
101101
#endif
102102

103+
#ifndef VICTRON_PIN_TX3
104+
#define VICTRON_PIN_TX3 -1
105+
#endif
106+
107+
#ifndef VICTRON_PIN_RX3
108+
#define VICTRON_PIN_RX3 -1
109+
#endif
110+
103111
#ifndef BATTERY_PIN_RX
104112
#define BATTERY_PIN_RX -1
105113
#endif
@@ -207,8 +215,11 @@ PinMappingClass::PinMappingClass()
207215
_pinMapping.victron_rx = VICTRON_PIN_RX;
208216
_pinMapping.victron_tx = VICTRON_PIN_TX;
209217

210-
_pinMapping.victron_rx2 = VICTRON_PIN_RX;
211-
_pinMapping.victron_tx2 = VICTRON_PIN_TX;
218+
_pinMapping.victron_rx2 = VICTRON_PIN_RX2;
219+
_pinMapping.victron_tx2 = VICTRON_PIN_TX2;
220+
221+
_pinMapping.victron_rx3 = VICTRON_PIN_RX3;
222+
_pinMapping.victron_tx3 = VICTRON_PIN_TX3;
212223

213224
_pinMapping.battery_rx = BATTERY_PIN_RX;
214225
_pinMapping.battery_rxen = BATTERY_PIN_RXEN;
@@ -292,6 +303,8 @@ bool PinMappingClass::init(const String& deviceMapping)
292303
_pinMapping.victron_tx = doc[i]["victron"]["tx"] | VICTRON_PIN_TX;
293304
_pinMapping.victron_rx2 = doc[i]["victron"]["rx2"] | VICTRON_PIN_RX2;
294305
_pinMapping.victron_tx2 = doc[i]["victron"]["tx2"] | VICTRON_PIN_TX2;
306+
_pinMapping.victron_rx3 = doc[i]["victron"]["rx3"] | VICTRON_PIN_RX3;
307+
_pinMapping.victron_tx3 = doc[i]["victron"]["tx3"] | VICTRON_PIN_TX3;
295308

296309
_pinMapping.battery_rx = doc[i]["battery"]["rx"] | BATTERY_PIN_RX;
297310
_pinMapping.battery_rxen = doc[i]["battery"]["rxen"] | BATTERY_PIN_RXEN;

src/SerialPortManager.cpp

+30-10
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,47 @@
66

77
SerialPortManagerClass SerialPortManager;
88

9-
bool SerialPortManagerClass::allocateBatteryPort(int port)
9+
void SerialPortManagerClass::init()
1010
{
11-
return allocatePort(port, Owner::BATTERY);
11+
if (ARDUINO_USB_CDC_ON_BOOT != 1) {
12+
allocatePort(0, Owner::Console);
13+
}
14+
}
15+
16+
bool SerialPortManagerClass::allocateBatteryPort(uint8_t port)
17+
{
18+
return allocatePort(port, Owner::Battery);
1219
}
1320

14-
bool SerialPortManagerClass::allocateMpptPort(int port)
21+
bool SerialPortManagerClass::allocateMpptPort(uint8_t port)
1522
{
1623
return allocatePort(port, Owner::MPPT);
1724
}
1825

1926
bool SerialPortManagerClass::allocatePort(uint8_t port, Owner owner)
2027
{
2128
if (port >= MAX_CONTROLLERS) {
22-
MessageOutput.printf("[SerialPortManager] Invalid serial port = %d \r\n", port);
29+
MessageOutput.printf("[SerialPortManager] Invalid serial port: %d\r\n", port);
30+
return false;
31+
}
32+
33+
auto res = allocatedPorts.insert({port, owner});
34+
35+
if (!res.second) {
36+
MessageOutput.printf("[SerialPortManager] Cannot assign HW UART "
37+
"port %d to %s: already in use by %s\r\n",
38+
port, print(owner), print(res.first->second));
2339
return false;
2440
}
2541

26-
return allocatedPorts.insert({port, owner}).second;
42+
MessageOutput.printf("[SerialPortManager] HW UART port %d now in use "
43+
"by %s\r\n", port, print(owner));
44+
return true;
2745
}
2846

2947
void SerialPortManagerClass::invalidateBatteryPort()
3048
{
31-
invalidate(Owner::BATTERY);
49+
invalidate(Owner::Battery);
3250
}
3351

3452
void SerialPortManagerClass::invalidateMpptPorts()
@@ -51,10 +69,12 @@ void SerialPortManagerClass::invalidate(Owner owner)
5169
const char* SerialPortManagerClass::print(Owner owner)
5270
{
5371
switch (owner) {
54-
case BATTERY:
55-
return "BATTERY";
56-
case MPPT:
57-
return "MPPT";
72+
case Owner::Console:
73+
return "Serial Console";
74+
case Owner::Battery:
75+
return "Battery Interface";
76+
case Owner::MPPT:
77+
return "Victron MPPT";
5878
}
5979
return "unknown";
6080
}

src/VictronMppt.cpp

+18-15
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,32 @@ void VictronMpptClass::updateSettings()
2929

3030
const PinMapping_t& pin = PinMapping.get();
3131

32-
int hwSerialPort = 1;
33-
bool initSuccess = initController(pin.victron_rx, pin.victron_tx, config.Vedirect.VerboseLogging, hwSerialPort);
34-
if (initSuccess) {
35-
hwSerialPort++;
36-
}
37-
38-
initController(pin.victron_rx2, pin.victron_tx2, config.Vedirect.VerboseLogging, hwSerialPort);
32+
// HW UART 1 has always been the designated UART to connect a Victron MPPT
33+
if (!initController(pin.victron_rx, pin.victron_tx,
34+
config.Vedirect.VerboseLogging, 1, 1)) { return; }
35+
36+
// HW UART 2 conflicts with the SDM power meter and the battery interface
37+
if (!initController(pin.victron_rx2, pin.victron_tx2,
38+
config.Vedirect.VerboseLogging, 2, 2)) { return; }
39+
40+
// HW UART 0 is only available on ESP32-S3 with logging over USB CDC, and
41+
// furthermore still conflicts with the battery interface in that case
42+
initController(pin.victron_rx3, pin.victron_tx3,
43+
config.Vedirect.VerboseLogging, 3, 0);
3944
}
4045

41-
bool VictronMpptClass::initController(int8_t rx, int8_t tx, bool logging, int hwSerialPort)
46+
bool VictronMpptClass::initController(int8_t rx, int8_t tx, bool logging,
47+
uint8_t instance, uint8_t hwSerialPort)
4248
{
43-
MessageOutput.printf("[VictronMppt] rx = %d, tx = %d, hwSerialPort = %d\r\n", rx, tx, hwSerialPort);
49+
MessageOutput.printf("[VictronMppt Instance %d] rx = %d, tx = %d, "
50+
"hwSerialPort = %d\r\n", instance, rx, tx, hwSerialPort);
4451

4552
if (rx < 0) {
46-
MessageOutput.printf("[VictronMppt] invalid pin config rx = %d, tx = %d\r\n", rx, tx);
53+
MessageOutput.printf("[VictronMppt Instance %d] invalid pin config\r\n", instance);
4754
return false;
4855
}
4956

50-
if (!SerialPortManager.allocateMpptPort(hwSerialPort)) {
51-
MessageOutput.printf("[VictronMppt] Serial port %d already in use. Initialization aborted!\r\n",
52-
hwSerialPort);
53-
return false;
54-
}
57+
if (!SerialPortManager.allocateMpptPort(hwSerialPort)) { return false; }
5558

5659
auto upController = std::make_unique<VeDirectMpptController>();
5760
upController->init(rx, tx, &MessageOutput, logging, hwSerialPort);

src/VictronSmartShunt.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ bool VictronSmartShunt::init(bool verboseLogging)
2121
auto tx = static_cast<gpio_num_t>(pin.battery_tx);
2222
auto rx = static_cast<gpio_num_t>(pin.battery_rx);
2323

24-
VeDirectShunt.init(rx, tx, &MessageOutput, verboseLogging);
24+
VeDirectShunt.init(rx, tx, &MessageOutput, verboseLogging, _hwSerialPort);
2525
return true;
2626
}
2727

src/WebApi_device.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
9191
victronPinObj["tx"] = pin.victron_tx;
9292
victronPinObj["rx2"] = pin.victron_rx2;
9393
victronPinObj["tx2"] = pin.victron_tx2;
94+
victronPinObj["rx3"] = pin.victron_rx3;
95+
victronPinObj["tx3"] = pin.victron_tx3;
9496

9597
auto batteryPinObj = curPin["battery"].to<JsonObject>();
9698
batteryPinObj["rx"] = pin.battery_rx;

src/main.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "InverterSettings.h"
99
#include "Led_Single.h"
1010
#include "MessageOutput.h"
11+
#include "SerialPortManager.h"
1112
#include "VictronMppt.h"
1213
#include "Battery.h"
1314
#include "Huawei_can.h"
@@ -96,6 +97,8 @@ void setup()
9697
const auto& pin = PinMapping.get();
9798
MessageOutput.println("done");
9899

100+
SerialPortManager.init();
101+
99102
// Initialize WiFi
100103
MessageOutput.print("Initialize Network... ");
101104
NetworkSettings.init(scheduler);

0 commit comments

Comments
 (0)