diff --git a/include/Battery.h b/include/Battery.h index 700c46c45..c8cf42f10 100644 --- a/include/Battery.h +++ b/include/Battery.h @@ -14,7 +14,7 @@ class BatteryProvider { virtual void deinit() = 0; virtual void loop() = 0; virtual std::shared_ptr getStats() const = 0; - virtual bool usesHwPort2() const { return false; } + virtual int usedHwUart() const { return -1; } // -1 => no HW UART used }; class BatteryClass { diff --git a/include/JkBmsController.h b/include/JkBmsController.h index bbc5a5ac0..60e316522 100644 --- a/include/JkBmsController.h +++ b/include/JkBmsController.h @@ -11,6 +11,8 @@ class DataPointContainer; namespace JkBms { +uint8_t constexpr HwSerialPort = ((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0); + class Controller : public BatteryProvider { public: Controller() = default; @@ -19,9 +21,7 @@ class Controller : public BatteryProvider { void deinit() final; void loop() final; std::shared_ptr getStats() const final { return _stats; } - bool usesHwPort2() const final { - return ARDUINO_USB_CDC_ON_BOOT != 1; - } + int usedHwUart() const final { return HwSerialPort; } private: enum class Status : unsigned { diff --git a/include/PinMapping.h b/include/PinMapping.h index f6db7a2da..75b27294e 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -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; diff --git a/include/SerialPortManager.h b/include/SerialPortManager.h index 4d4c2dabb..b1a584479 100644 --- a/include/SerialPortManager.h +++ b/include/SerialPortManager.h @@ -5,14 +5,16 @@ class SerialPortManagerClass { public: - bool allocateMpptPort(int port); - bool allocateBatteryPort(int port); + void init(); + bool allocateMpptPort(uint8_t port); + bool allocateBatteryPort(uint8_t port); void invalidateBatteryPort(); void invalidateMpptPorts(); private: - enum Owner { - BATTERY, + enum class Owner { + Console, + Battery, MPPT }; diff --git a/include/VictronMppt.h b/include/VictronMppt.h index a4f1b1ab8..11254ed66 100644 --- a/include/VictronMppt.h +++ b/include/VictronMppt.h @@ -55,7 +55,8 @@ class VictronMpptClass { using controller_t = std::unique_ptr; std::vector _controllers; - bool initController(int8_t rx, int8_t tx, bool logging, int hwSerialPort); + bool initController(int8_t rx, int8_t tx, bool logging, + uint8_t instance, uint8_t hwSerialPort); }; extern VictronMpptClass VictronMppt; diff --git a/include/VictronSmartShunt.h b/include/VictronSmartShunt.h index 97b421325..cc123e9f3 100644 --- a/include/VictronSmartShunt.h +++ b/include/VictronSmartShunt.h @@ -9,11 +9,10 @@ class VictronSmartShunt : public BatteryProvider { void deinit() final { } void loop() final; std::shared_ptr getStats() const final { return _stats; } - bool usesHwPort2() const final { - return ARDUINO_USB_CDC_ON_BOOT != 1; - } + int usedHwUart() const final { return _hwSerialPort; } private: + static uint8_t constexpr _hwSerialPort = ((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0); uint32_t _lastUpdate = 0; std::shared_ptr _stats = std::make_shared(); diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp index 0c434a4c7..4b258e75b 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp @@ -62,7 +62,8 @@ VeDirectFrameHandler::VeDirectFrameHandler() : } template -void VeDirectFrameHandler::init(char const* who, int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort) +void VeDirectFrameHandler::init(char const* who, int8_t rx, int8_t tx, + Print* msgOut, bool verboseLogging, uint8_t hwSerialPort) { _vedirectSerial = std::make_unique(hwSerialPort); _vedirectSerial->end(); // make sure the UART will be re-initialized diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h index 1c482920c..244caf3f3 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h @@ -30,7 +30,8 @@ class VeDirectFrameHandler { 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; diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index 368cac1d0..e0f386db6 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -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) diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index 595988985..b8bd4c72f 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -40,7 +40,8 @@ class VeDirectMpptController : public VeDirectFrameHandler { 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; diff --git a/lib/VeDirectFrameHandler/VeDirectShuntController.cpp b/lib/VeDirectFrameHandler/VeDirectShuntController.cpp index c8ef62a7e..c16b6a072 100644 --- a/lib/VeDirectFrameHandler/VeDirectShuntController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectShuntController.cpp @@ -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) diff --git a/lib/VeDirectFrameHandler/VeDirectShuntController.h b/lib/VeDirectFrameHandler/VeDirectShuntController.h index 03bc96b8b..d7bc25134 100644 --- a/lib/VeDirectFrameHandler/VeDirectShuntController.h +++ b/lib/VeDirectFrameHandler/VeDirectShuntController.h @@ -8,7 +8,8 @@ class VeDirectShuntController : public VeDirectFrameHandler { 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; diff --git a/src/Battery.cpp b/src/Battery.cpp index de05b03db..8d24c466c 100644 --- a/src/Battery.cpp +++ b/src/Battery.cpp @@ -60,16 +60,16 @@ void BatteryClass::updateSettings() _upProvider = std::make_unique(); 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; - } + // port is -1 if provider is neither JK BMS nor SmartShunt. otherwise, port + // is 2, unless running on ESP32-S3 with USB CDC, then port is 0. + int port = _upProvider->usedHwUart(); + if (port >= 0 && !SerialPortManager.allocateBatteryPort(port)) { + _upProvider = nullptr; + return; } if (!_upProvider->init(verboseLogging)) { diff --git a/src/JkBmsController.cpp b/src/JkBmsController.cpp index 94f80cb08..9fdb7922f 100644 --- a/src/JkBmsController.cpp +++ b/src/JkBmsController.cpp @@ -7,6 +7,8 @@ #include "JkBmsController.h" #include +namespace JkBms { + //#define JKBMS_DUMMY_SERIAL #ifdef JKBMS_DUMMY_SERIAL @@ -198,11 +200,9 @@ class DummySerial { }; DummySerial HwSerial; #else -HardwareSerial HwSerial((ARDUINO_USB_CDC_ON_BOOT != 1)?2:0); +HardwareSerial HwSerial(HwSerialPort); #endif -namespace JkBms { - bool Controller::init(bool verboseLogging) { _verboseLogging = verboseLogging; diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index a3c888861..0c4a09843 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -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; diff --git a/src/SerialPortManager.cpp b/src/SerialPortManager.cpp index c4eb8514a..d489cbd59 100644 --- a/src/SerialPortManager.cpp +++ b/src/SerialPortManager.cpp @@ -6,12 +6,19 @@ SerialPortManagerClass SerialPortManager; -bool SerialPortManagerClass::allocateBatteryPort(int port) +void SerialPortManagerClass::init() { - return allocatePort(port, Owner::BATTERY); + if (ARDUINO_USB_CDC_ON_BOOT != 1) { + allocatePort(0, Owner::Console); + } +} + +bool SerialPortManagerClass::allocateBatteryPort(uint8_t port) +{ + return allocatePort(port, Owner::Battery); } -bool SerialPortManagerClass::allocateMpptPort(int port) +bool SerialPortManagerClass::allocateMpptPort(uint8_t port) { return allocatePort(port, Owner::MPPT); } @@ -19,16 +26,27 @@ bool SerialPortManagerClass::allocateMpptPort(int port) bool SerialPortManagerClass::allocatePort(uint8_t port, Owner owner) { if (port >= MAX_CONTROLLERS) { - MessageOutput.printf("[SerialPortManager] Invalid serial port = %d \r\n", port); + MessageOutput.printf("[SerialPortManager] Invalid serial port: %d\r\n", port); + return false; + } + + auto res = allocatedPorts.insert({port, owner}); + + if (!res.second) { + MessageOutput.printf("[SerialPortManager] Cannot assign HW UART " + "port %d to %s: already in use by %s\r\n", + port, print(owner), print(res.first->second)); return false; } - return allocatedPorts.insert({port, owner}).second; + MessageOutput.printf("[SerialPortManager] HW UART port %d now in use " + "by %s\r\n", port, print(owner)); + return true; } void SerialPortManagerClass::invalidateBatteryPort() { - invalidate(Owner::BATTERY); + invalidate(Owner::Battery); } void SerialPortManagerClass::invalidateMpptPorts() @@ -51,10 +69,12 @@ void SerialPortManagerClass::invalidate(Owner owner) const char* SerialPortManagerClass::print(Owner owner) { switch (owner) { - case BATTERY: - return "BATTERY"; - case MPPT: - return "MPPT"; + case Owner::Console: + return "Serial Console"; + case Owner::Battery: + return "Battery Interface"; + case Owner::MPPT: + return "Victron MPPT"; } return "unknown"; } diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index 770be014b..d1366b868 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -29,29 +29,32 @@ void VictronMpptClass::updateSettings() 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_rx2, pin.victron_tx2, config.Vedirect.VerboseLogging, hwSerialPort); + // HW UART 1 has always been the designated UART to connect a Victron MPPT + if (!initController(pin.victron_rx, pin.victron_tx, + config.Vedirect.VerboseLogging, 1, 1)) { return; } + + // HW UART 2 conflicts with the SDM power meter and the battery interface + if (!initController(pin.victron_rx2, pin.victron_tx2, + config.Vedirect.VerboseLogging, 2, 2)) { return; } + + // HW UART 0 is only available on ESP32-S3 with logging over USB CDC, and + // furthermore still conflicts with the battery interface in that case + initController(pin.victron_rx3, pin.victron_tx3, + config.Vedirect.VerboseLogging, 3, 0); } -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, uint8_t hwSerialPort) { - MessageOutput.printf("[VictronMppt] rx = %d, tx = %d, hwSerialPort = %d\r\n", rx, tx, hwSerialPort); + MessageOutput.printf("[VictronMppt Instance %d] rx = %d, tx = %d, " + "hwSerialPort = %d\r\n", instance, rx, tx, hwSerialPort); 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; - } + if (!SerialPortManager.allocateMpptPort(hwSerialPort)) { return false; } auto upController = std::make_unique(); upController->init(rx, tx, &MessageOutput, logging, hwSerialPort); diff --git a/src/VictronSmartShunt.cpp b/src/VictronSmartShunt.cpp index e3da07fa4..067c7fd43 100644 --- a/src/VictronSmartShunt.cpp +++ b/src/VictronSmartShunt.cpp @@ -21,7 +21,7 @@ bool VictronSmartShunt::init(bool verboseLogging) auto tx = static_cast(pin.battery_tx); auto rx = static_cast(pin.battery_rx); - VeDirectShunt.init(rx, tx, &MessageOutput, verboseLogging); + VeDirectShunt.init(rx, tx, &MessageOutput, verboseLogging, _hwSerialPort); return true; } diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index e6c3170a8..9bed5e53e 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -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(); batteryPinObj["rx"] = pin.battery_rx; diff --git a/src/main.cpp b/src/main.cpp index e0a54c155..c23087aae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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);