Feature: Support for second Victron MPPT charge controller
this change adds support for a second Victron MPPT charge controller using a second serial connection. * Add device configuration for a second victron mppt * Update VedirectView for second victron mppt * Update MqttHandleVedirect for second victron mppt * Update MqttHandleVedirectHass for second victron mppt * Handle nonexisting victron controllers with optionals * Add bool-function to Battery and inherited classes, if uart port 2 is being used * Introduced a serial port manager. In order to prevent the battery and the Victron MPPT to use the same hw serial ports, this class keeps track of the used ports and their owners.
This commit is contained in:
parent
21c19f4b7f
commit
75541be248
@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
@ -9,28 +8,28 @@
|
|||||||
#include "BatteryStats.h"
|
#include "BatteryStats.h"
|
||||||
|
|
||||||
class BatteryProvider {
|
class BatteryProvider {
|
||||||
public:
|
public:
|
||||||
// returns true if the provider is ready for use, false otherwise
|
// returns true if the provider is ready for use, false otherwise
|
||||||
virtual bool init(bool verboseLogging) = 0;
|
virtual bool init(bool verboseLogging) = 0;
|
||||||
|
virtual void deinit() = 0;
|
||||||
virtual void deinit() = 0;
|
virtual void loop() = 0;
|
||||||
virtual void loop() = 0;
|
virtual std::shared_ptr<BatteryStats> getStats() const = 0;
|
||||||
virtual std::shared_ptr<BatteryStats> getStats() const = 0;
|
virtual bool usesHwPort2() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BatteryClass {
|
class BatteryClass {
|
||||||
public:
|
public:
|
||||||
void init(Scheduler&);
|
void init(Scheduler&);
|
||||||
void updateSettings();
|
void updateSettings();
|
||||||
|
|
||||||
std::shared_ptr<BatteryStats const> getStats() const;
|
std::shared_ptr<BatteryStats const> getStats() const;
|
||||||
private:
|
|
||||||
void loop();
|
|
||||||
|
|
||||||
Task _loopTask;
|
private:
|
||||||
|
void loop();
|
||||||
|
|
||||||
mutable std::mutex _mutex;
|
Task _loopTask;
|
||||||
std::unique_ptr<BatteryProvider> _upProvider = nullptr;
|
mutable std::mutex _mutex;
|
||||||
|
std::unique_ptr<BatteryProvider> _upProvider = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern BatteryClass Battery;
|
extern BatteryClass Battery;
|
||||||
|
|||||||
@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
#define DEV_MAX_MAPPING_NAME_STRLEN 63
|
#define DEV_MAX_MAPPING_NAME_STRLEN 63
|
||||||
|
|
||||||
|
#define VICTRON_MAX_COUNT 2
|
||||||
|
|
||||||
#define POWERMETER_MAX_PHASES 3
|
#define POWERMETER_MAX_PHASES 3
|
||||||
#define POWERMETER_MAX_HTTP_URL_STRLEN 1024
|
#define POWERMETER_MAX_HTTP_URL_STRLEN 1024
|
||||||
#define POWERMETER_MAX_USERNAME_STRLEN 64
|
#define POWERMETER_MAX_USERNAME_STRLEN 64
|
||||||
@ -198,7 +200,7 @@ struct CONFIG_T {
|
|||||||
bool HttpIndividualRequests;
|
bool HttpIndividualRequests;
|
||||||
POWERMETER_HTTP_PHASE_CONFIG_T Http_Phase[POWERMETER_MAX_PHASES];
|
POWERMETER_HTTP_PHASE_CONFIG_T Http_Phase[POWERMETER_MAX_PHASES];
|
||||||
} PowerMeter;
|
} PowerMeter;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool Enabled;
|
bool Enabled;
|
||||||
bool VerboseLogging;
|
bool VerboseLogging;
|
||||||
@ -225,7 +227,7 @@ struct CONFIG_T {
|
|||||||
float FullSolarPassThroughStartVoltage;
|
float FullSolarPassThroughStartVoltage;
|
||||||
float FullSolarPassThroughStopVoltage;
|
float FullSolarPassThroughStopVoltage;
|
||||||
} PowerLimiter;
|
} PowerLimiter;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool Enabled;
|
bool Enabled;
|
||||||
bool VerboseLogging;
|
bool VerboseLogging;
|
||||||
@ -243,7 +245,7 @@ struct CONFIG_T {
|
|||||||
float Auto_Power_Voltage_Limit;
|
float Auto_Power_Voltage_Limit;
|
||||||
float Auto_Power_Enable_Voltage_Limit;
|
float Auto_Power_Enable_Voltage_Limit;
|
||||||
float Auto_Power_Lower_Power_Limit;
|
float Auto_Power_Lower_Power_Limit;
|
||||||
float Auto_Power_Upper_Power_Limit;
|
float Auto_Power_Upper_Power_Limit;
|
||||||
} Huawei;
|
} Huawei;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ class Controller : public BatteryProvider {
|
|||||||
void deinit() final;
|
void deinit() final;
|
||||||
void loop() final;
|
void loop() final;
|
||||||
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
||||||
|
bool usesHwPort2() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Status : unsigned {
|
enum class Status : unsigned {
|
||||||
|
|||||||
@ -5,23 +5,24 @@
|
|||||||
#include <espMqttClient.h>
|
#include <espMqttClient.h>
|
||||||
|
|
||||||
class MqttBattery : public BatteryProvider {
|
class MqttBattery : public BatteryProvider {
|
||||||
public:
|
public:
|
||||||
MqttBattery() = default;
|
MqttBattery() = default;
|
||||||
|
|
||||||
bool init(bool verboseLogging) final;
|
bool init(bool verboseLogging) final;
|
||||||
void deinit() final;
|
void deinit() final;
|
||||||
void loop() final { return; } // this class is event-driven
|
void loop() final { return; } // this class is event-driven
|
||||||
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
||||||
|
bool usesHwPort2() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _verboseLogging = false;
|
bool _verboseLogging = false;
|
||||||
String _socTopic;
|
String _socTopic;
|
||||||
String _voltageTopic;
|
String _voltageTopic;
|
||||||
std::shared_ptr<MqttBatteryStats> _stats = std::make_shared<MqttBatteryStats>();
|
std::shared_ptr<MqttBatteryStats> _stats = std::make_shared<MqttBatteryStats>();
|
||||||
|
|
||||||
std::optional<float> getFloat(std::string const& src, char const* topic);
|
std::optional<float> getFloat(std::string const& src, char const* topic);
|
||||||
void onMqttMessageSoC(espMqttClientTypes::MessageProperties const& properties,
|
void onMqttMessageSoC(espMqttClientTypes::MessageProperties const& properties,
|
||||||
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total);
|
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total);
|
||||||
void onMqttMessageVoltage(espMqttClientTypes::MessageProperties const& properties,
|
void onMqttMessageVoltage(espMqttClientTypes::MessageProperties const& properties,
|
||||||
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total);
|
char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include "VeDirectMpptController.h"
|
#include "VeDirectMpptController.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <map>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
|
|
||||||
#ifndef VICTRON_PIN_RX
|
#ifndef VICTRON_PIN_RX
|
||||||
@ -20,7 +21,7 @@ public:
|
|||||||
void forceUpdate();
|
void forceUpdate();
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
VeDirectMpptController::veMpptStruct _kvFrame{};
|
std::map<std::string, VeDirectMpptController::veMpptStruct> _kvFrames;
|
||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
@ -31,6 +32,9 @@ private:
|
|||||||
uint32_t _nextPublishFull = 1;
|
uint32_t _nextPublishFull = 1;
|
||||||
|
|
||||||
bool _PublishFull;
|
bool _PublishFull;
|
||||||
|
|
||||||
|
void publish_mppt_data(const VeDirectMpptController::spData_t &spMpptData,
|
||||||
|
VeDirectMpptController::veMpptStruct &frame) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MqttHandleVedirectClass MqttHandleVedirect;
|
extern MqttHandleVedirectClass MqttHandleVedirect;
|
||||||
|
|||||||
@ -14,9 +14,15 @@ public:
|
|||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
void publish(const String& subtopic, const String& payload);
|
void publish(const String& subtopic, const String& payload);
|
||||||
void publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off);
|
void publishBinarySensor(const char *caption, const char *icon, const char *subTopic,
|
||||||
void publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass = NULL, const char* stateClass = NULL, const char* unitOfMeasurement = NULL);
|
const char *payload_on, const char *payload_off,
|
||||||
void createDeviceInfo(JsonObject& object);
|
const VeDirectMpptController::spData_t &spMpptData);
|
||||||
|
void publishSensor(const char *caption, const char *icon, const char *subTopic,
|
||||||
|
const char *deviceClass, const char *stateClass,
|
||||||
|
const char *unitOfMeasurement,
|
||||||
|
const VeDirectMpptController::spData_t &spMpptData);
|
||||||
|
void createDeviceInfo(JsonObject &object,
|
||||||
|
const VeDirectMpptController::spData_t &spMpptData);
|
||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
@ -24,4 +30,4 @@ private:
|
|||||||
bool _updateForced = false;
|
bool _updateForced = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MqttHandleVedirectHassClass MqttHandleVedirectHass;
|
extern MqttHandleVedirectHassClass MqttHandleVedirectHass;
|
||||||
|
|||||||
@ -40,6 +40,8 @@ struct PinMapping_t {
|
|||||||
uint8_t display_reset;
|
uint8_t display_reset;
|
||||||
int8_t victron_tx;
|
int8_t victron_tx;
|
||||||
int8_t victron_rx;
|
int8_t victron_rx;
|
||||||
|
int8_t victron_tx2;
|
||||||
|
int8_t victron_rx2;
|
||||||
int8_t battery_rx;
|
int8_t battery_rx;
|
||||||
int8_t battery_rxen;
|
int8_t battery_rxen;
|
||||||
int8_t battery_tx;
|
int8_t battery_tx;
|
||||||
@ -63,7 +65,7 @@ public:
|
|||||||
bool isValidCmt2300Config() const;
|
bool isValidCmt2300Config() const;
|
||||||
bool isValidEthConfig() const;
|
bool isValidEthConfig() const;
|
||||||
bool isValidHuaweiConfig() const;
|
bool isValidHuaweiConfig() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PinMapping_t _pinMapping;
|
PinMapping_t _pinMapping;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,6 +14,7 @@ public:
|
|||||||
void deinit() final;
|
void deinit() final;
|
||||||
void loop() final;
|
void loop() final;
|
||||||
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
||||||
|
bool usesHwPort2() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t readUnsignedInt16(uint8_t *data);
|
uint16_t readUnsignedInt16(uint8_t *data);
|
||||||
|
|||||||
27
include/SerialPortManager.h
Normal file
27
include/SerialPortManager.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
class SerialPortManager {
|
||||||
|
public:
|
||||||
|
bool allocateMpptPort(int port);
|
||||||
|
bool allocateBatteryPort(int port);
|
||||||
|
void invalidateBatteryPort();
|
||||||
|
void invalidateMpptPorts();
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SerialPortManager PortManager;
|
||||||
@ -5,6 +5,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "VeDirectMpptController.h"
|
#include "VeDirectMpptController.h"
|
||||||
|
#include "Configuration.h"
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
|
|
||||||
class VictronMpptClass {
|
class VictronMpptClass {
|
||||||
@ -16,12 +17,13 @@ public:
|
|||||||
void updateSettings();
|
void updateSettings();
|
||||||
|
|
||||||
bool isDataValid() const;
|
bool isDataValid() const;
|
||||||
|
bool isDataValid(size_t idx) const;
|
||||||
|
|
||||||
// returns the data age of all controllers,
|
// returns the data age of all controllers,
|
||||||
// i.e, the youngest data's age is returned.
|
// i.e, the youngest data's age is returned.
|
||||||
uint32_t getDataAgeMillis() const;
|
uint32_t getDataAgeMillis() const;
|
||||||
|
|
||||||
VeDirectMpptController::spData_t getData(size_t idx = 0) const;
|
std::optional<VeDirectMpptController::spData_t> getData(size_t idx = 0) const;
|
||||||
|
|
||||||
// total output of all MPPT charge controllers in Watts
|
// total output of all MPPT charge controllers in Watts
|
||||||
int32_t getPowerOutputWatts() const;
|
int32_t getPowerOutputWatts() const;
|
||||||
@ -50,6 +52,8 @@ private:
|
|||||||
mutable std::mutex _mutex;
|
mutable std::mutex _mutex;
|
||||||
using controller_t = std::unique_ptr<VeDirectMpptController>;
|
using controller_t = std::unique_ptr<VeDirectMpptController>;
|
||||||
std::vector<controller_t> _controllers;
|
std::vector<controller_t> _controllers;
|
||||||
|
|
||||||
|
bool initController(int8_t rx, int8_t tx, bool logging, int hwSerialPort);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern VictronMpptClass VictronMppt;
|
extern VictronMpptClass VictronMppt;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ public:
|
|||||||
void deinit() final { }
|
void deinit() final { }
|
||||||
void loop() final;
|
void loop() final;
|
||||||
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
std::shared_ptr<BatteryStats> getStats() const final { return _stats; }
|
||||||
|
bool usesHwPort2() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t _lastUpdate = 0;
|
uint32_t _lastUpdate = 0;
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ArduinoJson.h"
|
#include "ArduinoJson.h"
|
||||||
|
#include "Configuration.h"
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <VeDirectMpptController.h>
|
#include <VeDirectMpptController.h>
|
||||||
@ -14,6 +15,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void generateJsonResponse(JsonVariant& root);
|
void generateJsonResponse(JsonVariant& root);
|
||||||
|
static void populateJson(const JsonObject &root, const VeDirectMpptController::spData_t &spMpptData);
|
||||||
void onLivedataStatus(AsyncWebServerRequest* request);
|
void onLivedataStatus(AsyncWebServerRequest* request);
|
||||||
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ private:
|
|||||||
|
|
||||||
uint32_t _lastWsPublish = 0;
|
uint32_t _lastWsPublish = 0;
|
||||||
uint32_t _dataAgeMillis = 0;
|
uint32_t _dataAgeMillis = 0;
|
||||||
static constexpr uint16_t _responseSize = 1024 + 128;
|
static constexpr uint16_t _responseSize = VICTRON_MAX_COUNT * (1024 + 128);
|
||||||
|
|
||||||
std::mutex _mutex;
|
std::mutex _mutex;
|
||||||
|
|
||||||
@ -31,4 +33,4 @@ private:
|
|||||||
|
|
||||||
Task _sendDataTask;
|
Task _sendDataTask;
|
||||||
void sendDataTaskCb();
|
void sendDataTaskCb();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "VeDirectMpptController.h"
|
#include "VeDirectMpptController.h"
|
||||||
|
|
||||||
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging)
|
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort)
|
||||||
{
|
{
|
||||||
VeDirectFrameHandler::init(rx, tx, msgOut, verboseLogging, 1);
|
VeDirectFrameHandler::init(rx, tx, msgOut, verboseLogging, hwSerialPort);
|
||||||
_spData = std::make_shared<veMpptStruct>();
|
_spData = std::make_shared<veMpptStruct>();
|
||||||
if (_verboseLogging) { _msgOut->println("Finished init MPPTController"); }
|
if (_verboseLogging) { _msgOut->println("Finished init MPPTController"); }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ class VeDirectMpptController : public VeDirectFrameHandler {
|
|||||||
public:
|
public:
|
||||||
VeDirectMpptController() = default;
|
VeDirectMpptController() = 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, uint16_t hwSerialPort);
|
||||||
bool isDataValid() const; // return true if data valid and not outdated
|
bool isDataValid() const; // return true if data valid and not outdated
|
||||||
|
|
||||||
struct veMpptStruct : veStruct {
|
struct veMpptStruct : veStruct {
|
||||||
@ -49,7 +49,7 @@ public:
|
|||||||
double VPV; // panel voltage in V
|
double VPV; // panel voltage in V
|
||||||
double IPV; // panel current in A (calculated)
|
double IPV; // panel current in A (calculated)
|
||||||
bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit)
|
bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit)
|
||||||
uint8_t CS; // current state of operation e. g. OFF or Bulk
|
uint8_t CS; // current state of operation e.g. OFF or Bulk
|
||||||
uint8_t ERR; // error code
|
uint8_t ERR; // error code
|
||||||
uint32_t OR; // off reason
|
uint32_t OR; // off reason
|
||||||
uint32_t HSDS; // day sequence number 1...365
|
uint32_t HSDS; // day sequence number 1...365
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include "JkBmsController.h"
|
#include "JkBmsController.h"
|
||||||
#include "VictronSmartShunt.h"
|
#include "VictronSmartShunt.h"
|
||||||
#include "MqttBattery.h"
|
#include "MqttBattery.h"
|
||||||
|
#include "SerialPortManager.h"
|
||||||
|
|
||||||
BatteryClass Battery;
|
BatteryClass Battery;
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ void BatteryClass::updateSettings()
|
|||||||
_upProvider->deinit();
|
_upProvider->deinit();
|
||||||
_upProvider = nullptr;
|
_upProvider = nullptr;
|
||||||
}
|
}
|
||||||
|
PortManager.invalidateBatteryPort();
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
if (!config.Battery.Enabled) { return; }
|
if (!config.Battery.Enabled) { return; }
|
||||||
@ -47,23 +49,32 @@ void BatteryClass::updateSettings()
|
|||||||
switch (config.Battery.Provider) {
|
switch (config.Battery.Provider) {
|
||||||
case 0:
|
case 0:
|
||||||
_upProvider = std::make_unique<PylontechCanReceiver>();
|
_upProvider = std::make_unique<PylontechCanReceiver>();
|
||||||
if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; }
|
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
_upProvider = std::make_unique<JkBms::Controller>();
|
_upProvider = std::make_unique<JkBms::Controller>();
|
||||||
if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; }
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
_upProvider = std::make_unique<MqttBattery>();
|
_upProvider = std::make_unique<MqttBattery>();
|
||||||
if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; }
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
_upProvider = std::make_unique<VictronSmartShunt>();
|
_upProvider = std::make_unique<VictronSmartShunt>();
|
||||||
if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; }
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
MessageOutput.printf("Unknown battery provider: %d\r\n", config.Battery.Provider);
|
MessageOutput.printf("Unknown battery provider: %d\r\n", config.Battery.Provider);
|
||||||
break;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_upProvider->usesHwPort2()) {
|
||||||
|
if (!PortManager.allocateBatteryPort(2)) {
|
||||||
|
MessageOutput.printf("[Battery] Serial port %d already in use. Initialization aborted!\r\n", 2);
|
||||||
|
_upProvider = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_upProvider->init(verboseLogging)) {
|
||||||
|
PortManager.invalidateBatteryPort();
|
||||||
|
_upProvider = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -427,4 +427,8 @@ void Controller::processDataPoints(DataPointContainer const& dataPoints)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Controller::usesHwPort2() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace JkBms */
|
} /* namespace JkBms */
|
||||||
|
|||||||
@ -112,3 +112,7 @@ void MqttBattery::onMqttMessageVoltage(espMqttClientTypes::MessageProperties con
|
|||||||
*voltage, topic);
|
*voltage, topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MqttBattery::usesHwPort2() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "VictronMppt.h"
|
#include "VictronMppt.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
MqttHandleVedirectHassClass MqttHandleVedirectHass;
|
MqttHandleVedirectHassClass MqttHandleVedirectHass;
|
||||||
@ -15,7 +15,7 @@ MqttHandleVedirectHassClass MqttHandleVedirectHass;
|
|||||||
void MqttHandleVedirectHassClass::init(Scheduler& scheduler)
|
void MqttHandleVedirectHassClass::init(Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
scheduler.addTask(_loopTask);
|
scheduler.addTask(_loopTask);
|
||||||
_loopTask.setCallback(std::bind(&MqttHandleVedirectHassClass::loop, this));
|
_loopTask.setCallback([this] { loop(); });
|
||||||
_loopTask.setIterations(TASK_FOREVER);
|
_loopTask.setIterations(TASK_FOREVER);
|
||||||
_loopTask.enable();
|
_loopTask.enable();
|
||||||
}
|
}
|
||||||
@ -55,43 +55,56 @@ void MqttHandleVedirectHassClass::publishConfig()
|
|||||||
if (!MqttSettings.getConnected()) {
|
if (!MqttSettings.getConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ensure data is revieved from victron
|
|
||||||
if (!VictronMppt.isDataValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// device info
|
// device info
|
||||||
publishBinarySensor("MPPT load output state", "mdi:export", "LOAD", "ON", "OFF");
|
for (int idx = 0; idx < VICTRON_MAX_COUNT; ++idx) {
|
||||||
publishSensor("MPPT serial number", "mdi:counter", "SER");
|
// ensure data is received from victron
|
||||||
publishSensor("MPPT firmware number", "mdi:counter", "FW");
|
if (!VictronMppt.isDataValid(idx)) {
|
||||||
publishSensor("MPPT state of operation", "mdi:wrench", "CS");
|
continue;
|
||||||
publishSensor("MPPT error code", "mdi:bell", "ERR");
|
}
|
||||||
publishSensor("MPPT off reason", "mdi:wrench", "OR");
|
|
||||||
publishSensor("MPPT tracker operation mode", "mdi:wrench", "MPPT");
|
|
||||||
publishSensor("MPPT Day sequence number (0...364)", "mdi:calendar-month-outline", "HSDS", NULL, "total", "d");
|
|
||||||
|
|
||||||
// battery info
|
std::optional<VeDirectMpptController::spData_t> spOptMpptData = VictronMppt.getData(idx);
|
||||||
publishSensor("Battery voltage", NULL, "V", "voltage", "measurement", "V");
|
if (!spOptMpptData.has_value()) {
|
||||||
publishSensor("Battery current", NULL, "I", "current", "measurement", "A");
|
continue;
|
||||||
publishSensor("Battery power (calculated)", NULL, "P", "power", "measurement", "W");
|
}
|
||||||
publishSensor("Battery efficiency (calculated)", NULL, "E", NULL, "measurement", "%");
|
|
||||||
|
VeDirectMpptController::spData_t &spMpptData = spOptMpptData.value();
|
||||||
// panel info
|
|
||||||
publishSensor("Panel voltage", NULL, "VPV", "voltage", "measurement", "V");
|
publishBinarySensor("MPPT load output state", "mdi:export", "LOAD", "ON", "OFF", spMpptData);
|
||||||
publishSensor("Panel current (calculated)", NULL, "IPV", "current", "measurement", "A");
|
publishSensor("MPPT serial number", "mdi:counter", "SER", nullptr, nullptr, nullptr, spMpptData);
|
||||||
publishSensor("Panel power", NULL, "PPV", "power", "measurement", "W");
|
publishSensor("MPPT firmware number", "mdi:counter", "FW", nullptr, nullptr, nullptr, spMpptData);
|
||||||
publishSensor("Panel yield total", NULL, "H19", "energy", "total_increasing", "kWh");
|
publishSensor("MPPT state of operation", "mdi:wrench", "CS", nullptr, nullptr, nullptr, spMpptData);
|
||||||
publishSensor("Panel yield today", NULL, "H20", "energy", "total", "kWh");
|
publishSensor("MPPT error code", "mdi:bell", "ERR", nullptr, nullptr, nullptr, spMpptData);
|
||||||
publishSensor("Panel maximum power today", NULL, "H21", "power", "measurement", "W");
|
publishSensor("MPPT off reason", "mdi:wrench", "OR", nullptr, nullptr, nullptr, spMpptData);
|
||||||
publishSensor("Panel yield yesterday", NULL, "H22", "energy", "total", "kWh");
|
publishSensor("MPPT tracker operation mode", "mdi:wrench", "MPPT", nullptr, nullptr, nullptr, spMpptData);
|
||||||
publishSensor("Panel maximum power yesterday", NULL, "H23", "power", "measurement", "W");
|
publishSensor("MPPT Day sequence number (0...364)", "mdi:calendar-month-outline", "HSDS", NULL, "total", "d", spMpptData);
|
||||||
|
|
||||||
|
// battery info
|
||||||
|
publishSensor("Battery voltage", NULL, "V", "voltage", "measurement", "V", spMpptData);
|
||||||
|
publishSensor("Battery current", NULL, "I", "current", "measurement", "A", spMpptData);
|
||||||
|
publishSensor("Battery power (calculated)", NULL, "P", "power", "measurement", "W", spMpptData);
|
||||||
|
publishSensor("Battery efficiency (calculated)", NULL, "E", NULL, "measurement", "%", spMpptData);
|
||||||
|
|
||||||
|
// panel info
|
||||||
|
publishSensor("Panel voltage", NULL, "VPV", "voltage", "measurement", "V", spMpptData);
|
||||||
|
publishSensor("Panel current (calculated)", NULL, "IPV", "current", "measurement", "A", spMpptData);
|
||||||
|
publishSensor("Panel power", NULL, "PPV", "power", "measurement", "W", spMpptData);
|
||||||
|
publishSensor("Panel yield total", NULL, "H19", "energy", "total_increasing", "kWh", spMpptData);
|
||||||
|
publishSensor("Panel yield today", NULL, "H20", "energy", "total", "kWh", spMpptData);
|
||||||
|
publishSensor("Panel maximum power today", NULL, "H21", "power", "measurement", "W", spMpptData);
|
||||||
|
publishSensor("Panel yield yesterday", NULL, "H22", "energy", "total", "kWh", spMpptData);
|
||||||
|
publishSensor("Panel maximum power yesterday", NULL, "H23", "power", "measurement", "W", spMpptData);
|
||||||
|
}
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement )
|
void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char *icon, const char *subTopic,
|
||||||
|
const char *deviceClass, const char *stateClass,
|
||||||
|
const char *unitOfMeasurement,
|
||||||
|
const VeDirectMpptController::spData_t &spMpptData)
|
||||||
{
|
{
|
||||||
String serial = VictronMppt.getData()->SER;
|
String serial = spMpptData->SER;
|
||||||
|
|
||||||
String sensorId = caption;
|
String sensorId = caption;
|
||||||
sensorId.replace(" ", "_");
|
sensorId.replace(" ", "_");
|
||||||
@ -126,7 +139,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
|||||||
}
|
}
|
||||||
|
|
||||||
JsonObject deviceObj = root.createNestedObject("dev");
|
JsonObject deviceObj = root.createNestedObject("dev");
|
||||||
createDeviceInfo(deviceObj);
|
createDeviceInfo(deviceObj, spMpptData);
|
||||||
|
|
||||||
if (Configuration.get().Mqtt.Hass.Expire) {
|
if (Configuration.get().Mqtt.Hass.Expire) {
|
||||||
root["exp_aft"] = Configuration.get().Mqtt.PublishInterval * 3;
|
root["exp_aft"] = Configuration.get().Mqtt.PublishInterval * 3;
|
||||||
@ -143,9 +156,11 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
|||||||
publish(configTopic, buffer);
|
publish(configTopic, buffer);
|
||||||
|
|
||||||
}
|
}
|
||||||
void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off)
|
void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const char *icon, const char *subTopic,
|
||||||
|
const char *payload_on, const char *payload_off,
|
||||||
|
const VeDirectMpptController::spData_t &spMpptData)
|
||||||
{
|
{
|
||||||
String serial = VictronMppt.getData()->SER;
|
String serial = spMpptData->SER;
|
||||||
|
|
||||||
String sensorId = caption;
|
String sensorId = caption;
|
||||||
sensorId.replace(" ", "_");
|
sensorId.replace(" ", "_");
|
||||||
@ -178,16 +193,16 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
JsonObject deviceObj = root.createNestedObject("dev");
|
JsonObject deviceObj = root.createNestedObject("dev");
|
||||||
createDeviceInfo(deviceObj);
|
createDeviceInfo(deviceObj, spMpptData);
|
||||||
|
|
||||||
char buffer[512];
|
char buffer[512];
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
publish(configTopic, buffer);
|
publish(configTopic, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
|
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject &object,
|
||||||
|
const VeDirectMpptController::spData_t &spMpptData)
|
||||||
{
|
{
|
||||||
auto spMpptData = VictronMppt.getData();
|
|
||||||
String serial = spMpptData->SER;
|
String serial = spMpptData->SER;
|
||||||
object["name"] = "Victron(" + serial + ")";
|
object["name"] = "Victron(" + serial + ")";
|
||||||
object["ids"] = serial;
|
object["ids"] = serial;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ MqttHandleVedirectClass MqttHandleVedirect;
|
|||||||
void MqttHandleVedirectClass::init(Scheduler& scheduler)
|
void MqttHandleVedirectClass::init(Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
scheduler.addTask(_loopTask);
|
scheduler.addTask(_loopTask);
|
||||||
_loopTask.setCallback(std::bind(&MqttHandleVedirectClass::loop, this));
|
_loopTask.setCallback([this] { loop(); });
|
||||||
_loopTask.setIterations(TASK_FOREVER);
|
_loopTask.setIterations(TASK_FOREVER);
|
||||||
_loopTask.enable();
|
_loopTask.enable();
|
||||||
|
|
||||||
@ -41,10 +41,6 @@ void MqttHandleVedirectClass::loop()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VictronMppt.isDataValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((millis() >= _nextPublishFull) || (millis() >= _nextPublishUpdatesOnly)) {
|
if ((millis() >= _nextPublishFull) || (millis() >= _nextPublishUpdatesOnly)) {
|
||||||
// determine if this cycle should publish full values or updates only
|
// determine if this cycle should publish full values or updates only
|
||||||
if (_nextPublishFull <= _nextPublishUpdatesOnly) {
|
if (_nextPublishFull <= _nextPublishUpdatesOnly) {
|
||||||
@ -62,82 +58,23 @@ void MqttHandleVedirectClass::loop()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto spMpptData = VictronMppt.getData();
|
for (int idx = 0; idx < VICTRON_MAX_COUNT; ++idx) {
|
||||||
String value;
|
if (!VictronMppt.isDataValid(idx)) {
|
||||||
String topic = "victron/";
|
continue;
|
||||||
topic.concat(spMpptData->SER);
|
}
|
||||||
topic.concat("/");
|
|
||||||
|
|
||||||
if (_PublishFull || spMpptData->PID != _kvFrame.PID)
|
std::optional<VeDirectMpptController::spData_t> spOptMpptData = VictronMppt.getData(idx);
|
||||||
MqttSettings.publish(topic + "PID", spMpptData->getPidAsString().data());
|
if (!spOptMpptData.has_value()) {
|
||||||
if (_PublishFull || strcmp(spMpptData->SER, _kvFrame.SER) != 0)
|
continue;
|
||||||
MqttSettings.publish(topic + "SER", spMpptData->SER );
|
}
|
||||||
if (_PublishFull || strcmp(spMpptData->FW, _kvFrame.FW) != 0)
|
|
||||||
MqttSettings.publish(topic + "FW", spMpptData->FW);
|
VeDirectMpptController::spData_t &spMpptData = spOptMpptData.value();
|
||||||
if (_PublishFull || spMpptData->LOAD != _kvFrame.LOAD)
|
|
||||||
MqttSettings.publish(topic + "LOAD", spMpptData->LOAD == true ? "ON": "OFF");
|
VeDirectMpptController::veMpptStruct _kvFrame = _kvFrames[spMpptData->SER];
|
||||||
if (_PublishFull || spMpptData->CS != _kvFrame.CS)
|
publish_mppt_data(spMpptData, _kvFrame);
|
||||||
MqttSettings.publish(topic + "CS", spMpptData->getCsAsString().data());
|
if (!_PublishFull) {
|
||||||
if (_PublishFull || spMpptData->ERR != _kvFrame.ERR)
|
_kvFrames[spMpptData->SER] = *spMpptData;
|
||||||
MqttSettings.publish(topic + "ERR", spMpptData->getErrAsString().data());
|
}
|
||||||
if (_PublishFull || spMpptData->OR != _kvFrame.OR)
|
|
||||||
MqttSettings.publish(topic + "OR", spMpptData->getOrAsString().data());
|
|
||||||
if (_PublishFull || spMpptData->MPPT != _kvFrame.MPPT)
|
|
||||||
MqttSettings.publish(topic + "MPPT", spMpptData->getMpptAsString().data());
|
|
||||||
if (_PublishFull || spMpptData->HSDS != _kvFrame.HSDS) {
|
|
||||||
value = spMpptData->HSDS;
|
|
||||||
MqttSettings.publish(topic + "HSDS", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->V != _kvFrame.V) {
|
|
||||||
value = spMpptData->V;
|
|
||||||
MqttSettings.publish(topic + "V", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->I != _kvFrame.I) {
|
|
||||||
value = spMpptData->I;
|
|
||||||
MqttSettings.publish(topic + "I", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->P != _kvFrame.P) {
|
|
||||||
value = spMpptData->P;
|
|
||||||
MqttSettings.publish(topic + "P", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->VPV != _kvFrame.VPV) {
|
|
||||||
value = spMpptData->VPV;
|
|
||||||
MqttSettings.publish(topic + "VPV", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->IPV != _kvFrame.IPV) {
|
|
||||||
value = spMpptData->IPV;
|
|
||||||
MqttSettings.publish(topic + "IPV", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->PPV != _kvFrame.PPV) {
|
|
||||||
value = spMpptData->PPV;
|
|
||||||
MqttSettings.publish(topic + "PPV", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->E != _kvFrame.E) {
|
|
||||||
value = spMpptData->E;
|
|
||||||
MqttSettings.publish(topic + "E", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->H19 != _kvFrame.H19) {
|
|
||||||
value = spMpptData->H19;
|
|
||||||
MqttSettings.publish(topic + "H19", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->H20 != _kvFrame.H20) {
|
|
||||||
value = spMpptData->H20;
|
|
||||||
MqttSettings.publish(topic + "H20", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->H21 != _kvFrame.H21) {
|
|
||||||
value = spMpptData->H21;
|
|
||||||
MqttSettings.publish(topic + "H21", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->H22 != _kvFrame.H22) {
|
|
||||||
value = spMpptData->H22;
|
|
||||||
MqttSettings.publish(topic + "H22", value);
|
|
||||||
}
|
|
||||||
if (_PublishFull || spMpptData->H23 != _kvFrame.H23) {
|
|
||||||
value = spMpptData->H23;
|
|
||||||
MqttSettings.publish(topic + "H23", value);
|
|
||||||
}
|
|
||||||
if (!_PublishFull) {
|
|
||||||
_kvFrame = *spMpptData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now calculate next points of time to publish
|
// now calculate next points of time to publish
|
||||||
@ -165,4 +102,81 @@ void MqttHandleVedirectClass::loop()
|
|||||||
MessageOutput.printf("MqttHandleVedirectClass::loop _nextPublishUpdatesOnly %u _nextPublishFull %u\r\n", _nextPublishUpdatesOnly, _nextPublishFull);
|
MessageOutput.printf("MqttHandleVedirectClass::loop _nextPublishUpdatesOnly %u _nextPublishFull %u\r\n", _nextPublishUpdatesOnly, _nextPublishFull);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::spData_t &spMpptData,
|
||||||
|
VeDirectMpptController::veMpptStruct &frame) const {
|
||||||
|
String value;
|
||||||
|
String topic = "victron/";
|
||||||
|
topic.concat(spMpptData->SER);
|
||||||
|
topic.concat("/");
|
||||||
|
|
||||||
|
if (_PublishFull || spMpptData->PID != frame.PID)
|
||||||
|
MqttSettings.publish(topic + "PID", spMpptData->getPidAsString().data());
|
||||||
|
if (_PublishFull || strcmp(spMpptData->SER, frame.SER) != 0)
|
||||||
|
MqttSettings.publish(topic + "SER", spMpptData->SER );
|
||||||
|
if (_PublishFull || strcmp(spMpptData->FW, frame.FW) != 0)
|
||||||
|
MqttSettings.publish(topic + "FW", spMpptData->FW);
|
||||||
|
if (_PublishFull || spMpptData->LOAD != frame.LOAD)
|
||||||
|
MqttSettings.publish(topic + "LOAD", spMpptData->LOAD ? "ON" : "OFF");
|
||||||
|
if (_PublishFull || spMpptData->CS != frame.CS)
|
||||||
|
MqttSettings.publish(topic + "CS", spMpptData->getCsAsString().data());
|
||||||
|
if (_PublishFull || spMpptData->ERR != frame.ERR)
|
||||||
|
MqttSettings.publish(topic + "ERR", spMpptData->getErrAsString().data());
|
||||||
|
if (_PublishFull || spMpptData->OR != frame.OR)
|
||||||
|
MqttSettings.publish(topic + "OR", spMpptData->getOrAsString().data());
|
||||||
|
if (_PublishFull || spMpptData->MPPT != frame.MPPT)
|
||||||
|
MqttSettings.publish(topic + "MPPT", spMpptData->getMpptAsString().data());
|
||||||
|
if (_PublishFull || spMpptData->HSDS != frame.HSDS) {
|
||||||
|
value = spMpptData->HSDS;
|
||||||
|
MqttSettings.publish(topic + "HSDS", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->V != frame.V) {
|
||||||
|
value = spMpptData->V;
|
||||||
|
MqttSettings.publish(topic + "V", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->I != frame.I) {
|
||||||
|
value = spMpptData->I;
|
||||||
|
MqttSettings.publish(topic + "I", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->P != frame.P) {
|
||||||
|
value = spMpptData->P;
|
||||||
|
MqttSettings.publish(topic + "P", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->VPV != frame.VPV) {
|
||||||
|
value = spMpptData->VPV;
|
||||||
|
MqttSettings.publish(topic + "VPV", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->IPV != frame.IPV) {
|
||||||
|
value = spMpptData->IPV;
|
||||||
|
MqttSettings.publish(topic + "IPV", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->PPV != frame.PPV) {
|
||||||
|
value = spMpptData->PPV;
|
||||||
|
MqttSettings.publish(topic + "PPV", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->E != frame.E) {
|
||||||
|
value = spMpptData->E;
|
||||||
|
MqttSettings.publish(topic + "E", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->H19 != frame.H19) {
|
||||||
|
value = spMpptData->H19;
|
||||||
|
MqttSettings.publish(topic + "H19", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->H20 != frame.H20) {
|
||||||
|
value = spMpptData->H20;
|
||||||
|
MqttSettings.publish(topic + "H20", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->H21 != frame.H21) {
|
||||||
|
value = spMpptData->H21;
|
||||||
|
MqttSettings.publish(topic + "H21", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->H22 != frame.H22) {
|
||||||
|
value = spMpptData->H22;
|
||||||
|
MqttSettings.publish(topic + "H22", value);
|
||||||
|
}
|
||||||
|
if (_PublishFull || spMpptData->H23 != frame.H23) {
|
||||||
|
value = spMpptData->H23;
|
||||||
|
MqttSettings.publish(topic + "H23", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -181,9 +181,12 @@ PinMappingClass::PinMappingClass()
|
|||||||
_pinMapping.display_clk = DISPLAY_CLK;
|
_pinMapping.display_clk = DISPLAY_CLK;
|
||||||
_pinMapping.display_cs = DISPLAY_CS;
|
_pinMapping.display_cs = DISPLAY_CS;
|
||||||
_pinMapping.display_reset = DISPLAY_RESET;
|
_pinMapping.display_reset = DISPLAY_RESET;
|
||||||
|
|
||||||
_pinMapping.victron_tx = VICTRON_PIN_TX;
|
|
||||||
_pinMapping.victron_rx = VICTRON_PIN_RX;
|
_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.battery_rx = BATTERY_PIN_RX;
|
_pinMapping.battery_rx = BATTERY_PIN_RX;
|
||||||
_pinMapping.battery_rxen = BATTERY_PIN_RXEN;
|
_pinMapping.battery_rxen = BATTERY_PIN_RXEN;
|
||||||
@ -259,6 +262,8 @@ bool PinMappingClass::init(const String& deviceMapping)
|
|||||||
|
|
||||||
_pinMapping.victron_rx = doc[i]["victron"]["rx"] | VICTRON_PIN_RX;
|
_pinMapping.victron_rx = doc[i]["victron"]["rx"] | VICTRON_PIN_RX;
|
||||||
_pinMapping.victron_tx = doc[i]["victron"]["tx"] | VICTRON_PIN_TX;
|
_pinMapping.victron_tx = doc[i]["victron"]["tx"] | VICTRON_PIN_TX;
|
||||||
|
_pinMapping.victron_rx2 = doc[i]["victron"]["rx2"] | VICTRON_PIN_RX;
|
||||||
|
_pinMapping.victron_tx2 = doc[i]["victron"]["tx2"] | VICTRON_PIN_TX;
|
||||||
|
|
||||||
_pinMapping.battery_rx = doc[i]["battery"]["rx"] | BATTERY_PIN_RX;
|
_pinMapping.battery_rx = doc[i]["battery"]["rx"] | BATTERY_PIN_RX;
|
||||||
_pinMapping.battery_rxen = doc[i]["battery"]["rxen"] | BATTERY_PIN_RXEN;
|
_pinMapping.battery_rxen = doc[i]["battery"]["rxen"] | BATTERY_PIN_RXEN;
|
||||||
|
|||||||
@ -266,6 +266,10 @@ bool PylontechCanReceiver::getBit(uint8_t value, uint8_t bit)
|
|||||||
return (value & (1 << bit)) >> bit;
|
return (value & (1 << bit)) >> bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PylontechCanReceiver::usesHwPort2() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef PYLONTECH_DUMMY
|
#ifdef PYLONTECH_DUMMY
|
||||||
void PylontechCanReceiver::dummyData()
|
void PylontechCanReceiver::dummyData()
|
||||||
{
|
{
|
||||||
|
|||||||
59
src/SerialPortManager.cpp
Normal file
59
src/SerialPortManager.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include "SerialPortManager.h"
|
||||||
|
#include "MessageOutput.h"
|
||||||
|
|
||||||
|
#define MAX_CONTROLLERS 3
|
||||||
|
|
||||||
|
SerialPortManager PortManager;
|
||||||
|
|
||||||
|
bool SerialPortManager::allocateBatteryPort(int port)
|
||||||
|
{
|
||||||
|
return allocatePort(port, Owner::BATTERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerialPortManager::allocateMpptPort(int port)
|
||||||
|
{
|
||||||
|
return allocatePort(port, Owner::MPPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerialPortManager::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 SerialPortManager::invalidateBatteryPort()
|
||||||
|
{
|
||||||
|
invalidate(Owner::BATTERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialPortManager::invalidateMpptPorts()
|
||||||
|
{
|
||||||
|
invalidate(Owner::MPPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialPortManager::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* SerialPortManager::print(Owner owner)
|
||||||
|
{
|
||||||
|
switch (owner) {
|
||||||
|
case BATTERY:
|
||||||
|
return "BATTERY";
|
||||||
|
case MPPT:
|
||||||
|
return "MPPT";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,13 +3,14 @@
|
|||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
|
#include "SerialPortManager.h"
|
||||||
|
|
||||||
VictronMpptClass VictronMppt;
|
VictronMpptClass VictronMppt;
|
||||||
|
|
||||||
void VictronMpptClass::init(Scheduler& scheduler)
|
void VictronMpptClass::init(Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
scheduler.addTask(_loopTask);
|
scheduler.addTask(_loopTask);
|
||||||
_loopTask.setCallback(std::bind(&VictronMpptClass::loop, this));
|
_loopTask.setCallback([this] { loop(); });
|
||||||
_loopTask.setIterations(TASK_FOREVER);
|
_loopTask.setIterations(TASK_FOREVER);
|
||||||
_loopTask.enable();
|
_loopTask.enable();
|
||||||
|
|
||||||
@ -21,24 +22,41 @@ void VictronMpptClass::updateSettings()
|
|||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
_controllers.clear();
|
_controllers.clear();
|
||||||
|
PortManager.invalidateMpptPorts();
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
if (!config.Vedirect.Enabled) { return; }
|
if (!config.Vedirect.Enabled) { return; }
|
||||||
|
|
||||||
const PinMapping_t& pin = PinMapping.get();
|
const PinMapping_t& pin = PinMapping.get();
|
||||||
int8_t rx = pin.victron_rx;
|
|
||||||
int8_t tx = pin.victron_tx;
|
|
||||||
|
|
||||||
MessageOutput.printf("[VictronMppt] rx = %d, tx = %d\r\n", rx, tx);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VictronMpptClass::initController(int8_t rx, int8_t tx, bool logging, int hwSerialPort)
|
||||||
|
{
|
||||||
|
MessageOutput.printf("[VictronMppt] rx = %d, tx = %d, hwSerialPort = %d\r\n", rx, tx, hwSerialPort);
|
||||||
|
|
||||||
if (rx < 0) {
|
if (rx < 0) {
|
||||||
MessageOutput.println("[VictronMppt] invalid pin config");
|
MessageOutput.printf("[VictronMppt] invalid pin config rx = %d, tx = %d\r\n", rx, tx);
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PortManager.allocateMpptPort(hwSerialPort)) {
|
||||||
|
MessageOutput.printf("[VictronMppt] Serial port %d already in use. Initialization aborted!\r\n",
|
||||||
|
hwSerialPort);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto upController = std::make_unique<VeDirectMpptController>();
|
auto upController = std::make_unique<VeDirectMpptController>();
|
||||||
upController->init(rx, tx, &MessageOutput, config.Vedirect.VerboseLogging);
|
upController->init(rx, tx, &MessageOutput, logging, hwSerialPort);
|
||||||
_controllers.push_back(std::move(upController));
|
_controllers.push_back(std::move(upController));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VictronMpptClass::loop()
|
void VictronMpptClass::loop()
|
||||||
@ -54,13 +72,24 @@ bool VictronMpptClass::isDataValid() const
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
for (auto const& upController : _controllers) {
|
for (auto const& upController: _controllers) {
|
||||||
if (!upController->isDataValid()) { return false; }
|
if (!upController->isDataValid()) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
return !_controllers.empty();
|
return !_controllers.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VictronMpptClass::isDataValid(size_t idx) const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
|
if (_controllers.empty() || idx >= _controllers.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _controllers[idx]->isDataValid();
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t VictronMpptClass::getDataAgeMillis() const
|
uint32_t VictronMpptClass::getDataAgeMillis() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
@ -81,17 +110,17 @@ uint32_t VictronMpptClass::getDataAgeMillis() const
|
|||||||
return age;
|
return age;
|
||||||
}
|
}
|
||||||
|
|
||||||
VeDirectMpptController::spData_t VictronMpptClass::getData(size_t idx) const
|
std::optional<VeDirectMpptController::spData_t> VictronMpptClass::getData(size_t idx) const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
if (_controllers.empty() || idx >= _controllers.size()) {
|
if (_controllers.empty() || idx >= _controllers.size()) {
|
||||||
MessageOutput.printf("ERROR: MPPT controller index %d is out of bounds (%d controllers)\r\n",
|
MessageOutput.printf("ERROR: MPPT controller index %d is out of bounds (%d controllers)\r\n",
|
||||||
idx, _controllers.size());
|
idx, _controllers.size());
|
||||||
return std::make_shared<VeDirectMpptController::veMpptStruct>();
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _controllers[idx]->getData();
|
return std::optional<VeDirectMpptController::spData_t>{_controllers[idx]->getData()};
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t VictronMpptClass::getPowerOutputWatts() const
|
int32_t VictronMpptClass::getPowerOutputWatts() const
|
||||||
|
|||||||
@ -34,3 +34,7 @@ void VictronSmartShunt::loop()
|
|||||||
_stats->updateFrom(VeDirectShunt.veFrame);
|
_stats->updateFrom(VeDirectShunt.veFrame);
|
||||||
_lastUpdate = VeDirectShunt.getLastUpdate();
|
_lastUpdate = VeDirectShunt.getLastUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VictronSmartShunt::usesHwPort2() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@ -86,9 +86,11 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
led["brightness"] = config.Led_Single[i].Brightness;
|
led["brightness"] = config.Led_Single[i].Brightness;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject victronPinObj = curPin.createNestedObject("victron");
|
auto victronPinObj = curPin.createNestedObject("victron");
|
||||||
victronPinObj["rx"] = pin.victron_rx;
|
victronPinObj["rx"] = pin.victron_rx;
|
||||||
victronPinObj["tx"] = pin.victron_tx;
|
victronPinObj["tx"] = pin.victron_tx;
|
||||||
|
victronPinObj["rx2"] = pin.victron_rx2;
|
||||||
|
victronPinObj["tx2"] = pin.victron_tx2;
|
||||||
|
|
||||||
JsonObject batteryPinObj = curPin.createNestedObject("battery");
|
JsonObject batteryPinObj = curPin.createNestedObject("battery");
|
||||||
batteryPinObj["rx"] = pin.battery_rx;
|
batteryPinObj["rx"] = pin.battery_rx;
|
||||||
|
|||||||
@ -32,7 +32,7 @@ void WebApiWsVedirectLiveClass::init(AsyncWebServer& server, Scheduler& schedule
|
|||||||
_server->addHandler(&_ws);
|
_server->addHandler(&_ws);
|
||||||
_ws.onEvent(std::bind(&WebApiWsVedirectLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
|
_ws.onEvent(std::bind(&WebApiWsVedirectLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
|
||||||
|
|
||||||
|
|
||||||
scheduler.addTask(_wsCleanupTask);
|
scheduler.addTask(_wsCleanupTask);
|
||||||
_wsCleanupTask.setCallback(std::bind(&WebApiWsVedirectLiveClass::wsCleanupTaskCb, this));
|
_wsCleanupTask.setCallback(std::bind(&WebApiWsVedirectLiveClass::wsCleanupTaskCb, this));
|
||||||
_wsCleanupTask.setIterations(TASK_FOREVER);
|
_wsCleanupTask.setIterations(TASK_FOREVER);
|
||||||
@ -61,13 +61,13 @@ void WebApiWsVedirectLiveClass::sendDataTaskCb()
|
|||||||
|
|
||||||
// we assume this loop to be running at least twice for every
|
// we assume this loop to be running at least twice for every
|
||||||
// update from a VE.Direct MPPT data producer, so _dataAgeMillis
|
// update from a VE.Direct MPPT data producer, so _dataAgeMillis
|
||||||
// acutally grows in between updates.
|
// actually grows in between updates.
|
||||||
auto lastDataAgeMillis = _dataAgeMillis;
|
auto lastDataAgeMillis = _dataAgeMillis;
|
||||||
_dataAgeMillis = VictronMppt.getDataAgeMillis();
|
_dataAgeMillis = VictronMppt.getDataAgeMillis();
|
||||||
|
|
||||||
// Update on ve.direct change or at least after 10 seconds
|
// Update on ve.direct change or at least after 10 seconds
|
||||||
if (millis() - _lastWsPublish > (10 * 1000) || lastDataAgeMillis > _dataAgeMillis) {
|
if (millis() - _lastWsPublish > (10 * 1000) || lastDataAgeMillis > _dataAgeMillis) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
DynamicJsonDocument root(_responseSize);
|
DynamicJsonDocument root(_responseSize);
|
||||||
@ -77,7 +77,7 @@ void WebApiWsVedirectLiveClass::sendDataTaskCb()
|
|||||||
|
|
||||||
String buffer;
|
String buffer;
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
|
|
||||||
if (Configuration.get().Security.AllowReadonly) {
|
if (Configuration.get().Security.AllowReadonly) {
|
||||||
_ws.setAuthentication("", "");
|
_ws.setAuthentication("", "");
|
||||||
} else {
|
} else {
|
||||||
@ -99,15 +99,36 @@ void WebApiWsVedirectLiveClass::sendDataTaskCb()
|
|||||||
|
|
||||||
void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
||||||
{
|
{
|
||||||
auto spMpptData = VictronMppt.getData();
|
root["vedirect"]["data_age"] = VictronMppt.getDataAgeMillis() / 1000;
|
||||||
|
const JsonArray &array = root["vedirect"].createNestedArray("devices");
|
||||||
|
|
||||||
|
for (int idx = 0; idx < VICTRON_MAX_COUNT; ++idx) {
|
||||||
|
std::optional<VeDirectMpptController::spData_t> spOptMpptData = VictronMppt.getData(idx);
|
||||||
|
if (!spOptMpptData.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
VeDirectMpptController::spData_t &spMpptData = spOptMpptData.value();
|
||||||
|
|
||||||
|
const JsonObject &nested = array.createNestedObject();
|
||||||
|
nested["age_critical"] = !VictronMppt.isDataValid(idx);
|
||||||
|
populateJson(nested, spMpptData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// power limiter state
|
||||||
|
root["dpl"]["PLSTATE"] = -1;
|
||||||
|
if (Configuration.get().PowerLimiter.Enabled)
|
||||||
|
root["dpl"]["PLSTATE"] = PowerLimiter.getPowerLimiterState();
|
||||||
|
root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowerLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDirectMpptController::spData_t &spMpptData) {
|
||||||
// device info
|
// device info
|
||||||
root["device"]["data_age"] = VictronMppt.getDataAgeMillis() / 1000;
|
|
||||||
root["device"]["age_critical"] = !VictronMppt.isDataValid();
|
|
||||||
root["device"]["PID"] = spMpptData->getPidAsString();
|
root["device"]["PID"] = spMpptData->getPidAsString();
|
||||||
root["device"]["SER"] = spMpptData->SER;
|
root["device"]["SER"] = spMpptData->SER;
|
||||||
root["device"]["FW"] = spMpptData->FW;
|
root["device"]["FW"] = spMpptData->FW;
|
||||||
root["device"]["LOAD"] = spMpptData->LOAD == true ? "ON" : "OFF";
|
root["device"]["LOAD"] = spMpptData->LOAD ? "ON" : "OFF";
|
||||||
root["device"]["CS"] = spMpptData->getCsAsString();
|
root["device"]["CS"] = spMpptData->getCsAsString();
|
||||||
root["device"]["ERR"] = spMpptData->getErrAsString();
|
root["device"]["ERR"] = spMpptData->getErrAsString();
|
||||||
root["device"]["OR"] = spMpptData->getOrAsString();
|
root["device"]["OR"] = spMpptData->getOrAsString();
|
||||||
@ -115,7 +136,7 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
root["device"]["HSDS"]["v"] = spMpptData->HSDS;
|
root["device"]["HSDS"]["v"] = spMpptData->HSDS;
|
||||||
root["device"]["HSDS"]["u"] = "d";
|
root["device"]["HSDS"]["u"] = "d";
|
||||||
|
|
||||||
// battery info
|
// battery info
|
||||||
root["output"]["P"]["v"] = spMpptData->P;
|
root["output"]["P"]["v"] = spMpptData->P;
|
||||||
root["output"]["P"]["u"] = "W";
|
root["output"]["P"]["u"] = "W";
|
||||||
root["output"]["P"]["d"] = 0;
|
root["output"]["P"]["d"] = 0;
|
||||||
@ -154,12 +175,6 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
root["input"]["MaximumPowerYesterday"]["v"] = spMpptData->H23;
|
root["input"]["MaximumPowerYesterday"]["v"] = spMpptData->H23;
|
||||||
root["input"]["MaximumPowerYesterday"]["u"] = "W";
|
root["input"]["MaximumPowerYesterday"]["u"] = "W";
|
||||||
root["input"]["MaximumPowerYesterday"]["d"] = 0;
|
root["input"]["MaximumPowerYesterday"]["d"] = 0;
|
||||||
|
|
||||||
// power limiter state
|
|
||||||
root["dpl"]["PLSTATE"] = -1;
|
|
||||||
if (Configuration.get().PowerLimiter.Enabled)
|
|
||||||
root["dpl"]["PLSTATE"] = PowerLimiter.getPowerLimiterState();
|
|
||||||
root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowerLimit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsVedirectLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
void WebApiWsVedirectLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
||||||
@ -199,4 +214,4 @@ void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
|||||||
MessageOutput.printf("Unknown exception in /api/vedirectlivedata/status. Reason: \"%s\".\r\n", exc.what());
|
MessageOutput.printf("Unknown exception in /api/vedirectlivedata/status. Reason: \"%s\".\r\n", exc.what());
|
||||||
WebApi.sendTooManyRequests(request);
|
WebApi.sendTooManyRequests(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,25 +9,25 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="row gy-3">
|
<div class="row gy-3">
|
||||||
<div class="tab-content col-sm-12 col-md-12" id="v-pills-tabContent">
|
<div class="tab-content col-sm-12 col-md-12" id="v-pills-tabContent">
|
||||||
<div class="card">
|
<div class="card" v-for="item in vedirect.devices">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center"
|
<div class="card-header d-flex justify-content-between align-items-center"
|
||||||
:class="{
|
:class="{
|
||||||
'text-bg-danger': vedirectData.age_critical,
|
'text-bg-danger': item.age_critical,
|
||||||
'text-bg-primary': !vedirectData.age_critical,
|
'text-bg-primary': !item.age_critical,
|
||||||
}">
|
}">
|
||||||
<div class="p-1 flex-grow-1">
|
<div class="p-1 flex-grow-1">
|
||||||
<div class="d-flex flex-wrap">
|
<div class="d-flex flex-wrap">
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ vedirectData.PID }}
|
{{ item.device.PID }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('vedirecthome.SerialNumber') }} {{ vedirectData.SER }}
|
{{ $t('vedirecthome.SerialNumber') }} {{ item.device.SER }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('vedirecthome.FirmwareNumber') }} {{ vedirectData.FW }}
|
{{ $t('vedirecthome.FirmwareNumber') }} {{ item.device.FW }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('vedirecthome.DataAge') }} {{ $t('vedirecthome.Seconds', {'val': vedirectData.data_age }) }}
|
{{ $t('vedirecthome.DataAge') }} {{ $t('vedirecthome.Seconds', {'val': vedirect.data_age }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -71,33 +71,33 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('vedirecthome.LoadOutputState') }}</th>
|
<th scope="row">{{ $t('vedirecthome.LoadOutputState') }}</th>
|
||||||
<td style="text-align: right">{{vedirectData.LOAD}}</td>
|
<td style="text-align: right">{{item.device.LOAD}}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('vedirecthome.StateOfOperation') }}</th>
|
<th scope="row">{{ $t('vedirecthome.StateOfOperation') }}</th>
|
||||||
<td style="text-align: right">{{vedirectData.CS}}</td>
|
<td style="text-align: right">{{item.device.CS}}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('vedirecthome.TrackerOperationMode') }}</th>
|
<th scope="row">{{ $t('vedirecthome.TrackerOperationMode') }}</th>
|
||||||
<td style="text-align: right">{{vedirectData.MPPT}}</td>
|
<td style="text-align: right">{{item.device.MPPT}}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('vedirecthome.OffReason') }}</th>
|
<th scope="row">{{ $t('vedirecthome.OffReason') }}</th>
|
||||||
<td style="text-align: right">{{vedirectData.OR}}</td>
|
<td style="text-align: right">{{item.device.OR}}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('vedirecthome.ErrorCode') }}</th>
|
<th scope="row">{{ $t('vedirecthome.ErrorCode') }}</th>
|
||||||
<td style="text-align: right">{{vedirectData.ERR}}</td>
|
<td style="text-align: right">{{item.device.ERR}}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('vedirecthome.DaySequenceNumber') }}</th>
|
<th scope="row">{{ $t('vedirecthome.DaySequenceNumber') }}</th>
|
||||||
<td style="text-align: right">{{vedirectData.HSDS.v}}</td>
|
<td style="text-align: right">{{item.device.HSDS.v}}</td>
|
||||||
<td>{{vedirectData.HSDS.u}}</td>
|
<td>{{item.device.HSDS.u}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -119,7 +119,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(prop, key) in vedirectOutput" v-bind:key="key">
|
<tr v-for="(prop, key) in item.output" v-bind:key="key">
|
||||||
<th scope="row">{{ $t('vedirecthome.output.' + key) }}</th>
|
<th scope="row">{{ $t('vedirecthome.output.' + key) }}</th>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{ $n(prop.v, 'decimal', {
|
{{ $n(prop.v, 'decimal', {
|
||||||
@ -149,7 +149,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(prop, key) in vedirectInput" v-bind:key="key">
|
<tr v-for="(prop, key) in item.input" v-bind:key="key">
|
||||||
<th scope="row">{{ $t('vedirecthome.input.' + key) }}</th>
|
<th scope="row">{{ $t('vedirecthome.input.' + key) }}</th>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{ $n(prop.v, 'decimal', {
|
{{ $n(prop.v, 'decimal', {
|
||||||
@ -167,7 +167,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -178,7 +178,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import type { DynamicPowerLimiter, VedirectDevice, VedirectOutput, VedirectInput } from '@/types/VedirectLiveDataStatus';
|
import type { DynamicPowerLimiter, Vedirect } from '@/types/VedirectLiveDataStatus';
|
||||||
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
|
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
|
||||||
import {
|
import {
|
||||||
BIconSun,
|
BIconSun,
|
||||||
@ -202,9 +202,7 @@ export default defineComponent({
|
|||||||
dataAgeInterval: 0,
|
dataAgeInterval: 0,
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
dplData: {} as DynamicPowerLimiter,
|
dplData: {} as DynamicPowerLimiter,
|
||||||
vedirectData: {} as VedirectDevice,
|
vedirect: {} as Vedirect,
|
||||||
vedirectOutput: {} as VedirectOutput,
|
|
||||||
vedirectInput: {} as VedirectInput,
|
|
||||||
isFirstFetchAfterConnect: true,
|
isFirstFetchAfterConnect: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -224,9 +222,7 @@ export default defineComponent({
|
|||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((root) => {
|
.then((root) => {
|
||||||
this.dplData = root["dpl"];
|
this.dplData = root["dpl"];
|
||||||
this.vedirectData = root["device"];
|
this.vedirect = root["vedirect"];
|
||||||
this.vedirectOutput = root["output"];
|
|
||||||
this.vedirectInput = root["input"];
|
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -244,9 +240,7 @@ export default defineComponent({
|
|||||||
console.log(event);
|
console.log(event);
|
||||||
var root = JSON.parse(event.data);
|
var root = JSON.parse(event.data);
|
||||||
this.dplData = root["dpl"];
|
this.dplData = root["dpl"];
|
||||||
this.vedirectData = root["device"];
|
this.vedirect = root["vedirect"];
|
||||||
this.vedirectOutput = root["output"];
|
|
||||||
this.vedirectInput = root["input"];
|
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
this.heartCheck(); // Reset heartbeat detection
|
this.heartCheck(); // Reset heartbeat detection
|
||||||
};
|
};
|
||||||
@ -263,8 +257,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
initDataAgeing() {
|
initDataAgeing() {
|
||||||
this.dataAgeInterval = setInterval(() => {
|
this.dataAgeInterval = setInterval(() => {
|
||||||
if (this.vedirectData) {
|
if (this.vedirect) {
|
||||||
this.vedirectData.data_age++;
|
this.vedirect.data_age++;
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
},
|
},
|
||||||
@ -293,4 +287,4 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -5,12 +5,22 @@ export interface DynamicPowerLimiter {
|
|||||||
PLLIMIT: number;
|
PLLIMIT: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Vedirect {
|
||||||
|
data_age: 0;
|
||||||
|
devices: Array<VedirectDevices>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VedirectDevices {
|
||||||
|
age_critical: boolean;
|
||||||
|
device: VedirectDevice;
|
||||||
|
input: VedirectInput;
|
||||||
|
output: VedirectOutput;
|
||||||
|
}
|
||||||
|
|
||||||
export interface VedirectDevice {
|
export interface VedirectDevice {
|
||||||
SER: string;
|
SER: string;
|
||||||
PID: string;
|
PID: string;
|
||||||
FW: string;
|
FW: string;
|
||||||
age_critical: boolean;
|
|
||||||
data_age: 0;
|
|
||||||
LOAD: ValueObject;
|
LOAD: ValueObject;
|
||||||
CS: ValueObject;
|
CS: ValueObject;
|
||||||
MPPT: ValueObject;
|
MPPT: ValueObject;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user