convert files with CRLF endings to LF endings
this only changes line endings. inspect this commit with command `git
show <commit-sha> --ignore-space-at-eol` and it will tell you that the
commit appears to be "empty" (since all changes are whitespace changes
near the end of a line, which are ignored in that git show command).
the files to be changed were found and updated using this command:
find lib src include webapp/src -type f | \
xargs grep --binary-files=without-match --files-with-matches \
$(printf '\r\n') | xargs dos2unix
the following files were restored afterwards, as they are using CRLF
line endings in the upstream as well:
- lib/CMT2300a/cmt2300a_defs.h
- lib/README
- include/README
This commit is contained in:
parent
2cc086335f
commit
83c59d7811
@ -1,158 +1,158 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "SPI.h"
|
#include "SPI.h"
|
||||||
#include <mcp_can.h>
|
#include <mcp_can.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
|
|
||||||
#ifndef HUAWEI_PIN_MISO
|
#ifndef HUAWEI_PIN_MISO
|
||||||
#define HUAWEI_PIN_MISO 12
|
#define HUAWEI_PIN_MISO 12
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef HUAWEI_PIN_MOSI
|
#ifndef HUAWEI_PIN_MOSI
|
||||||
#define HUAWEI_PIN_MOSI 13
|
#define HUAWEI_PIN_MOSI 13
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef HUAWEI_PIN_SCLK
|
#ifndef HUAWEI_PIN_SCLK
|
||||||
#define HUAWEI_PIN_SCLK 26
|
#define HUAWEI_PIN_SCLK 26
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef HUAWEI_PIN_IRQ
|
#ifndef HUAWEI_PIN_IRQ
|
||||||
#define HUAWEI_PIN_IRQ 25
|
#define HUAWEI_PIN_IRQ 25
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef HUAWEI_PIN_CS
|
#ifndef HUAWEI_PIN_CS
|
||||||
#define HUAWEI_PIN_CS 15
|
#define HUAWEI_PIN_CS 15
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef HUAWEI_PIN_POWER
|
#ifndef HUAWEI_PIN_POWER
|
||||||
#define HUAWEI_PIN_POWER 33
|
#define HUAWEI_PIN_POWER 33
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define HUAWEI_MINIMAL_OFFLINE_VOLTAGE 48
|
#define HUAWEI_MINIMAL_OFFLINE_VOLTAGE 48
|
||||||
#define HUAWEI_MINIMAL_ONLINE_VOLTAGE 42
|
#define HUAWEI_MINIMAL_ONLINE_VOLTAGE 42
|
||||||
|
|
||||||
#define MAX_CURRENT_MULTIPLIER 20
|
#define MAX_CURRENT_MULTIPLIER 20
|
||||||
|
|
||||||
// Index values for rec_values array
|
// Index values for rec_values array
|
||||||
#define HUAWEI_INPUT_POWER_IDX 0
|
#define HUAWEI_INPUT_POWER_IDX 0
|
||||||
#define HUAWEI_INPUT_FREQ_IDX 1
|
#define HUAWEI_INPUT_FREQ_IDX 1
|
||||||
#define HUAWEI_INPUT_CURRENT_IDX 2
|
#define HUAWEI_INPUT_CURRENT_IDX 2
|
||||||
#define HUAWEI_OUTPUT_POWER_IDX 3
|
#define HUAWEI_OUTPUT_POWER_IDX 3
|
||||||
#define HUAWEI_EFFICIENCY_IDX 4
|
#define HUAWEI_EFFICIENCY_IDX 4
|
||||||
#define HUAWEI_OUTPUT_VOLTAGE_IDX 5
|
#define HUAWEI_OUTPUT_VOLTAGE_IDX 5
|
||||||
#define HUAWEI_OUTPUT_CURRENT_MAX_IDX 6
|
#define HUAWEI_OUTPUT_CURRENT_MAX_IDX 6
|
||||||
#define HUAWEI_INPUT_VOLTAGE_IDX 7
|
#define HUAWEI_INPUT_VOLTAGE_IDX 7
|
||||||
#define HUAWEI_OUTPUT_TEMPERATURE_IDX 8
|
#define HUAWEI_OUTPUT_TEMPERATURE_IDX 8
|
||||||
#define HUAWEI_INPUT_TEMPERATURE_IDX 9
|
#define HUAWEI_INPUT_TEMPERATURE_IDX 9
|
||||||
#define HUAWEI_OUTPUT_CURRENT_IDX 10
|
#define HUAWEI_OUTPUT_CURRENT_IDX 10
|
||||||
#define HUAWEI_OUTPUT_CURRENT1_IDX 11
|
#define HUAWEI_OUTPUT_CURRENT1_IDX 11
|
||||||
|
|
||||||
// Defines and index values for tx_values array
|
// Defines and index values for tx_values array
|
||||||
#define HUAWEI_OFFLINE_VOLTAGE 0x01
|
#define HUAWEI_OFFLINE_VOLTAGE 0x01
|
||||||
#define HUAWEI_ONLINE_VOLTAGE 0x00
|
#define HUAWEI_ONLINE_VOLTAGE 0x00
|
||||||
#define HUAWEI_OFFLINE_CURRENT 0x04
|
#define HUAWEI_OFFLINE_CURRENT 0x04
|
||||||
#define HUAWEI_ONLINE_CURRENT 0x03
|
#define HUAWEI_ONLINE_CURRENT 0x03
|
||||||
|
|
||||||
// Modes of operation
|
// Modes of operation
|
||||||
#define HUAWEI_MODE_OFF 0
|
#define HUAWEI_MODE_OFF 0
|
||||||
#define HUAWEI_MODE_ON 1
|
#define HUAWEI_MODE_ON 1
|
||||||
#define HUAWEI_MODE_AUTO_EXT 2
|
#define HUAWEI_MODE_AUTO_EXT 2
|
||||||
#define HUAWEI_MODE_AUTO_INT 3
|
#define HUAWEI_MODE_AUTO_INT 3
|
||||||
|
|
||||||
// Error codes
|
// Error codes
|
||||||
#define HUAWEI_ERROR_CODE_RX 0x01
|
#define HUAWEI_ERROR_CODE_RX 0x01
|
||||||
#define HUAWEI_ERROR_CODE_TX 0x02
|
#define HUAWEI_ERROR_CODE_TX 0x02
|
||||||
|
|
||||||
// Wait time/current before shuting down the PSU / charger
|
// Wait time/current before shuting down the PSU / charger
|
||||||
// This is set to allow the fan to run for some time
|
// This is set to allow the fan to run for some time
|
||||||
#define HUAWEI_AUTO_MODE_SHUTDOWN_DELAY 60000
|
#define HUAWEI_AUTO_MODE_SHUTDOWN_DELAY 60000
|
||||||
#define HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT 0.75
|
#define HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT 0.75
|
||||||
|
|
||||||
// Updateinterval used to request new values from the PSU
|
// Updateinterval used to request new values from the PSU
|
||||||
#define HUAWEI_DATA_REQUEST_INTERVAL_MS 2500
|
#define HUAWEI_DATA_REQUEST_INTERVAL_MS 2500
|
||||||
|
|
||||||
typedef struct RectifierParameters {
|
typedef struct RectifierParameters {
|
||||||
float input_voltage;
|
float input_voltage;
|
||||||
float input_frequency;
|
float input_frequency;
|
||||||
float input_current;
|
float input_current;
|
||||||
float input_power;
|
float input_power;
|
||||||
float input_temp;
|
float input_temp;
|
||||||
float efficiency;
|
float efficiency;
|
||||||
float output_voltage;
|
float output_voltage;
|
||||||
float output_current;
|
float output_current;
|
||||||
float max_output_current;
|
float max_output_current;
|
||||||
float output_power;
|
float output_power;
|
||||||
float output_temp;
|
float output_temp;
|
||||||
float amp_hour;
|
float amp_hour;
|
||||||
} RectifierParameters_t;
|
} RectifierParameters_t;
|
||||||
|
|
||||||
class HuaweiCanCommClass {
|
class HuaweiCanCommClass {
|
||||||
public:
|
public:
|
||||||
bool init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk,
|
bool init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk,
|
||||||
uint8_t huawei_irq, uint8_t huawei_cs, uint32_t frequency);
|
uint8_t huawei_irq, uint8_t huawei_cs, uint32_t frequency);
|
||||||
void loop();
|
void loop();
|
||||||
bool gotNewRxDataFrame(bool clear);
|
bool gotNewRxDataFrame(bool clear);
|
||||||
uint8_t getErrorCode(bool clear);
|
uint8_t getErrorCode(bool clear);
|
||||||
uint32_t getParameterValue(uint8_t parameter);
|
uint32_t getParameterValue(uint8_t parameter);
|
||||||
void setParameterValue(uint16_t in, uint8_t parameterType);
|
void setParameterValue(uint16_t in, uint8_t parameterType);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void sendRequest();
|
void sendRequest();
|
||||||
|
|
||||||
SPIClass *SPI;
|
SPIClass *SPI;
|
||||||
MCP_CAN *_CAN;
|
MCP_CAN *_CAN;
|
||||||
uint8_t _huaweiIrq; // IRQ pin
|
uint8_t _huaweiIrq; // IRQ pin
|
||||||
uint32_t _nextRequestMillis = 0; // When to send next data request to PSU
|
uint32_t _nextRequestMillis = 0; // When to send next data request to PSU
|
||||||
|
|
||||||
std::mutex _mutex;
|
std::mutex _mutex;
|
||||||
|
|
||||||
uint32_t _recValues[12];
|
uint32_t _recValues[12];
|
||||||
uint16_t _txValues[5];
|
uint16_t _txValues[5];
|
||||||
bool _hasNewTxValue[5];
|
bool _hasNewTxValue[5];
|
||||||
|
|
||||||
uint8_t _errorCode;
|
uint8_t _errorCode;
|
||||||
bool _completeUpdateReceived;
|
bool _completeUpdateReceived;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HuaweiCanClass {
|
class HuaweiCanClass {
|
||||||
public:
|
public:
|
||||||
void init(Scheduler& scheduler, uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power);
|
void init(Scheduler& scheduler, uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power);
|
||||||
void updateSettings(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power);
|
void updateSettings(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power);
|
||||||
void setValue(float in, uint8_t parameterType);
|
void setValue(float in, uint8_t parameterType);
|
||||||
void setMode(uint8_t mode);
|
void setMode(uint8_t mode);
|
||||||
|
|
||||||
RectifierParameters_t * get();
|
RectifierParameters_t * get();
|
||||||
uint32_t getLastUpdate() const { return _lastUpdateReceivedMillis; };
|
uint32_t getLastUpdate() const { return _lastUpdateReceivedMillis; };
|
||||||
bool getAutoPowerStatus() const { return _autoPowerEnabled; };
|
bool getAutoPowerStatus() const { return _autoPowerEnabled; };
|
||||||
uint8_t getMode() const { return _mode; };
|
uint8_t getMode() const { return _mode; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
void processReceivedParameters();
|
void processReceivedParameters();
|
||||||
void _setValue(float in, uint8_t parameterType);
|
void _setValue(float in, uint8_t parameterType);
|
||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
TaskHandle_t _HuaweiCanCommunicationTaskHdl = NULL;
|
TaskHandle_t _HuaweiCanCommunicationTaskHdl = NULL;
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
uint8_t _huaweiPower; // Power pin
|
uint8_t _huaweiPower; // Power pin
|
||||||
uint8_t _mode = HUAWEI_MODE_AUTO_EXT;
|
uint8_t _mode = HUAWEI_MODE_AUTO_EXT;
|
||||||
|
|
||||||
RectifierParameters_t _rp;
|
RectifierParameters_t _rp;
|
||||||
|
|
||||||
uint32_t _lastUpdateReceivedMillis; // Timestamp for last data seen from the PSU
|
uint32_t _lastUpdateReceivedMillis; // Timestamp for last data seen from the PSU
|
||||||
uint32_t _outputCurrentOnSinceMillis; // Timestamp since when the PSU was idle at zero amps
|
uint32_t _outputCurrentOnSinceMillis; // Timestamp since when the PSU was idle at zero amps
|
||||||
uint32_t _nextAutoModePeriodicIntMillis; // When to set the next output voltage in automatic mode
|
uint32_t _nextAutoModePeriodicIntMillis; // When to set the next output voltage in automatic mode
|
||||||
uint32_t _lastPowerMeterUpdateReceivedMillis; // Timestamp of last seen power meter value
|
uint32_t _lastPowerMeterUpdateReceivedMillis; // Timestamp of last seen power meter value
|
||||||
uint32_t _autoModeBlockedTillMillis = 0; // Timestamp to block running auto mode for some time
|
uint32_t _autoModeBlockedTillMillis = 0; // Timestamp to block running auto mode for some time
|
||||||
|
|
||||||
uint8_t _autoPowerEnabledCounter = 0;
|
uint8_t _autoPowerEnabledCounter = 0;
|
||||||
bool _autoPowerEnabled = false;
|
bool _autoPowerEnabled = false;
|
||||||
bool _batteryEmergencyCharging = false;
|
bool _batteryEmergencyCharging = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern HuaweiCanClass HuaweiCan;
|
extern HuaweiCanClass HuaweiCan;
|
||||||
extern HuaweiCanCommClass HuaweiCanComm;
|
extern HuaweiCanCommClass HuaweiCanComm;
|
||||||
|
|||||||
@ -1,41 +1,41 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AsyncWebSocket.h>
|
#include <AsyncWebSocket.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <Print.h>
|
#include <Print.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
class MessageOutputClass : public Print {
|
class MessageOutputClass : public Print {
|
||||||
public:
|
public:
|
||||||
MessageOutputClass();
|
MessageOutputClass();
|
||||||
void init(Scheduler& scheduler);
|
void init(Scheduler& scheduler);
|
||||||
size_t write(uint8_t c) override;
|
size_t write(uint8_t c) override;
|
||||||
size_t write(const uint8_t* buffer, size_t size) override;
|
size_t write(const uint8_t* buffer, size_t size) override;
|
||||||
void register_ws_output(AsyncWebSocket* output);
|
void register_ws_output(AsyncWebSocket* output);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
using message_t = std::vector<uint8_t>;
|
using message_t = std::vector<uint8_t>;
|
||||||
|
|
||||||
// we keep a buffer for every task and only write complete lines to the
|
// we keep a buffer for every task and only write complete lines to the
|
||||||
// serial output and then move them to be pushed through the websocket.
|
// serial output and then move them to be pushed through the websocket.
|
||||||
// this way we prevent mangling of messages from different contexts.
|
// this way we prevent mangling of messages from different contexts.
|
||||||
std::unordered_map<TaskHandle_t, message_t> _task_messages;
|
std::unordered_map<TaskHandle_t, message_t> _task_messages;
|
||||||
std::queue<message_t> _lines;
|
std::queue<message_t> _lines;
|
||||||
|
|
||||||
AsyncWebSocket* _ws = nullptr;
|
AsyncWebSocket* _ws = nullptr;
|
||||||
|
|
||||||
std::mutex _msgLock;
|
std::mutex _msgLock;
|
||||||
|
|
||||||
void serialWrite(message_t const& m);
|
void serialWrite(message_t const& m);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MessageOutputClass MessageOutput;
|
extern MessageOutputClass MessageOutput;
|
||||||
|
|||||||
@ -1,44 +1,44 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include <Huawei_can.h>
|
#include <Huawei_can.h>
|
||||||
#include <espMqttClient.h>
|
#include <espMqttClient.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
class MqttHandleHuaweiClass {
|
class MqttHandleHuaweiClass {
|
||||||
public:
|
public:
|
||||||
void init(Scheduler& scheduler);
|
void init(Scheduler& scheduler);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
enum class Topic : unsigned {
|
enum class Topic : unsigned {
|
||||||
LimitOnlineVoltage,
|
LimitOnlineVoltage,
|
||||||
LimitOnlineCurrent,
|
LimitOnlineCurrent,
|
||||||
LimitOfflineVoltage,
|
LimitOfflineVoltage,
|
||||||
LimitOfflineCurrent,
|
LimitOfflineCurrent,
|
||||||
Mode
|
Mode
|
||||||
};
|
};
|
||||||
|
|
||||||
void onMqttMessage(Topic t,
|
void onMqttMessage(Topic t,
|
||||||
const espMqttClientTypes::MessageProperties& properties,
|
const espMqttClientTypes::MessageProperties& properties,
|
||||||
const char* topic, const uint8_t* payload, size_t len,
|
const char* topic, const uint8_t* payload, size_t len,
|
||||||
size_t index, size_t total);
|
size_t index, size_t total);
|
||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
uint32_t _lastPublishStats;
|
uint32_t _lastPublishStats;
|
||||||
uint32_t _lastPublish;
|
uint32_t _lastPublish;
|
||||||
|
|
||||||
// MQTT callbacks to process updates on subscribed topics are executed in
|
// MQTT callbacks to process updates on subscribed topics are executed in
|
||||||
// the MQTT thread's context. we use this queue to switch processing the
|
// the MQTT thread's context. we use this queue to switch processing the
|
||||||
// user requests into the main loop's context (TaskScheduler context).
|
// user requests into the main loop's context (TaskScheduler context).
|
||||||
mutable std::mutex _mqttMutex;
|
mutable std::mutex _mqttMutex;
|
||||||
std::deque<std::function<void()>> _mqttCallbacks;
|
std::deque<std::function<void()>> _mqttCallbacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MqttHandleHuaweiClass MqttHandleHuawei;
|
extern MqttHandleHuaweiClass MqttHandleHuawei;
|
||||||
@ -1,45 +1,45 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include <espMqttClient.h>
|
#include <espMqttClient.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
class MqttHandlePowerLimiterClass {
|
class MqttHandlePowerLimiterClass {
|
||||||
public:
|
public:
|
||||||
void init(Scheduler& scheduler);
|
void init(Scheduler& scheduler);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
enum class MqttPowerLimiterCommand : unsigned {
|
enum class MqttPowerLimiterCommand : unsigned {
|
||||||
Mode,
|
Mode,
|
||||||
BatterySoCStartThreshold,
|
BatterySoCStartThreshold,
|
||||||
BatterySoCStopThreshold,
|
BatterySoCStopThreshold,
|
||||||
FullSolarPassthroughSoC,
|
FullSolarPassthroughSoC,
|
||||||
VoltageStartThreshold,
|
VoltageStartThreshold,
|
||||||
VoltageStopThreshold,
|
VoltageStopThreshold,
|
||||||
FullSolarPassThroughStartVoltage,
|
FullSolarPassThroughStartVoltage,
|
||||||
FullSolarPassThroughStopVoltage,
|
FullSolarPassThroughStopVoltage,
|
||||||
UpperPowerLimit,
|
UpperPowerLimit,
|
||||||
TargetPowerConsumption
|
TargetPowerConsumption
|
||||||
};
|
};
|
||||||
|
|
||||||
void onMqttCmd(MqttPowerLimiterCommand command, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
|
void onMqttCmd(MqttPowerLimiterCommand command, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
|
||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
uint32_t _lastPublishStats;
|
uint32_t _lastPublishStats;
|
||||||
uint32_t _lastPublish;
|
uint32_t _lastPublish;
|
||||||
|
|
||||||
// MQTT callbacks to process updates on subscribed topics are executed in
|
// MQTT callbacks to process updates on subscribed topics are executed in
|
||||||
// the MQTT thread's context. we use this queue to switch processing the
|
// the MQTT thread's context. we use this queue to switch processing the
|
||||||
// user requests into the main loop's context (TaskScheduler context).
|
// user requests into the main loop's context (TaskScheduler context).
|
||||||
mutable std::mutex _mqttMutex;
|
mutable std::mutex _mqttMutex;
|
||||||
std::deque<std::function<void()>> _mqttCallbacks;
|
std::deque<std::function<void()>> _mqttCallbacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
|
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <AsyncJson.h>
|
#include <AsyncJson.h>
|
||||||
|
|
||||||
class WebApiHuaweiClass {
|
class WebApiHuaweiClass {
|
||||||
public:
|
public:
|
||||||
void init(AsyncWebServer& server, Scheduler& scheduler);
|
void init(AsyncWebServer& server, Scheduler& scheduler);
|
||||||
void getJsonData(JsonVariant& root);
|
void getJsonData(JsonVariant& root);
|
||||||
private:
|
private:
|
||||||
void onStatus(AsyncWebServerRequest* request);
|
void onStatus(AsyncWebServerRequest* request);
|
||||||
void onAdminGet(AsyncWebServerRequest* request);
|
void onAdminGet(AsyncWebServerRequest* request);
|
||||||
void onAdminPost(AsyncWebServerRequest* request);
|
void onAdminPost(AsyncWebServerRequest* request);
|
||||||
void onPost(AsyncWebServerRequest* request);
|
void onPost(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
};
|
};
|
||||||
@ -1,29 +1,29 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ArduinoJson.h"
|
#include "ArduinoJson.h"
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
class WebApiWsHuaweiLiveClass {
|
class WebApiWsHuaweiLiveClass {
|
||||||
public:
|
public:
|
||||||
WebApiWsHuaweiLiveClass();
|
WebApiWsHuaweiLiveClass();
|
||||||
void init(AsyncWebServer& server, Scheduler& scheduler);
|
void init(AsyncWebServer& server, Scheduler& scheduler);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void generateCommonJsonResponse(JsonVariant& root);
|
void generateCommonJsonResponse(JsonVariant& root);
|
||||||
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);
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
AsyncWebSocket _ws;
|
AsyncWebSocket _ws;
|
||||||
|
|
||||||
std::mutex _mutex;
|
std::mutex _mutex;
|
||||||
|
|
||||||
Task _wsCleanupTask;
|
Task _wsCleanupTask;
|
||||||
void wsCleanupTaskCb();
|
void wsCleanupTaskCb();
|
||||||
|
|
||||||
Task _sendDataTask;
|
Task _sendDataTask;
|
||||||
void sendDataTaskCb();
|
void sendDataTaskCb();
|
||||||
};
|
};
|
||||||
@ -1,32 +1,32 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ArduinoJson.h"
|
#include "ArduinoJson.h"
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
class WebApiWsBatteryLiveClass {
|
class WebApiWsBatteryLiveClass {
|
||||||
public:
|
public:
|
||||||
WebApiWsBatteryLiveClass();
|
WebApiWsBatteryLiveClass();
|
||||||
void init(AsyncWebServer& server, Scheduler& scheduler);
|
void init(AsyncWebServer& server, Scheduler& scheduler);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void generateCommonJsonResponse(JsonVariant& root);
|
void generateCommonJsonResponse(JsonVariant& root);
|
||||||
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);
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
AsyncWebSocket _ws;
|
AsyncWebSocket _ws;
|
||||||
|
|
||||||
uint32_t _lastUpdateCheck = 0;
|
uint32_t _lastUpdateCheck = 0;
|
||||||
static constexpr uint16_t _responseSize = 1024 + 512;
|
static constexpr uint16_t _responseSize = 1024 + 512;
|
||||||
|
|
||||||
std::mutex _mutex;
|
std::mutex _mutex;
|
||||||
|
|
||||||
Task _wsCleanupTask;
|
Task _wsCleanupTask;
|
||||||
void wsCleanupTaskCb();
|
void wsCleanupTaskCb();
|
||||||
|
|
||||||
Task _sendDataTask;
|
Task _sendDataTask;
|
||||||
void sendDataTaskCb();
|
void sendDataTaskCb();
|
||||||
};
|
};
|
||||||
1052
src/Huawei_can.cpp
1052
src/Huawei_can.cpp
File diff suppressed because it is too large
Load Diff
@ -1,114 +1,114 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2024 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
|
|
||||||
MessageOutputClass MessageOutput;
|
MessageOutputClass MessageOutput;
|
||||||
|
|
||||||
MessageOutputClass::MessageOutputClass()
|
MessageOutputClass::MessageOutputClass()
|
||||||
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MessageOutputClass::loop, this))
|
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MessageOutputClass::loop, this))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageOutputClass::init(Scheduler& scheduler)
|
void MessageOutputClass::init(Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
scheduler.addTask(_loopTask);
|
scheduler.addTask(_loopTask);
|
||||||
_loopTask.enable();
|
_loopTask.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageOutputClass::register_ws_output(AsyncWebSocket* output)
|
void MessageOutputClass::register_ws_output(AsyncWebSocket* output)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_msgLock);
|
std::lock_guard<std::mutex> lock(_msgLock);
|
||||||
|
|
||||||
_ws = output;
|
_ws = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageOutputClass::serialWrite(MessageOutputClass::message_t const& m)
|
void MessageOutputClass::serialWrite(MessageOutputClass::message_t const& m)
|
||||||
{
|
{
|
||||||
// operator bool() of HWCDC returns false if the device is not attached to
|
// operator bool() of HWCDC returns false if the device is not attached to
|
||||||
// a USB host. in general it makes sense to skip writing entirely if the
|
// a USB host. in general it makes sense to skip writing entirely if the
|
||||||
// default serial port is not ready.
|
// default serial port is not ready.
|
||||||
if (!Serial) { return; }
|
if (!Serial) { return; }
|
||||||
|
|
||||||
size_t written = 0;
|
size_t written = 0;
|
||||||
while (written < m.size()) {
|
while (written < m.size()) {
|
||||||
written += Serial.write(m.data() + written, m.size() - written);
|
written += Serial.write(m.data() + written, m.size() - written);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t MessageOutputClass::write(uint8_t c)
|
size_t MessageOutputClass::write(uint8_t c)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_msgLock);
|
std::lock_guard<std::mutex> lock(_msgLock);
|
||||||
|
|
||||||
auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t());
|
auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t());
|
||||||
auto iter = res.first;
|
auto iter = res.first;
|
||||||
auto& message = iter->second;
|
auto& message = iter->second;
|
||||||
|
|
||||||
message.push_back(c);
|
message.push_back(c);
|
||||||
|
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
serialWrite(message);
|
serialWrite(message);
|
||||||
_lines.emplace(std::move(message));
|
_lines.emplace(std::move(message));
|
||||||
_task_messages.erase(iter);
|
_task_messages.erase(iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t MessageOutputClass::write(const uint8_t *buffer, size_t size)
|
size_t MessageOutputClass::write(const uint8_t *buffer, size_t size)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_msgLock);
|
std::lock_guard<std::mutex> lock(_msgLock);
|
||||||
|
|
||||||
auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t());
|
auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t());
|
||||||
auto iter = res.first;
|
auto iter = res.first;
|
||||||
auto& message = iter->second;
|
auto& message = iter->second;
|
||||||
|
|
||||||
message.reserve(message.size() + size);
|
message.reserve(message.size() + size);
|
||||||
|
|
||||||
for (size_t idx = 0; idx < size; ++idx) {
|
for (size_t idx = 0; idx < size; ++idx) {
|
||||||
uint8_t c = buffer[idx];
|
uint8_t c = buffer[idx];
|
||||||
|
|
||||||
message.push_back(c);
|
message.push_back(c);
|
||||||
|
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
serialWrite(message);
|
serialWrite(message);
|
||||||
_lines.emplace(std::move(message));
|
_lines.emplace(std::move(message));
|
||||||
message.clear();
|
message.clear();
|
||||||
message.reserve(size - idx - 1);
|
message.reserve(size - idx - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.empty()) { _task_messages.erase(iter); }
|
if (message.empty()) { _task_messages.erase(iter); }
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageOutputClass::loop()
|
void MessageOutputClass::loop()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_msgLock);
|
std::lock_guard<std::mutex> lock(_msgLock);
|
||||||
|
|
||||||
// clean up (possibly filled) buffers of deleted tasks
|
// clean up (possibly filled) buffers of deleted tasks
|
||||||
auto map_iter = _task_messages.begin();
|
auto map_iter = _task_messages.begin();
|
||||||
while (map_iter != _task_messages.end()) {
|
while (map_iter != _task_messages.end()) {
|
||||||
if (eTaskGetState(map_iter->first) == eDeleted) {
|
if (eTaskGetState(map_iter->first) == eDeleted) {
|
||||||
map_iter = _task_messages.erase(map_iter);
|
map_iter = _task_messages.erase(map_iter);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
++map_iter;
|
++map_iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_ws) {
|
if (!_ws) {
|
||||||
while (!_lines.empty()) {
|
while (!_lines.empty()) {
|
||||||
_lines.pop(); // do not hog memory
|
_lines.pop(); // do not hog memory
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!_lines.empty() && _ws->availableForWriteAll()) {
|
while (!_lines.empty() && _ws->availableForWriteAll()) {
|
||||||
_ws->textAll(std::make_shared<message_t>(std::move(_lines.front())));
|
_ws->textAll(std::make_shared<message_t>(std::move(_lines.front())));
|
||||||
_lines.pop();
|
_lines.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,162 +1,162 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Thomas Basler and others
|
* Copyright (C) 2022 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "MqttHandleHuawei.h"
|
#include "MqttHandleHuawei.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "Huawei_can.h"
|
#include "Huawei_can.h"
|
||||||
// #include "Failsafe.h"
|
// #include "Failsafe.h"
|
||||||
#include "WebApi_Huawei.h"
|
#include "WebApi_Huawei.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
MqttHandleHuaweiClass MqttHandleHuawei;
|
MqttHandleHuaweiClass MqttHandleHuawei;
|
||||||
|
|
||||||
void MqttHandleHuaweiClass::init(Scheduler& scheduler)
|
void MqttHandleHuaweiClass::init(Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
scheduler.addTask(_loopTask);
|
scheduler.addTask(_loopTask);
|
||||||
_loopTask.setCallback(std::bind(&MqttHandleHuaweiClass::loop, this));
|
_loopTask.setCallback(std::bind(&MqttHandleHuaweiClass::loop, this));
|
||||||
_loopTask.setIterations(TASK_FOREVER);
|
_loopTask.setIterations(TASK_FOREVER);
|
||||||
_loopTask.enable();
|
_loopTask.enable();
|
||||||
|
|
||||||
String const& prefix = MqttSettings.getPrefix();
|
String const& prefix = MqttSettings.getPrefix();
|
||||||
|
|
||||||
auto subscribe = [&prefix, this](char const* subTopic, Topic t) {
|
auto subscribe = [&prefix, this](char const* subTopic, Topic t) {
|
||||||
String fullTopic(prefix + "huawei/cmd/" + subTopic);
|
String fullTopic(prefix + "huawei/cmd/" + subTopic);
|
||||||
MqttSettings.subscribe(fullTopic.c_str(), 0,
|
MqttSettings.subscribe(fullTopic.c_str(), 0,
|
||||||
std::bind(&MqttHandleHuaweiClass::onMqttMessage, this, t,
|
std::bind(&MqttHandleHuaweiClass::onMqttMessage, this, t,
|
||||||
std::placeholders::_1, std::placeholders::_2,
|
std::placeholders::_1, std::placeholders::_2,
|
||||||
std::placeholders::_3, std::placeholders::_4,
|
std::placeholders::_3, std::placeholders::_4,
|
||||||
std::placeholders::_5, std::placeholders::_6));
|
std::placeholders::_5, std::placeholders::_6));
|
||||||
};
|
};
|
||||||
|
|
||||||
subscribe("limit_online_voltage", Topic::LimitOnlineVoltage);
|
subscribe("limit_online_voltage", Topic::LimitOnlineVoltage);
|
||||||
subscribe("limit_online_current", Topic::LimitOnlineCurrent);
|
subscribe("limit_online_current", Topic::LimitOnlineCurrent);
|
||||||
subscribe("limit_offline_voltage", Topic::LimitOfflineVoltage);
|
subscribe("limit_offline_voltage", Topic::LimitOfflineVoltage);
|
||||||
subscribe("limit_offline_current", Topic::LimitOfflineCurrent);
|
subscribe("limit_offline_current", Topic::LimitOfflineCurrent);
|
||||||
subscribe("mode", Topic::Mode);
|
subscribe("mode", Topic::Mode);
|
||||||
|
|
||||||
_lastPublish = millis();
|
_lastPublish = millis();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MqttHandleHuaweiClass::loop()
|
void MqttHandleHuaweiClass::loop()
|
||||||
{
|
{
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
std::unique_lock<std::mutex> mqttLock(_mqttMutex);
|
std::unique_lock<std::mutex> mqttLock(_mqttMutex);
|
||||||
|
|
||||||
if (!config.Huawei.Enabled) {
|
if (!config.Huawei.Enabled) {
|
||||||
_mqttCallbacks.clear();
|
_mqttCallbacks.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& callback : _mqttCallbacks) { callback(); }
|
for (auto& callback : _mqttCallbacks) { callback(); }
|
||||||
_mqttCallbacks.clear();
|
_mqttCallbacks.clear();
|
||||||
|
|
||||||
mqttLock.unlock();
|
mqttLock.unlock();
|
||||||
|
|
||||||
if (!MqttSettings.getConnected() ) {
|
if (!MqttSettings.getConnected() ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RectifierParameters_t *rp = HuaweiCan.get();
|
const RectifierParameters_t *rp = HuaweiCan.get();
|
||||||
|
|
||||||
if ((millis() - _lastPublish) > (config.Mqtt.PublishInterval * 1000) ) {
|
if ((millis() - _lastPublish) > (config.Mqtt.PublishInterval * 1000) ) {
|
||||||
MqttSettings.publish("huawei/data_age", String((millis() - HuaweiCan.getLastUpdate()) / 1000));
|
MqttSettings.publish("huawei/data_age", String((millis() - HuaweiCan.getLastUpdate()) / 1000));
|
||||||
MqttSettings.publish("huawei/input_voltage", String(rp->input_voltage));
|
MqttSettings.publish("huawei/input_voltage", String(rp->input_voltage));
|
||||||
MqttSettings.publish("huawei/input_current", String(rp->input_current));
|
MqttSettings.publish("huawei/input_current", String(rp->input_current));
|
||||||
MqttSettings.publish("huawei/input_power", String(rp->input_power));
|
MqttSettings.publish("huawei/input_power", String(rp->input_power));
|
||||||
MqttSettings.publish("huawei/output_voltage", String(rp->output_voltage));
|
MqttSettings.publish("huawei/output_voltage", String(rp->output_voltage));
|
||||||
MqttSettings.publish("huawei/output_current", String(rp->output_current));
|
MqttSettings.publish("huawei/output_current", String(rp->output_current));
|
||||||
MqttSettings.publish("huawei/max_output_current", String(rp->max_output_current));
|
MqttSettings.publish("huawei/max_output_current", String(rp->max_output_current));
|
||||||
MqttSettings.publish("huawei/output_power", String(rp->output_power));
|
MqttSettings.publish("huawei/output_power", String(rp->output_power));
|
||||||
MqttSettings.publish("huawei/input_temp", String(rp->input_temp));
|
MqttSettings.publish("huawei/input_temp", String(rp->input_temp));
|
||||||
MqttSettings.publish("huawei/output_temp", String(rp->output_temp));
|
MqttSettings.publish("huawei/output_temp", String(rp->output_temp));
|
||||||
MqttSettings.publish("huawei/efficiency", String(rp->efficiency));
|
MqttSettings.publish("huawei/efficiency", String(rp->efficiency));
|
||||||
MqttSettings.publish("huawei/mode", String(HuaweiCan.getMode()));
|
MqttSettings.publish("huawei/mode", String(HuaweiCan.getMode()));
|
||||||
|
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
_lastPublish = millis();
|
_lastPublish = millis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MqttHandleHuaweiClass::onMqttMessage(Topic t,
|
void MqttHandleHuaweiClass::onMqttMessage(Topic t,
|
||||||
const espMqttClientTypes::MessageProperties& properties,
|
const espMqttClientTypes::MessageProperties& properties,
|
||||||
const char* topic, const uint8_t* payload, size_t len,
|
const char* topic, const uint8_t* payload, size_t len,
|
||||||
size_t index, size_t total)
|
size_t index, size_t total)
|
||||||
{
|
{
|
||||||
std::string strValue(reinterpret_cast<const char*>(payload), len);
|
std::string strValue(reinterpret_cast<const char*>(payload), len);
|
||||||
float payload_val = -1;
|
float payload_val = -1;
|
||||||
try {
|
try {
|
||||||
payload_val = std::stof(strValue);
|
payload_val = std::stof(strValue);
|
||||||
}
|
}
|
||||||
catch (std::invalid_argument const& e) {
|
catch (std::invalid_argument const& e) {
|
||||||
MessageOutput.printf("Huawei MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
|
MessageOutput.printf("Huawei MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
|
||||||
topic, strValue.c_str());
|
topic, strValue.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> mqttLock(_mqttMutex);
|
std::lock_guard<std::mutex> mqttLock(_mqttMutex);
|
||||||
|
|
||||||
switch (t) {
|
switch (t) {
|
||||||
case Topic::LimitOnlineVoltage:
|
case Topic::LimitOnlineVoltage:
|
||||||
MessageOutput.printf("Limit Voltage: %f V\r\n", payload_val);
|
MessageOutput.printf("Limit Voltage: %f V\r\n", payload_val);
|
||||||
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
|
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
|
||||||
&HuaweiCan, payload_val, HUAWEI_ONLINE_VOLTAGE));
|
&HuaweiCan, payload_val, HUAWEI_ONLINE_VOLTAGE));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Topic::LimitOfflineVoltage:
|
case Topic::LimitOfflineVoltage:
|
||||||
MessageOutput.printf("Offline Limit Voltage: %f V\r\n", payload_val);
|
MessageOutput.printf("Offline Limit Voltage: %f V\r\n", payload_val);
|
||||||
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
|
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
|
||||||
&HuaweiCan, payload_val, HUAWEI_OFFLINE_VOLTAGE));
|
&HuaweiCan, payload_val, HUAWEI_OFFLINE_VOLTAGE));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Topic::LimitOnlineCurrent:
|
case Topic::LimitOnlineCurrent:
|
||||||
MessageOutput.printf("Limit Current: %f A\r\n", payload_val);
|
MessageOutput.printf("Limit Current: %f A\r\n", payload_val);
|
||||||
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
|
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
|
||||||
&HuaweiCan, payload_val, HUAWEI_ONLINE_CURRENT));
|
&HuaweiCan, payload_val, HUAWEI_ONLINE_CURRENT));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Topic::LimitOfflineCurrent:
|
case Topic::LimitOfflineCurrent:
|
||||||
MessageOutput.printf("Offline Limit Current: %f A\r\n", payload_val);
|
MessageOutput.printf("Offline Limit Current: %f A\r\n", payload_val);
|
||||||
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
|
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
|
||||||
&HuaweiCan, payload_val, HUAWEI_OFFLINE_CURRENT));
|
&HuaweiCan, payload_val, HUAWEI_OFFLINE_CURRENT));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Topic::Mode:
|
case Topic::Mode:
|
||||||
switch (static_cast<int>(payload_val)) {
|
switch (static_cast<int>(payload_val)) {
|
||||||
case 3:
|
case 3:
|
||||||
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Full internal control");
|
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Full internal control");
|
||||||
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
|
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
|
||||||
&HuaweiCan, HUAWEI_MODE_AUTO_INT));
|
&HuaweiCan, HUAWEI_MODE_AUTO_INT));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Internal on/off control, external power limit");
|
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Internal on/off control, external power limit");
|
||||||
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
|
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
|
||||||
&HuaweiCan, HUAWEI_MODE_AUTO_EXT));
|
&HuaweiCan, HUAWEI_MODE_AUTO_EXT));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned ON");
|
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned ON");
|
||||||
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
|
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
|
||||||
&HuaweiCan, HUAWEI_MODE_ON));
|
&HuaweiCan, HUAWEI_MODE_ON));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0:
|
case 0:
|
||||||
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned OFF");
|
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned OFF");
|
||||||
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
|
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
|
||||||
&HuaweiCan, HUAWEI_MODE_OFF));
|
&HuaweiCan, HUAWEI_MODE_OFF));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
MessageOutput.printf("[Huawei MQTT::] Invalid mode %.0f\r\n", payload_val);
|
MessageOutput.printf("[Huawei MQTT::] Invalid mode %.0f\r\n", payload_val);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,197 +1,197 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Thomas Basler, Malte Schmidt and others
|
* Copyright (C) 2022 Thomas Basler, Malte Schmidt and others
|
||||||
*/
|
*/
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "MqttHandlePowerLimiter.h"
|
#include "MqttHandlePowerLimiter.h"
|
||||||
#include "PowerLimiter.h"
|
#include "PowerLimiter.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
|
MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
|
||||||
|
|
||||||
void MqttHandlePowerLimiterClass::init(Scheduler& scheduler)
|
void MqttHandlePowerLimiterClass::init(Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
scheduler.addTask(_loopTask);
|
scheduler.addTask(_loopTask);
|
||||||
_loopTask.setCallback(std::bind(&MqttHandlePowerLimiterClass::loop, this));
|
_loopTask.setCallback(std::bind(&MqttHandlePowerLimiterClass::loop, this));
|
||||||
_loopTask.setIterations(TASK_FOREVER);
|
_loopTask.setIterations(TASK_FOREVER);
|
||||||
_loopTask.enable();
|
_loopTask.enable();
|
||||||
|
|
||||||
using std::placeholders::_1;
|
using std::placeholders::_1;
|
||||||
using std::placeholders::_2;
|
using std::placeholders::_2;
|
||||||
using std::placeholders::_3;
|
using std::placeholders::_3;
|
||||||
using std::placeholders::_4;
|
using std::placeholders::_4;
|
||||||
using std::placeholders::_5;
|
using std::placeholders::_5;
|
||||||
using std::placeholders::_6;
|
using std::placeholders::_6;
|
||||||
|
|
||||||
String const& prefix = MqttSettings.getPrefix();
|
String const& prefix = MqttSettings.getPrefix();
|
||||||
|
|
||||||
auto subscribe = [&prefix, this](char const* subTopic, MqttPowerLimiterCommand command) {
|
auto subscribe = [&prefix, this](char const* subTopic, MqttPowerLimiterCommand command) {
|
||||||
String fullTopic(prefix + "powerlimiter/cmd/" + subTopic);
|
String fullTopic(prefix + "powerlimiter/cmd/" + subTopic);
|
||||||
MqttSettings.subscribe(fullTopic.c_str(), 0,
|
MqttSettings.subscribe(fullTopic.c_str(), 0,
|
||||||
std::bind(&MqttHandlePowerLimiterClass::onMqttCmd, this, command,
|
std::bind(&MqttHandlePowerLimiterClass::onMqttCmd, this, command,
|
||||||
std::placeholders::_1, std::placeholders::_2,
|
std::placeholders::_1, std::placeholders::_2,
|
||||||
std::placeholders::_3, std::placeholders::_4,
|
std::placeholders::_3, std::placeholders::_4,
|
||||||
std::placeholders::_5, std::placeholders::_6));
|
std::placeholders::_5, std::placeholders::_6));
|
||||||
};
|
};
|
||||||
|
|
||||||
subscribe("threshold/soc/start", MqttPowerLimiterCommand::BatterySoCStartThreshold);
|
subscribe("threshold/soc/start", MqttPowerLimiterCommand::BatterySoCStartThreshold);
|
||||||
subscribe("threshold/soc/stop", MqttPowerLimiterCommand::BatterySoCStopThreshold);
|
subscribe("threshold/soc/stop", MqttPowerLimiterCommand::BatterySoCStopThreshold);
|
||||||
subscribe("threshold/soc/full_solar_passthrough", MqttPowerLimiterCommand::FullSolarPassthroughSoC);
|
subscribe("threshold/soc/full_solar_passthrough", MqttPowerLimiterCommand::FullSolarPassthroughSoC);
|
||||||
subscribe("threshold/voltage/start", MqttPowerLimiterCommand::VoltageStartThreshold);
|
subscribe("threshold/voltage/start", MqttPowerLimiterCommand::VoltageStartThreshold);
|
||||||
subscribe("threshold/voltage/stop", MqttPowerLimiterCommand::VoltageStopThreshold);
|
subscribe("threshold/voltage/stop", MqttPowerLimiterCommand::VoltageStopThreshold);
|
||||||
subscribe("threshold/voltage/full_solar_passthrough_start", MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage);
|
subscribe("threshold/voltage/full_solar_passthrough_start", MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage);
|
||||||
subscribe("threshold/voltage/full_solar_passthrough_stop", MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage);
|
subscribe("threshold/voltage/full_solar_passthrough_stop", MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage);
|
||||||
subscribe("mode", MqttPowerLimiterCommand::Mode);
|
subscribe("mode", MqttPowerLimiterCommand::Mode);
|
||||||
subscribe("upper_power_limit", MqttPowerLimiterCommand::UpperPowerLimit);
|
subscribe("upper_power_limit", MqttPowerLimiterCommand::UpperPowerLimit);
|
||||||
subscribe("target_power_consumption", MqttPowerLimiterCommand::TargetPowerConsumption);
|
subscribe("target_power_consumption", MqttPowerLimiterCommand::TargetPowerConsumption);
|
||||||
|
|
||||||
_lastPublish = millis();
|
_lastPublish = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MqttHandlePowerLimiterClass::loop()
|
void MqttHandlePowerLimiterClass::loop()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> mqttLock(_mqttMutex);
|
std::unique_lock<std::mutex> mqttLock(_mqttMutex);
|
||||||
|
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
if (!config.PowerLimiter.Enabled) {
|
if (!config.PowerLimiter.Enabled) {
|
||||||
_mqttCallbacks.clear();
|
_mqttCallbacks.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& callback : _mqttCallbacks) { callback(); }
|
for (auto& callback : _mqttCallbacks) { callback(); }
|
||||||
_mqttCallbacks.clear();
|
_mqttCallbacks.clear();
|
||||||
|
|
||||||
mqttLock.unlock();
|
mqttLock.unlock();
|
||||||
|
|
||||||
if (!MqttSettings.getConnected() ) { return; }
|
if (!MqttSettings.getConnected() ) { return; }
|
||||||
|
|
||||||
if ((millis() - _lastPublish) < (config.Mqtt.PublishInterval * 1000)) {
|
if ((millis() - _lastPublish) < (config.Mqtt.PublishInterval * 1000)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastPublish = millis();
|
_lastPublish = millis();
|
||||||
|
|
||||||
auto val = static_cast<unsigned>(PowerLimiter.getMode());
|
auto val = static_cast<unsigned>(PowerLimiter.getMode());
|
||||||
MqttSettings.publish("powerlimiter/status/mode", String(val));
|
MqttSettings.publish("powerlimiter/status/mode", String(val));
|
||||||
|
|
||||||
MqttSettings.publish("powerlimiter/status/upper_power_limit", String(config.PowerLimiter.UpperPowerLimit));
|
MqttSettings.publish("powerlimiter/status/upper_power_limit", String(config.PowerLimiter.UpperPowerLimit));
|
||||||
|
|
||||||
MqttSettings.publish("powerlimiter/status/target_power_consumption", String(config.PowerLimiter.TargetPowerConsumption));
|
MqttSettings.publish("powerlimiter/status/target_power_consumption", String(config.PowerLimiter.TargetPowerConsumption));
|
||||||
|
|
||||||
MqttSettings.publish("powerlimiter/status/inverter_update_timeouts", String(PowerLimiter.getInverterUpdateTimeouts()));
|
MqttSettings.publish("powerlimiter/status/inverter_update_timeouts", String(PowerLimiter.getInverterUpdateTimeouts()));
|
||||||
|
|
||||||
// no thresholds are relevant for setups without a battery
|
// no thresholds are relevant for setups without a battery
|
||||||
if (config.PowerLimiter.IsInverterSolarPowered) { return; }
|
if (config.PowerLimiter.IsInverterSolarPowered) { return; }
|
||||||
|
|
||||||
MqttSettings.publish("powerlimiter/status/threshold/voltage/start", String(config.PowerLimiter.VoltageStartThreshold));
|
MqttSettings.publish("powerlimiter/status/threshold/voltage/start", String(config.PowerLimiter.VoltageStartThreshold));
|
||||||
MqttSettings.publish("powerlimiter/status/threshold/voltage/stop", String(config.PowerLimiter.VoltageStopThreshold));
|
MqttSettings.publish("powerlimiter/status/threshold/voltage/stop", String(config.PowerLimiter.VoltageStopThreshold));
|
||||||
|
|
||||||
if (config.Vedirect.Enabled) {
|
if (config.Vedirect.Enabled) {
|
||||||
MqttSettings.publish("powerlimiter/status/threshold/voltage/full_solar_passthrough_start", String(config.PowerLimiter.FullSolarPassThroughStartVoltage));
|
MqttSettings.publish("powerlimiter/status/threshold/voltage/full_solar_passthrough_start", String(config.PowerLimiter.FullSolarPassThroughStartVoltage));
|
||||||
MqttSettings.publish("powerlimiter/status/threshold/voltage/full_solar_passthrough_stop", String(config.PowerLimiter.FullSolarPassThroughStopVoltage));
|
MqttSettings.publish("powerlimiter/status/threshold/voltage/full_solar_passthrough_stop", String(config.PowerLimiter.FullSolarPassThroughStopVoltage));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.Battery.Enabled || config.PowerLimiter.IgnoreSoc) { return; }
|
if (!config.Battery.Enabled || config.PowerLimiter.IgnoreSoc) { return; }
|
||||||
|
|
||||||
MqttSettings.publish("powerlimiter/status/threshold/soc/start", String(config.PowerLimiter.BatterySocStartThreshold));
|
MqttSettings.publish("powerlimiter/status/threshold/soc/start", String(config.PowerLimiter.BatterySocStartThreshold));
|
||||||
MqttSettings.publish("powerlimiter/status/threshold/soc/stop", String(config.PowerLimiter.BatterySocStopThreshold));
|
MqttSettings.publish("powerlimiter/status/threshold/soc/stop", String(config.PowerLimiter.BatterySocStopThreshold));
|
||||||
|
|
||||||
if (config.Vedirect.Enabled) {
|
if (config.Vedirect.Enabled) {
|
||||||
MqttSettings.publish("powerlimiter/status/threshold/soc/full_solar_passthrough", String(config.PowerLimiter.FullSolarPassThroughSoc));
|
MqttSettings.publish("powerlimiter/status/threshold/soc/full_solar_passthrough", String(config.PowerLimiter.FullSolarPassThroughSoc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandlePowerLimiterClass::onMqttCmd(MqttPowerLimiterCommand command, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
void MqttHandlePowerLimiterClass::onMqttCmd(MqttPowerLimiterCommand command, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||||
{
|
{
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
std::string strValue(reinterpret_cast<const char*>(payload), len);
|
std::string strValue(reinterpret_cast<const char*>(payload), len);
|
||||||
float payload_val = -1;
|
float payload_val = -1;
|
||||||
try {
|
try {
|
||||||
payload_val = std::stof(strValue);
|
payload_val = std::stof(strValue);
|
||||||
}
|
}
|
||||||
catch (std::invalid_argument const& e) {
|
catch (std::invalid_argument const& e) {
|
||||||
MessageOutput.printf("PowerLimiter MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
|
MessageOutput.printf("PowerLimiter MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
|
||||||
topic, strValue.c_str());
|
topic, strValue.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const int intValue = static_cast<int>(payload_val);
|
const int intValue = static_cast<int>(payload_val);
|
||||||
|
|
||||||
std::lock_guard<std::mutex> mqttLock(_mqttMutex);
|
std::lock_guard<std::mutex> mqttLock(_mqttMutex);
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case MqttPowerLimiterCommand::Mode:
|
case MqttPowerLimiterCommand::Mode:
|
||||||
{
|
{
|
||||||
using Mode = PowerLimiterClass::Mode;
|
using Mode = PowerLimiterClass::Mode;
|
||||||
Mode mode = static_cast<Mode>(intValue);
|
Mode mode = static_cast<Mode>(intValue);
|
||||||
if (mode == Mode::UnconditionalFullSolarPassthrough) {
|
if (mode == Mode::UnconditionalFullSolarPassthrough) {
|
||||||
MessageOutput.println("Power limiter unconditional full solar PT");
|
MessageOutput.println("Power limiter unconditional full solar PT");
|
||||||
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
|
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
|
||||||
&PowerLimiter, Mode::UnconditionalFullSolarPassthrough));
|
&PowerLimiter, Mode::UnconditionalFullSolarPassthrough));
|
||||||
} else if (mode == Mode::Disabled) {
|
} else if (mode == Mode::Disabled) {
|
||||||
MessageOutput.println("Power limiter disabled (override)");
|
MessageOutput.println("Power limiter disabled (override)");
|
||||||
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
|
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
|
||||||
&PowerLimiter, Mode::Disabled));
|
&PowerLimiter, Mode::Disabled));
|
||||||
} else if (mode == Mode::Normal) {
|
} else if (mode == Mode::Normal) {
|
||||||
MessageOutput.println("Power limiter normal operation");
|
MessageOutput.println("Power limiter normal operation");
|
||||||
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
|
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
|
||||||
&PowerLimiter, Mode::Normal));
|
&PowerLimiter, Mode::Normal));
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.printf("PowerLimiter - unknown mode %d\r\n", intValue);
|
MessageOutput.printf("PowerLimiter - unknown mode %d\r\n", intValue);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case MqttPowerLimiterCommand::BatterySoCStartThreshold:
|
case MqttPowerLimiterCommand::BatterySoCStartThreshold:
|
||||||
if (config.PowerLimiter.BatterySocStartThreshold == intValue) { return; }
|
if (config.PowerLimiter.BatterySocStartThreshold == intValue) { return; }
|
||||||
MessageOutput.printf("Setting battery SoC start threshold to: %d %%\r\n", intValue);
|
MessageOutput.printf("Setting battery SoC start threshold to: %d %%\r\n", intValue);
|
||||||
config.PowerLimiter.BatterySocStartThreshold = intValue;
|
config.PowerLimiter.BatterySocStartThreshold = intValue;
|
||||||
break;
|
break;
|
||||||
case MqttPowerLimiterCommand::BatterySoCStopThreshold:
|
case MqttPowerLimiterCommand::BatterySoCStopThreshold:
|
||||||
if (config.PowerLimiter.BatterySocStopThreshold == intValue) { return; }
|
if (config.PowerLimiter.BatterySocStopThreshold == intValue) { return; }
|
||||||
MessageOutput.printf("Setting battery SoC stop threshold to: %d %%\r\n", intValue);
|
MessageOutput.printf("Setting battery SoC stop threshold to: %d %%\r\n", intValue);
|
||||||
config.PowerLimiter.BatterySocStopThreshold = intValue;
|
config.PowerLimiter.BatterySocStopThreshold = intValue;
|
||||||
break;
|
break;
|
||||||
case MqttPowerLimiterCommand::FullSolarPassthroughSoC:
|
case MqttPowerLimiterCommand::FullSolarPassthroughSoC:
|
||||||
if (config.PowerLimiter.FullSolarPassThroughSoc == intValue) { return; }
|
if (config.PowerLimiter.FullSolarPassThroughSoc == intValue) { return; }
|
||||||
MessageOutput.printf("Setting full solar passthrough SoC to: %d %%\r\n", intValue);
|
MessageOutput.printf("Setting full solar passthrough SoC to: %d %%\r\n", intValue);
|
||||||
config.PowerLimiter.FullSolarPassThroughSoc = intValue;
|
config.PowerLimiter.FullSolarPassThroughSoc = intValue;
|
||||||
break;
|
break;
|
||||||
case MqttPowerLimiterCommand::VoltageStartThreshold:
|
case MqttPowerLimiterCommand::VoltageStartThreshold:
|
||||||
if (config.PowerLimiter.VoltageStartThreshold == payload_val) { return; }
|
if (config.PowerLimiter.VoltageStartThreshold == payload_val) { return; }
|
||||||
MessageOutput.printf("Setting voltage start threshold to: %.2f V\r\n", payload_val);
|
MessageOutput.printf("Setting voltage start threshold to: %.2f V\r\n", payload_val);
|
||||||
config.PowerLimiter.VoltageStartThreshold = payload_val;
|
config.PowerLimiter.VoltageStartThreshold = payload_val;
|
||||||
break;
|
break;
|
||||||
case MqttPowerLimiterCommand::VoltageStopThreshold:
|
case MqttPowerLimiterCommand::VoltageStopThreshold:
|
||||||
if (config.PowerLimiter.VoltageStopThreshold == payload_val) { return; }
|
if (config.PowerLimiter.VoltageStopThreshold == payload_val) { return; }
|
||||||
MessageOutput.printf("Setting voltage stop threshold to: %.2f V\r\n", payload_val);
|
MessageOutput.printf("Setting voltage stop threshold to: %.2f V\r\n", payload_val);
|
||||||
config.PowerLimiter.VoltageStopThreshold = payload_val;
|
config.PowerLimiter.VoltageStopThreshold = payload_val;
|
||||||
break;
|
break;
|
||||||
case MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage:
|
case MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage:
|
||||||
if (config.PowerLimiter.FullSolarPassThroughStartVoltage == payload_val) { return; }
|
if (config.PowerLimiter.FullSolarPassThroughStartVoltage == payload_val) { return; }
|
||||||
MessageOutput.printf("Setting full solar passthrough start voltage to: %.2f V\r\n", payload_val);
|
MessageOutput.printf("Setting full solar passthrough start voltage to: %.2f V\r\n", payload_val);
|
||||||
config.PowerLimiter.FullSolarPassThroughStartVoltage = payload_val;
|
config.PowerLimiter.FullSolarPassThroughStartVoltage = payload_val;
|
||||||
break;
|
break;
|
||||||
case MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage:
|
case MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage:
|
||||||
if (config.PowerLimiter.FullSolarPassThroughStopVoltage == payload_val) { return; }
|
if (config.PowerLimiter.FullSolarPassThroughStopVoltage == payload_val) { return; }
|
||||||
MessageOutput.printf("Setting full solar passthrough stop voltage to: %.2f V\r\n", payload_val);
|
MessageOutput.printf("Setting full solar passthrough stop voltage to: %.2f V\r\n", payload_val);
|
||||||
config.PowerLimiter.FullSolarPassThroughStopVoltage = payload_val;
|
config.PowerLimiter.FullSolarPassThroughStopVoltage = payload_val;
|
||||||
break;
|
break;
|
||||||
case MqttPowerLimiterCommand::UpperPowerLimit:
|
case MqttPowerLimiterCommand::UpperPowerLimit:
|
||||||
if (config.PowerLimiter.UpperPowerLimit == intValue) { return; }
|
if (config.PowerLimiter.UpperPowerLimit == intValue) { return; }
|
||||||
MessageOutput.printf("Setting upper power limit to: %d W\r\n", intValue);
|
MessageOutput.printf("Setting upper power limit to: %d W\r\n", intValue);
|
||||||
config.PowerLimiter.UpperPowerLimit = intValue;
|
config.PowerLimiter.UpperPowerLimit = intValue;
|
||||||
break;
|
break;
|
||||||
case MqttPowerLimiterCommand::TargetPowerConsumption:
|
case MqttPowerLimiterCommand::TargetPowerConsumption:
|
||||||
if (config.PowerLimiter.TargetPowerConsumption == intValue) { return; }
|
if (config.PowerLimiter.TargetPowerConsumption == intValue) { return; }
|
||||||
MessageOutput.printf("Setting target power consumption to: %d W\r\n", intValue);
|
MessageOutput.printf("Setting target power consumption to: %d W\r\n", intValue);
|
||||||
config.PowerLimiter.TargetPowerConsumption = intValue;
|
config.PowerLimiter.TargetPowerConsumption = intValue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not reached if the value did not change
|
// not reached if the value did not change
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,259 +1,259 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2024 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "WebApi_Huawei.h"
|
#include "WebApi_Huawei.h"
|
||||||
#include "Huawei_can.h"
|
#include "Huawei_can.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "WebApi_errors.h"
|
#include "WebApi_errors.h"
|
||||||
#include <AsyncJson.h>
|
#include <AsyncJson.h>
|
||||||
#include <Hoymiles.h>
|
#include <Hoymiles.h>
|
||||||
|
|
||||||
void WebApiHuaweiClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
void WebApiHuaweiClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
using std::placeholders::_1;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = &server;
|
_server = &server;
|
||||||
|
|
||||||
_server->on("/api/huawei/status", HTTP_GET, std::bind(&WebApiHuaweiClass::onStatus, this, _1));
|
_server->on("/api/huawei/status", HTTP_GET, std::bind(&WebApiHuaweiClass::onStatus, this, _1));
|
||||||
_server->on("/api/huawei/config", HTTP_GET, std::bind(&WebApiHuaweiClass::onAdminGet, this, _1));
|
_server->on("/api/huawei/config", HTTP_GET, std::bind(&WebApiHuaweiClass::onAdminGet, this, _1));
|
||||||
_server->on("/api/huawei/config", HTTP_POST, std::bind(&WebApiHuaweiClass::onAdminPost, this, _1));
|
_server->on("/api/huawei/config", HTTP_POST, std::bind(&WebApiHuaweiClass::onAdminPost, this, _1));
|
||||||
_server->on("/api/huawei/limit/config", HTTP_POST, std::bind(&WebApiHuaweiClass::onPost, this, _1));
|
_server->on("/api/huawei/limit/config", HTTP_POST, std::bind(&WebApiHuaweiClass::onPost, this, _1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiHuaweiClass::getJsonData(JsonVariant& root) {
|
void WebApiHuaweiClass::getJsonData(JsonVariant& root) {
|
||||||
const RectifierParameters_t * rp = HuaweiCan.get();
|
const RectifierParameters_t * rp = HuaweiCan.get();
|
||||||
|
|
||||||
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
|
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
|
||||||
root["input_voltage"]["v"] = rp->input_voltage;
|
root["input_voltage"]["v"] = rp->input_voltage;
|
||||||
root["input_voltage"]["u"] = "V";
|
root["input_voltage"]["u"] = "V";
|
||||||
root["input_current"]["v"] = rp->input_current;
|
root["input_current"]["v"] = rp->input_current;
|
||||||
root["input_current"]["u"] = "A";
|
root["input_current"]["u"] = "A";
|
||||||
root["input_power"]["v"] = rp->input_power;
|
root["input_power"]["v"] = rp->input_power;
|
||||||
root["input_power"]["u"] = "W";
|
root["input_power"]["u"] = "W";
|
||||||
root["output_voltage"]["v"] = rp->output_voltage;
|
root["output_voltage"]["v"] = rp->output_voltage;
|
||||||
root["output_voltage"]["u"] = "V";
|
root["output_voltage"]["u"] = "V";
|
||||||
root["output_current"]["v"] = rp->output_current;
|
root["output_current"]["v"] = rp->output_current;
|
||||||
root["output_current"]["u"] = "A";
|
root["output_current"]["u"] = "A";
|
||||||
root["max_output_current"]["v"] = rp->max_output_current;
|
root["max_output_current"]["v"] = rp->max_output_current;
|
||||||
root["max_output_current"]["u"] = "A";
|
root["max_output_current"]["u"] = "A";
|
||||||
root["output_power"]["v"] = rp->output_power;
|
root["output_power"]["v"] = rp->output_power;
|
||||||
root["output_power"]["u"] = "W";
|
root["output_power"]["u"] = "W";
|
||||||
root["input_temp"]["v"] = rp->input_temp;
|
root["input_temp"]["v"] = rp->input_temp;
|
||||||
root["input_temp"]["u"] = "°C";
|
root["input_temp"]["u"] = "°C";
|
||||||
root["output_temp"]["v"] = rp->output_temp;
|
root["output_temp"]["v"] = rp->output_temp;
|
||||||
root["output_temp"]["u"] = "°C";
|
root["output_temp"]["u"] = "°C";
|
||||||
root["efficiency"]["v"] = rp->efficiency * 100;
|
root["efficiency"]["v"] = rp->efficiency * 100;
|
||||||
root["efficiency"]["u"] = "%";
|
root["efficiency"]["u"] = "%";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiHuaweiClass::onStatus(AsyncWebServerRequest* request)
|
void WebApiHuaweiClass::onStatus(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
getJsonData(root);
|
getJsonData(root);
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request)
|
void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentials(request)) {
|
if (!WebApi.checkCredentials(request)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonDocument root;
|
JsonDocument root;
|
||||||
if (!WebApi.parseRequestData(request, response, root)) {
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float value;
|
float value;
|
||||||
uint8_t online = true;
|
uint8_t online = true;
|
||||||
float minimal_voltage;
|
float minimal_voltage;
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (root.containsKey("online")) {
|
if (root.containsKey("online")) {
|
||||||
online = root["online"].as<bool>();
|
online = root["online"].as<bool>();
|
||||||
if (online) {
|
if (online) {
|
||||||
minimal_voltage = HUAWEI_MINIMAL_ONLINE_VOLTAGE;
|
minimal_voltage = HUAWEI_MINIMAL_ONLINE_VOLTAGE;
|
||||||
} else {
|
} else {
|
||||||
minimal_voltage = HUAWEI_MINIMAL_OFFLINE_VOLTAGE;
|
minimal_voltage = HUAWEI_MINIMAL_OFFLINE_VOLTAGE;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
retMsg["message"] = "Could not read info if data should be set for online/offline operation!";
|
retMsg["message"] = "Could not read info if data should be set for online/offline operation!";
|
||||||
retMsg["code"] = WebApiError::LimitInvalidType;
|
retMsg["code"] = WebApiError::LimitInvalidType;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.containsKey("voltage_valid")) {
|
if (root.containsKey("voltage_valid")) {
|
||||||
if (root["voltage_valid"].as<bool>()) {
|
if (root["voltage_valid"].as<bool>()) {
|
||||||
if (root["voltage"].as<float>() < minimal_voltage || root["voltage"].as<float>() > 58) {
|
if (root["voltage"].as<float>() < minimal_voltage || root["voltage"].as<float>() > 58) {
|
||||||
retMsg["message"] = "voltage not in range between 42 (online)/48 (offline and 58V !";
|
retMsg["message"] = "voltage not in range between 42 (online)/48 (offline and 58V !";
|
||||||
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
||||||
retMsg["param"]["max"] = 58;
|
retMsg["param"]["max"] = 58;
|
||||||
retMsg["param"]["min"] = minimal_voltage;
|
retMsg["param"]["min"] = minimal_voltage;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
value = root["voltage"].as<float>();
|
value = root["voltage"].as<float>();
|
||||||
if (online) {
|
if (online) {
|
||||||
HuaweiCan.setValue(value, HUAWEI_ONLINE_VOLTAGE);
|
HuaweiCan.setValue(value, HUAWEI_ONLINE_VOLTAGE);
|
||||||
} else {
|
} else {
|
||||||
HuaweiCan.setValue(value, HUAWEI_OFFLINE_VOLTAGE);
|
HuaweiCan.setValue(value, HUAWEI_OFFLINE_VOLTAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.containsKey("current_valid")) {
|
if (root.containsKey("current_valid")) {
|
||||||
if (root["current_valid"].as<bool>()) {
|
if (root["current_valid"].as<bool>()) {
|
||||||
if (root["current"].as<float>() < 0 || root["current"].as<float>() > 60) {
|
if (root["current"].as<float>() < 0 || root["current"].as<float>() > 60) {
|
||||||
retMsg["message"] = "current must be in range between 0 and 60!";
|
retMsg["message"] = "current must be in range between 0 and 60!";
|
||||||
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
||||||
retMsg["param"]["max"] = 60;
|
retMsg["param"]["max"] = 60;
|
||||||
retMsg["param"]["min"] = 0;
|
retMsg["param"]["min"] = 0;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
value = root["current"].as<float>();
|
value = root["current"].as<float>();
|
||||||
if (online) {
|
if (online) {
|
||||||
HuaweiCan.setValue(value, HUAWEI_ONLINE_CURRENT);
|
HuaweiCan.setValue(value, HUAWEI_ONLINE_CURRENT);
|
||||||
} else {
|
} else {
|
||||||
HuaweiCan.setValue(value, HUAWEI_OFFLINE_CURRENT);
|
HuaweiCan.setValue(value, HUAWEI_OFFLINE_CURRENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request)
|
void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root["enabled"] = config.Huawei.Enabled;
|
root["enabled"] = config.Huawei.Enabled;
|
||||||
root["verbose_logging"] = config.Huawei.VerboseLogging;
|
root["verbose_logging"] = config.Huawei.VerboseLogging;
|
||||||
root["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency;
|
root["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency;
|
||||||
root["auto_power_enabled"] = config.Huawei.Auto_Power_Enabled;
|
root["auto_power_enabled"] = config.Huawei.Auto_Power_Enabled;
|
||||||
root["auto_power_batterysoc_limits_enabled"] = config.Huawei.Auto_Power_BatterySoC_Limits_Enabled;
|
root["auto_power_batterysoc_limits_enabled"] = config.Huawei.Auto_Power_BatterySoC_Limits_Enabled;
|
||||||
root["emergency_charge_enabled"] = config.Huawei.Emergency_Charge_Enabled;
|
root["emergency_charge_enabled"] = config.Huawei.Emergency_Charge_Enabled;
|
||||||
root["voltage_limit"] = static_cast<int>(config.Huawei.Auto_Power_Voltage_Limit * 100) / 100.0;
|
root["voltage_limit"] = static_cast<int>(config.Huawei.Auto_Power_Voltage_Limit * 100) / 100.0;
|
||||||
root["enable_voltage_limit"] = static_cast<int>(config.Huawei.Auto_Power_Enable_Voltage_Limit * 100) / 100.0;
|
root["enable_voltage_limit"] = static_cast<int>(config.Huawei.Auto_Power_Enable_Voltage_Limit * 100) / 100.0;
|
||||||
root["lower_power_limit"] = config.Huawei.Auto_Power_Lower_Power_Limit;
|
root["lower_power_limit"] = config.Huawei.Auto_Power_Lower_Power_Limit;
|
||||||
root["upper_power_limit"] = config.Huawei.Auto_Power_Upper_Power_Limit;
|
root["upper_power_limit"] = config.Huawei.Auto_Power_Upper_Power_Limit;
|
||||||
root["stop_batterysoc_threshold"] = config.Huawei.Auto_Power_Stop_BatterySoC_Threshold;
|
root["stop_batterysoc_threshold"] = config.Huawei.Auto_Power_Stop_BatterySoC_Threshold;
|
||||||
root["target_power_consumption"] = config.Huawei.Auto_Power_Target_Power_Consumption;
|
root["target_power_consumption"] = config.Huawei.Auto_Power_Target_Power_Consumption;
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
|
void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentials(request)) {
|
if (!WebApi.checkCredentials(request)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonDocument root;
|
JsonDocument root;
|
||||||
if (!WebApi.parseRequestData(request, response, root)) {
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("enabled")) ||
|
if (!(root.containsKey("enabled")) ||
|
||||||
!(root.containsKey("can_controller_frequency")) ||
|
!(root.containsKey("can_controller_frequency")) ||
|
||||||
!(root.containsKey("auto_power_enabled")) ||
|
!(root.containsKey("auto_power_enabled")) ||
|
||||||
!(root.containsKey("emergency_charge_enabled")) ||
|
!(root.containsKey("emergency_charge_enabled")) ||
|
||||||
!(root.containsKey("voltage_limit")) ||
|
!(root.containsKey("voltage_limit")) ||
|
||||||
!(root.containsKey("lower_power_limit")) ||
|
!(root.containsKey("lower_power_limit")) ||
|
||||||
!(root.containsKey("upper_power_limit"))) {
|
!(root.containsKey("upper_power_limit"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
config.Huawei.Enabled = root["enabled"].as<bool>();
|
config.Huawei.Enabled = root["enabled"].as<bool>();
|
||||||
config.Huawei.VerboseLogging = root["verbose_logging"];
|
config.Huawei.VerboseLogging = root["verbose_logging"];
|
||||||
config.Huawei.CAN_Controller_Frequency = root["can_controller_frequency"].as<uint32_t>();
|
config.Huawei.CAN_Controller_Frequency = root["can_controller_frequency"].as<uint32_t>();
|
||||||
config.Huawei.Auto_Power_Enabled = root["auto_power_enabled"].as<bool>();
|
config.Huawei.Auto_Power_Enabled = root["auto_power_enabled"].as<bool>();
|
||||||
config.Huawei.Auto_Power_BatterySoC_Limits_Enabled = root["auto_power_batterysoc_limits_enabled"].as<bool>();
|
config.Huawei.Auto_Power_BatterySoC_Limits_Enabled = root["auto_power_batterysoc_limits_enabled"].as<bool>();
|
||||||
config.Huawei.Emergency_Charge_Enabled = root["emergency_charge_enabled"].as<bool>();
|
config.Huawei.Emergency_Charge_Enabled = root["emergency_charge_enabled"].as<bool>();
|
||||||
config.Huawei.Auto_Power_Voltage_Limit = root["voltage_limit"].as<float>();
|
config.Huawei.Auto_Power_Voltage_Limit = root["voltage_limit"].as<float>();
|
||||||
config.Huawei.Auto_Power_Enable_Voltage_Limit = root["enable_voltage_limit"].as<float>();
|
config.Huawei.Auto_Power_Enable_Voltage_Limit = root["enable_voltage_limit"].as<float>();
|
||||||
config.Huawei.Auto_Power_Lower_Power_Limit = root["lower_power_limit"].as<float>();
|
config.Huawei.Auto_Power_Lower_Power_Limit = root["lower_power_limit"].as<float>();
|
||||||
config.Huawei.Auto_Power_Upper_Power_Limit = root["upper_power_limit"].as<float>();
|
config.Huawei.Auto_Power_Upper_Power_Limit = root["upper_power_limit"].as<float>();
|
||||||
config.Huawei.Auto_Power_Stop_BatterySoC_Threshold = root["stop_batterysoc_threshold"];
|
config.Huawei.Auto_Power_Stop_BatterySoC_Threshold = root["stop_batterysoc_threshold"];
|
||||||
config.Huawei.Auto_Power_Target_Power_Consumption = root["target_power_consumption"];
|
config.Huawei.Auto_Power_Target_Power_Consumption = root["target_power_consumption"];
|
||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|
||||||
// TODO(schlimmchen): HuaweiCan has no real concept of the fact that the
|
// TODO(schlimmchen): HuaweiCan has no real concept of the fact that the
|
||||||
// config might change. at least not regarding CAN parameters. until that
|
// config might change. at least not regarding CAN parameters. until that
|
||||||
// changes, the ESP must restart for configuration changes to take effect.
|
// changes, the ESP must restart for configuration changes to take effect.
|
||||||
yield();
|
yield();
|
||||||
delay(1000);
|
delay(1000);
|
||||||
yield();
|
yield();
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
|
|
||||||
const PinMapping_t& pin = PinMapping.get();
|
const PinMapping_t& pin = PinMapping.get();
|
||||||
// Properly turn this on
|
// Properly turn this on
|
||||||
if (config.Huawei.Enabled) {
|
if (config.Huawei.Enabled) {
|
||||||
MessageOutput.println("Initialize Huawei AC charger interface... ");
|
MessageOutput.println("Initialize Huawei AC charger interface... ");
|
||||||
if (PinMapping.isValidHuaweiConfig()) {
|
if (PinMapping.isValidHuaweiConfig()) {
|
||||||
MessageOutput.printf("Huawei AC-charger miso = %d, mosi = %d, clk = %d, irq = %d, cs = %d, power_pin = %d\r\n", pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
|
MessageOutput.printf("Huawei AC-charger miso = %d, mosi = %d, clk = %d, irq = %d, cs = %d, power_pin = %d\r\n", pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
|
||||||
HuaweiCan.updateSettings(pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
|
HuaweiCan.updateSettings(pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.println("Invalid pin config");
|
MessageOutput.println("Invalid pin config");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properly turn this off
|
// Properly turn this off
|
||||||
if (!config.Huawei.Enabled) {
|
if (!config.Huawei.Enabled) {
|
||||||
HuaweiCan.setValue(0, HUAWEI_ONLINE_CURRENT);
|
HuaweiCan.setValue(0, HUAWEI_ONLINE_CURRENT);
|
||||||
delay(500);
|
delay(500);
|
||||||
HuaweiCan.setMode(HUAWEI_MODE_OFF);
|
HuaweiCan.setMode(HUAWEI_MODE_OFF);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.Huawei.Auto_Power_Enabled) {
|
if (config.Huawei.Auto_Power_Enabled) {
|
||||||
HuaweiCan.setMode(HUAWEI_MODE_AUTO_INT);
|
HuaweiCan.setMode(HUAWEI_MODE_AUTO_INT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
HuaweiCan.setMode(HUAWEI_MODE_AUTO_EXT);
|
HuaweiCan.setMode(HUAWEI_MODE_AUTO_EXT);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,144 +1,144 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2024 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "WebApi_ws_Huawei.h"
|
#include "WebApi_ws_Huawei.h"
|
||||||
#include "AsyncJson.h"
|
#include "AsyncJson.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "Huawei_can.h"
|
#include "Huawei_can.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
|
|
||||||
WebApiWsHuaweiLiveClass::WebApiWsHuaweiLiveClass()
|
WebApiWsHuaweiLiveClass::WebApiWsHuaweiLiveClass()
|
||||||
: _ws("/huaweilivedata")
|
: _ws("/huaweilivedata")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
using std::placeholders::_1;
|
using std::placeholders::_1;
|
||||||
using std::placeholders::_2;
|
using std::placeholders::_2;
|
||||||
using std::placeholders::_3;
|
using std::placeholders::_3;
|
||||||
using std::placeholders::_4;
|
using std::placeholders::_4;
|
||||||
using std::placeholders::_5;
|
using std::placeholders::_5;
|
||||||
using std::placeholders::_6;
|
using std::placeholders::_6;
|
||||||
|
|
||||||
_server = &server;
|
_server = &server;
|
||||||
_server->on("/api/huaweilivedata/status", HTTP_GET, std::bind(&WebApiWsHuaweiLiveClass::onLivedataStatus, this, _1));
|
_server->on("/api/huaweilivedata/status", HTTP_GET, std::bind(&WebApiWsHuaweiLiveClass::onLivedataStatus, this, _1));
|
||||||
|
|
||||||
_server->addHandler(&_ws);
|
_server->addHandler(&_ws);
|
||||||
_ws.onEvent(std::bind(&WebApiWsHuaweiLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
|
_ws.onEvent(std::bind(&WebApiWsHuaweiLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
|
||||||
|
|
||||||
scheduler.addTask(_wsCleanupTask);
|
scheduler.addTask(_wsCleanupTask);
|
||||||
_wsCleanupTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::wsCleanupTaskCb, this));
|
_wsCleanupTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::wsCleanupTaskCb, this));
|
||||||
_wsCleanupTask.setIterations(TASK_FOREVER);
|
_wsCleanupTask.setIterations(TASK_FOREVER);
|
||||||
_wsCleanupTask.setInterval(1 * TASK_SECOND);
|
_wsCleanupTask.setInterval(1 * TASK_SECOND);
|
||||||
_wsCleanupTask.enable();
|
_wsCleanupTask.enable();
|
||||||
|
|
||||||
scheduler.addTask(_sendDataTask);
|
scheduler.addTask(_sendDataTask);
|
||||||
_sendDataTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::sendDataTaskCb, this));
|
_sendDataTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::sendDataTaskCb, this));
|
||||||
_sendDataTask.setIterations(TASK_FOREVER);
|
_sendDataTask.setIterations(TASK_FOREVER);
|
||||||
_sendDataTask.setInterval(1 * TASK_SECOND);
|
_sendDataTask.setInterval(1 * TASK_SECOND);
|
||||||
_sendDataTask.enable();
|
_sendDataTask.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsHuaweiLiveClass::wsCleanupTaskCb()
|
void WebApiWsHuaweiLiveClass::wsCleanupTaskCb()
|
||||||
{
|
{
|
||||||
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
||||||
_ws.cleanupClients();
|
_ws.cleanupClients();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsHuaweiLiveClass::sendDataTaskCb()
|
void WebApiWsHuaweiLiveClass::sendDataTaskCb()
|
||||||
{
|
{
|
||||||
// do nothing if no WS client is connected
|
// do nothing if no WS client is connected
|
||||||
if (_ws.count() == 0) {
|
if (_ws.count() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
JsonDocument root;
|
JsonDocument root;
|
||||||
JsonVariant var = root;
|
JsonVariant var = root;
|
||||||
|
|
||||||
generateCommonJsonResponse(var);
|
generateCommonJsonResponse(var);
|
||||||
|
|
||||||
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
String buffer;
|
String buffer;
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
|
|
||||||
_ws.textAll(buffer);
|
_ws.textAll(buffer);
|
||||||
}
|
}
|
||||||
} catch (std::bad_alloc& bad_alloc) {
|
} catch (std::bad_alloc& bad_alloc) {
|
||||||
MessageOutput.printf("Calling /api/huaweilivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
MessageOutput.printf("Calling /api/huaweilivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||||
} catch (const std::exception& exc) {
|
} catch (const std::exception& exc) {
|
||||||
MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what());
|
MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsHuaweiLiveClass::generateCommonJsonResponse(JsonVariant& root)
|
void WebApiWsHuaweiLiveClass::generateCommonJsonResponse(JsonVariant& root)
|
||||||
{
|
{
|
||||||
const RectifierParameters_t * rp = HuaweiCan.get();
|
const RectifierParameters_t * rp = HuaweiCan.get();
|
||||||
|
|
||||||
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
|
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
|
||||||
root["input_voltage"]["v"] = rp->input_voltage;
|
root["input_voltage"]["v"] = rp->input_voltage;
|
||||||
root["input_voltage"]["u"] = "V";
|
root["input_voltage"]["u"] = "V";
|
||||||
root["input_current"]["v"] = rp->input_current;
|
root["input_current"]["v"] = rp->input_current;
|
||||||
root["input_current"]["u"] = "A";
|
root["input_current"]["u"] = "A";
|
||||||
root["input_power"]["v"] = rp->input_power;
|
root["input_power"]["v"] = rp->input_power;
|
||||||
root["input_power"]["u"] = "W";
|
root["input_power"]["u"] = "W";
|
||||||
root["output_voltage"]["v"] = rp->output_voltage;
|
root["output_voltage"]["v"] = rp->output_voltage;
|
||||||
root["output_voltage"]["u"] = "V";
|
root["output_voltage"]["u"] = "V";
|
||||||
root["output_current"]["v"] = rp->output_current;
|
root["output_current"]["v"] = rp->output_current;
|
||||||
root["output_current"]["u"] = "A";
|
root["output_current"]["u"] = "A";
|
||||||
root["max_output_current"]["v"] = rp->max_output_current;
|
root["max_output_current"]["v"] = rp->max_output_current;
|
||||||
root["max_output_current"]["u"] = "A";
|
root["max_output_current"]["u"] = "A";
|
||||||
root["output_power"]["v"] = rp->output_power;
|
root["output_power"]["v"] = rp->output_power;
|
||||||
root["output_power"]["u"] = "W";
|
root["output_power"]["u"] = "W";
|
||||||
root["input_temp"]["v"] = rp->input_temp;
|
root["input_temp"]["v"] = rp->input_temp;
|
||||||
root["input_temp"]["u"] = "°C";
|
root["input_temp"]["u"] = "°C";
|
||||||
root["output_temp"]["v"] = rp->output_temp;
|
root["output_temp"]["v"] = rp->output_temp;
|
||||||
root["output_temp"]["u"] = "°C";
|
root["output_temp"]["u"] = "°C";
|
||||||
root["efficiency"]["v"] = rp->efficiency * 100;
|
root["efficiency"]["v"] = rp->efficiency * 100;
|
||||||
root["efficiency"]["u"] = "%";
|
root["efficiency"]["u"] = "%";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsHuaweiLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
void WebApiWsHuaweiLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
||||||
{
|
{
|
||||||
if (type == WS_EVT_CONNECT) {
|
if (type == WS_EVT_CONNECT) {
|
||||||
char str[64];
|
char str[64];
|
||||||
snprintf(str, sizeof(str), "Websocket: [%s][%u] connect", server->url(), client->id());
|
snprintf(str, sizeof(str), "Websocket: [%s][%u] connect", server->url(), client->id());
|
||||||
Serial.println(str);
|
Serial.println(str);
|
||||||
MessageOutput.println(str);
|
MessageOutput.println(str);
|
||||||
} else if (type == WS_EVT_DISCONNECT) {
|
} else if (type == WS_EVT_DISCONNECT) {
|
||||||
char str[64];
|
char str[64];
|
||||||
snprintf(str, sizeof(str), "Websocket: [%s][%u] disconnect", server->url(), client->id());
|
snprintf(str, sizeof(str), "Websocket: [%s][%u] disconnect", server->url(), client->id());
|
||||||
Serial.println(str);
|
Serial.println(str);
|
||||||
MessageOutput.println(str);
|
MessageOutput.println(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsHuaweiLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
void WebApiWsHuaweiLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
|
|
||||||
generateCommonJsonResponse(root);
|
generateCommonJsonResponse(root);
|
||||||
|
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|
||||||
} catch (std::bad_alloc& bad_alloc) {
|
} catch (std::bad_alloc& bad_alloc) {
|
||||||
MessageOutput.printf("Calling /api/huaweilivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
MessageOutput.printf("Calling /api/huaweilivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||||
WebApi.sendTooManyRequests(request);
|
WebApi.sendTooManyRequests(request);
|
||||||
} catch (const std::exception& exc) {
|
} catch (const std::exception& exc) {
|
||||||
MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what());
|
MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what());
|
||||||
WebApi.sendTooManyRequests(request);
|
WebApi.sendTooManyRequests(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,126 +1,126 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2024 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "WebApi_ws_battery.h"
|
#include "WebApi_ws_battery.h"
|
||||||
#include "AsyncJson.h"
|
#include "AsyncJson.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
WebApiWsBatteryLiveClass::WebApiWsBatteryLiveClass()
|
WebApiWsBatteryLiveClass::WebApiWsBatteryLiveClass()
|
||||||
: _ws("/batterylivedata")
|
: _ws("/batterylivedata")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsBatteryLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
void WebApiWsBatteryLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
using std::placeholders::_1;
|
using std::placeholders::_1;
|
||||||
using std::placeholders::_2;
|
using std::placeholders::_2;
|
||||||
using std::placeholders::_3;
|
using std::placeholders::_3;
|
||||||
using std::placeholders::_4;
|
using std::placeholders::_4;
|
||||||
using std::placeholders::_5;
|
using std::placeholders::_5;
|
||||||
using std::placeholders::_6;
|
using std::placeholders::_6;
|
||||||
|
|
||||||
_server = &server;
|
_server = &server;
|
||||||
_server->on("/api/batterylivedata/status", HTTP_GET, std::bind(&WebApiWsBatteryLiveClass::onLivedataStatus, this, _1));
|
_server->on("/api/batterylivedata/status", HTTP_GET, std::bind(&WebApiWsBatteryLiveClass::onLivedataStatus, this, _1));
|
||||||
|
|
||||||
_server->addHandler(&_ws);
|
_server->addHandler(&_ws);
|
||||||
_ws.onEvent(std::bind(&WebApiWsBatteryLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
|
_ws.onEvent(std::bind(&WebApiWsBatteryLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
|
||||||
|
|
||||||
scheduler.addTask(_wsCleanupTask);
|
scheduler.addTask(_wsCleanupTask);
|
||||||
_wsCleanupTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::wsCleanupTaskCb, this));
|
_wsCleanupTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::wsCleanupTaskCb, this));
|
||||||
_wsCleanupTask.setIterations(TASK_FOREVER);
|
_wsCleanupTask.setIterations(TASK_FOREVER);
|
||||||
_wsCleanupTask.setInterval(1 * TASK_SECOND);
|
_wsCleanupTask.setInterval(1 * TASK_SECOND);
|
||||||
_wsCleanupTask.enable();
|
_wsCleanupTask.enable();
|
||||||
|
|
||||||
scheduler.addTask(_sendDataTask);
|
scheduler.addTask(_sendDataTask);
|
||||||
_sendDataTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::sendDataTaskCb, this));
|
_sendDataTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::sendDataTaskCb, this));
|
||||||
_sendDataTask.setIterations(TASK_FOREVER);
|
_sendDataTask.setIterations(TASK_FOREVER);
|
||||||
_sendDataTask.setInterval(1 * TASK_SECOND);
|
_sendDataTask.setInterval(1 * TASK_SECOND);
|
||||||
_sendDataTask.enable();
|
_sendDataTask.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsBatteryLiveClass::wsCleanupTaskCb()
|
void WebApiWsBatteryLiveClass::wsCleanupTaskCb()
|
||||||
{
|
{
|
||||||
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
||||||
_ws.cleanupClients();
|
_ws.cleanupClients();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsBatteryLiveClass::sendDataTaskCb()
|
void WebApiWsBatteryLiveClass::sendDataTaskCb()
|
||||||
{
|
{
|
||||||
// do nothing if no WS client is connected
|
// do nothing if no WS client is connected
|
||||||
if (_ws.count() == 0) {
|
if (_ws.count() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Battery.getStats()->updateAvailable(_lastUpdateCheck)) { return; }
|
if (!Battery.getStats()->updateAvailable(_lastUpdateCheck)) { return; }
|
||||||
_lastUpdateCheck = millis();
|
_lastUpdateCheck = millis();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
JsonDocument root;
|
JsonDocument root;
|
||||||
JsonVariant var = root;
|
JsonVariant var = root;
|
||||||
|
|
||||||
generateCommonJsonResponse(var);
|
generateCommonJsonResponse(var);
|
||||||
|
|
||||||
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
|
|
||||||
// battery provider does not generate a card, e.g., MQTT provider
|
// battery provider does not generate a card, e.g., MQTT provider
|
||||||
if (root.isNull()) { return; }
|
if (root.isNull()) { return; }
|
||||||
|
|
||||||
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 {
|
||||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
|
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ws.textAll(buffer);
|
_ws.textAll(buffer);
|
||||||
}
|
}
|
||||||
} catch (std::bad_alloc& bad_alloc) {
|
} catch (std::bad_alloc& bad_alloc) {
|
||||||
MessageOutput.printf("Calling /api/batterylivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
MessageOutput.printf("Calling /api/batterylivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||||
} catch (const std::exception& exc) {
|
} catch (const std::exception& exc) {
|
||||||
MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what());
|
MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsBatteryLiveClass::generateCommonJsonResponse(JsonVariant& root)
|
void WebApiWsBatteryLiveClass::generateCommonJsonResponse(JsonVariant& root)
|
||||||
{
|
{
|
||||||
Battery.getStats()->getLiveViewData(root);
|
Battery.getStats()->getLiveViewData(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsBatteryLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
void WebApiWsBatteryLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
||||||
{
|
{
|
||||||
if (type == WS_EVT_CONNECT) {
|
if (type == WS_EVT_CONNECT) {
|
||||||
MessageOutput.printf("Websocket: [%s][%u] connect\r\n", server->url(), client->id());
|
MessageOutput.printf("Websocket: [%s][%u] connect\r\n", server->url(), client->id());
|
||||||
} else if (type == WS_EVT_DISCONNECT) {
|
} else if (type == WS_EVT_DISCONNECT) {
|
||||||
MessageOutput.printf("Websocket: [%s][%u] disconnect\r\n", server->url(), client->id());
|
MessageOutput.printf("Websocket: [%s][%u] disconnect\r\n", server->url(), client->id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsBatteryLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
void WebApiWsBatteryLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
generateCommonJsonResponse(root);
|
generateCommonJsonResponse(root);
|
||||||
|
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
} catch (std::bad_alloc& bad_alloc) {
|
} catch (std::bad_alloc& bad_alloc) {
|
||||||
MessageOutput.printf("Calling /api/batterylivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
MessageOutput.printf("Calling /api/batterylivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||||
WebApi.sendTooManyRequests(request);
|
WebApi.sendTooManyRequests(request);
|
||||||
} catch (const std::exception& exc) {
|
} catch (const std::exception& exc) {
|
||||||
MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what());
|
MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what());
|
||||||
WebApi.sendTooManyRequests(request);
|
WebApi.sendTooManyRequests(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
406
src/main.cpp
406
src/main.cpp
@ -1,203 +1,203 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2024 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "Datastore.h"
|
#include "Datastore.h"
|
||||||
#include "Display_Graphic.h"
|
#include "Display_Graphic.h"
|
||||||
#include "InverterSettings.h"
|
#include "InverterSettings.h"
|
||||||
#include "Led_Single.h"
|
#include "Led_Single.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "SerialPortManager.h"
|
#include "SerialPortManager.h"
|
||||||
#include "VictronMppt.h"
|
#include "VictronMppt.h"
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "Huawei_can.h"
|
#include "Huawei_can.h"
|
||||||
#include "MqttHandleDtu.h"
|
#include "MqttHandleDtu.h"
|
||||||
#include "MqttHandleHass.h"
|
#include "MqttHandleHass.h"
|
||||||
#include "MqttHandleVedirectHass.h"
|
#include "MqttHandleVedirectHass.h"
|
||||||
#include "MqttHandleBatteryHass.h"
|
#include "MqttHandleBatteryHass.h"
|
||||||
#include "MqttHandleInverter.h"
|
#include "MqttHandleInverter.h"
|
||||||
#include "MqttHandleInverterTotal.h"
|
#include "MqttHandleInverterTotal.h"
|
||||||
#include "MqttHandleVedirect.h"
|
#include "MqttHandleVedirect.h"
|
||||||
#include "MqttHandleHuawei.h"
|
#include "MqttHandleHuawei.h"
|
||||||
#include "MqttHandlePowerLimiter.h"
|
#include "MqttHandlePowerLimiter.h"
|
||||||
#include "MqttHandlePowerLimiterHass.h"
|
#include "MqttHandlePowerLimiterHass.h"
|
||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "NtpSettings.h"
|
#include "NtpSettings.h"
|
||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
#include "Scheduler.h"
|
#include "Scheduler.h"
|
||||||
#include "SunPosition.h"
|
#include "SunPosition.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "PowerMeter.h"
|
#include "PowerMeter.h"
|
||||||
#include "PowerLimiter.h"
|
#include "PowerLimiter.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <TaskScheduler.h>
|
#include <TaskScheduler.h>
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
// Move all dynamic allocations >512byte to psram (if available)
|
// Move all dynamic allocations >512byte to psram (if available)
|
||||||
heap_caps_malloc_extmem_enable(512);
|
heap_caps_malloc_extmem_enable(512);
|
||||||
|
|
||||||
// Initialize serial output
|
// Initialize serial output
|
||||||
Serial.begin(SERIAL_BAUDRATE);
|
Serial.begin(SERIAL_BAUDRATE);
|
||||||
#if ARDUINO_USB_CDC_ON_BOOT
|
#if ARDUINO_USB_CDC_ON_BOOT
|
||||||
Serial.setTxTimeoutMs(0);
|
Serial.setTxTimeoutMs(0);
|
||||||
delay(100);
|
delay(100);
|
||||||
#else
|
#else
|
||||||
while (!Serial)
|
while (!Serial)
|
||||||
yield();
|
yield();
|
||||||
#endif
|
#endif
|
||||||
MessageOutput.init(scheduler);
|
MessageOutput.init(scheduler);
|
||||||
MessageOutput.println();
|
MessageOutput.println();
|
||||||
MessageOutput.println("Starting OpenDTU");
|
MessageOutput.println("Starting OpenDTU");
|
||||||
|
|
||||||
// Initialize file system
|
// Initialize file system
|
||||||
MessageOutput.print("Initialize FS... ");
|
MessageOutput.print("Initialize FS... ");
|
||||||
if (!LittleFS.begin(false)) { // Do not format if mount failed
|
if (!LittleFS.begin(false)) { // Do not format if mount failed
|
||||||
MessageOutput.print("failed... trying to format...");
|
MessageOutput.print("failed... trying to format...");
|
||||||
if (!LittleFS.begin(true)) {
|
if (!LittleFS.begin(true)) {
|
||||||
MessageOutput.print("success");
|
MessageOutput.print("success");
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.print("failed");
|
MessageOutput.print("failed");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read configuration values
|
// Read configuration values
|
||||||
MessageOutput.print("Reading configuration... ");
|
MessageOutput.print("Reading configuration... ");
|
||||||
if (!Configuration.read()) {
|
if (!Configuration.read()) {
|
||||||
MessageOutput.print("initializing... ");
|
MessageOutput.print("initializing... ");
|
||||||
Configuration.init();
|
Configuration.init();
|
||||||
if (Configuration.write()) {
|
if (Configuration.write()) {
|
||||||
MessageOutput.print("written... ");
|
MessageOutput.print("written... ");
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.print("failed... ");
|
MessageOutput.print("failed... ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Configuration.get().Cfg.Version != CONFIG_VERSION) {
|
if (Configuration.get().Cfg.Version != CONFIG_VERSION) {
|
||||||
MessageOutput.print("migrated... ");
|
MessageOutput.print("migrated... ");
|
||||||
Configuration.migrate();
|
Configuration.migrate();
|
||||||
}
|
}
|
||||||
auto& config = Configuration.get();
|
auto& config = Configuration.get();
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
// Load PinMapping
|
// Load PinMapping
|
||||||
MessageOutput.print("Reading PinMapping... ");
|
MessageOutput.print("Reading PinMapping... ");
|
||||||
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
|
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
|
||||||
MessageOutput.print("found valid mapping ");
|
MessageOutput.print("found valid mapping ");
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.print("using default config ");
|
MessageOutput.print("using default config ");
|
||||||
}
|
}
|
||||||
const auto& pin = PinMapping.get();
|
const auto& pin = PinMapping.get();
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
SerialPortManager.init();
|
SerialPortManager.init();
|
||||||
|
|
||||||
// Initialize WiFi
|
// Initialize WiFi
|
||||||
MessageOutput.print("Initialize Network... ");
|
MessageOutput.print("Initialize Network... ");
|
||||||
NetworkSettings.init(scheduler);
|
NetworkSettings.init(scheduler);
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
NetworkSettings.applyConfig();
|
NetworkSettings.applyConfig();
|
||||||
|
|
||||||
// Initialize NTP
|
// Initialize NTP
|
||||||
MessageOutput.print("Initialize NTP... ");
|
MessageOutput.print("Initialize NTP... ");
|
||||||
NtpSettings.init();
|
NtpSettings.init();
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
// Initialize SunPosition
|
// Initialize SunPosition
|
||||||
MessageOutput.print("Initialize SunPosition... ");
|
MessageOutput.print("Initialize SunPosition... ");
|
||||||
SunPosition.init(scheduler);
|
SunPosition.init(scheduler);
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
// Initialize MqTT
|
// Initialize MqTT
|
||||||
MessageOutput.print("Initialize MqTT... ");
|
MessageOutput.print("Initialize MqTT... ");
|
||||||
MqttSettings.init();
|
MqttSettings.init();
|
||||||
MqttHandleDtu.init(scheduler);
|
MqttHandleDtu.init(scheduler);
|
||||||
MqttHandleInverter.init(scheduler);
|
MqttHandleInverter.init(scheduler);
|
||||||
MqttHandleInverterTotal.init(scheduler);
|
MqttHandleInverterTotal.init(scheduler);
|
||||||
MqttHandleVedirect.init(scheduler);
|
MqttHandleVedirect.init(scheduler);
|
||||||
MqttHandleHass.init(scheduler);
|
MqttHandleHass.init(scheduler);
|
||||||
MqttHandleVedirectHass.init(scheduler);
|
MqttHandleVedirectHass.init(scheduler);
|
||||||
MqttHandleBatteryHass.init(scheduler);
|
MqttHandleBatteryHass.init(scheduler);
|
||||||
MqttHandleHuawei.init(scheduler);
|
MqttHandleHuawei.init(scheduler);
|
||||||
MqttHandlePowerLimiter.init(scheduler);
|
MqttHandlePowerLimiter.init(scheduler);
|
||||||
MqttHandlePowerLimiterHass.init(scheduler);
|
MqttHandlePowerLimiterHass.init(scheduler);
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
// Initialize WebApi
|
// Initialize WebApi
|
||||||
MessageOutput.print("Initialize WebApi... ");
|
MessageOutput.print("Initialize WebApi... ");
|
||||||
WebApi.init(scheduler);
|
WebApi.init(scheduler);
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
// Initialize Display
|
// Initialize Display
|
||||||
MessageOutput.print("Initialize Display... ");
|
MessageOutput.print("Initialize Display... ");
|
||||||
Display.init(
|
Display.init(
|
||||||
scheduler,
|
scheduler,
|
||||||
static_cast<DisplayType_t>(pin.display_type),
|
static_cast<DisplayType_t>(pin.display_type),
|
||||||
pin.display_data,
|
pin.display_data,
|
||||||
pin.display_clk,
|
pin.display_clk,
|
||||||
pin.display_cs,
|
pin.display_cs,
|
||||||
pin.display_reset);
|
pin.display_reset);
|
||||||
Display.setDiagramMode(static_cast<DiagramMode_t>(config.Display.Diagram.Mode));
|
Display.setDiagramMode(static_cast<DiagramMode_t>(config.Display.Diagram.Mode));
|
||||||
Display.setOrientation(config.Display.Rotation);
|
Display.setOrientation(config.Display.Rotation);
|
||||||
Display.enablePowerSafe = config.Display.PowerSafe;
|
Display.enablePowerSafe = config.Display.PowerSafe;
|
||||||
Display.enableScreensaver = config.Display.ScreenSaver;
|
Display.enableScreensaver = config.Display.ScreenSaver;
|
||||||
Display.setContrast(config.Display.Contrast);
|
Display.setContrast(config.Display.Contrast);
|
||||||
Display.setLanguage(config.Display.Language);
|
Display.setLanguage(config.Display.Language);
|
||||||
Display.setStartupDisplay();
|
Display.setStartupDisplay();
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
// Initialize Single LEDs
|
// Initialize Single LEDs
|
||||||
MessageOutput.print("Initialize LEDs... ");
|
MessageOutput.print("Initialize LEDs... ");
|
||||||
LedSingle.init(scheduler);
|
LedSingle.init(scheduler);
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
// Check for default DTU serial
|
// Check for default DTU serial
|
||||||
MessageOutput.print("Check for default DTU serial... ");
|
MessageOutput.print("Check for default DTU serial... ");
|
||||||
if (config.Dtu.Serial == DTU_SERIAL) {
|
if (config.Dtu.Serial == DTU_SERIAL) {
|
||||||
MessageOutput.print("generate serial based on ESP chip id: ");
|
MessageOutput.print("generate serial based on ESP chip id: ");
|
||||||
const uint64_t dtuId = Utils::generateDtuSerial();
|
const uint64_t dtuId = Utils::generateDtuSerial();
|
||||||
MessageOutput.printf("%0x%08x... ",
|
MessageOutput.printf("%0x%08x... ",
|
||||||
((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(dtuId & 0xFFFFFFFF)));
|
((uint32_t)(dtuId & 0xFFFFFFFF)));
|
||||||
config.Dtu.Serial = dtuId;
|
config.Dtu.Serial = dtuId;
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
}
|
}
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
InverterSettings.init(scheduler);
|
InverterSettings.init(scheduler);
|
||||||
|
|
||||||
Datastore.init(scheduler);
|
Datastore.init(scheduler);
|
||||||
|
|
||||||
VictronMppt.init(scheduler);
|
VictronMppt.init(scheduler);
|
||||||
|
|
||||||
// Power meter
|
// Power meter
|
||||||
PowerMeter.init(scheduler);
|
PowerMeter.init(scheduler);
|
||||||
|
|
||||||
// Dynamic power limiter
|
// Dynamic power limiter
|
||||||
PowerLimiter.init(scheduler);
|
PowerLimiter.init(scheduler);
|
||||||
|
|
||||||
// Initialize Huawei AC-charger PSU / CAN bus
|
// Initialize Huawei AC-charger PSU / CAN bus
|
||||||
MessageOutput.println("Initialize Huawei AC charger interface... ");
|
MessageOutput.println("Initialize Huawei AC charger interface... ");
|
||||||
if (PinMapping.isValidHuaweiConfig()) {
|
if (PinMapping.isValidHuaweiConfig()) {
|
||||||
MessageOutput.printf("Huawei AC-charger miso = %d, mosi = %d, clk = %d, irq = %d, cs = %d, power_pin = %d\r\n", pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
|
MessageOutput.printf("Huawei AC-charger miso = %d, mosi = %d, clk = %d, irq = %d, cs = %d, power_pin = %d\r\n", pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
|
||||||
HuaweiCan.init(scheduler, pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
|
HuaweiCan.init(scheduler, pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.println("Invalid pin config");
|
MessageOutput.println("Invalid pin config");
|
||||||
}
|
}
|
||||||
|
|
||||||
Battery.init(scheduler);
|
Battery.init(scheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
scheduler.execute();
|
scheduler.execute();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,217 +1,217 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-center" v-if="dataLoading">
|
<div class="text-center" v-if="dataLoading">
|
||||||
<div class="spinner-border" role="status">
|
<div class="spinner-border" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="'values' in batteryData"> <!-- suppress the card for MQTT battery provider -->
|
<div v-else-if="'values' in batteryData"> <!-- suppress the card for MQTT battery provider -->
|
||||||
<div class="row gy-3 mt-0">
|
<div class="row gy-3 mt-0">
|
||||||
<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">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center" :class="{
|
<div class="card-header d-flex justify-content-between align-items-center" :class="{
|
||||||
'text-bg-danger': batteryData.data_age >= 20,
|
'text-bg-danger': batteryData.data_age >= 20,
|
||||||
'text-bg-primary': batteryData.data_age < 20,
|
'text-bg-primary': batteryData.data_age < 20,
|
||||||
}">
|
}">
|
||||||
<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;">
|
||||||
{{ $t('battery.battery') }}: {{ batteryData.manufacturer }}
|
{{ $t('battery.battery') }}: {{ batteryData.manufacturer }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;" v-if="'fwversion' in batteryData">
|
<div style="padding-right: 2em;" v-if="'fwversion' in batteryData">
|
||||||
{{ $t('battery.FwVersion') }}: {{ batteryData.fwversion }}
|
{{ $t('battery.FwVersion') }}: {{ batteryData.fwversion }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;" v-if="'hwversion' in batteryData">
|
<div style="padding-right: 2em;" v-if="'hwversion' in batteryData">
|
||||||
{{ $t('battery.HwVersion') }}: {{ batteryData.hwversion }}
|
{{ $t('battery.HwVersion') }}: {{ batteryData.hwversion }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('battery.DataAge') }} {{ $t('battery.Seconds', { 'val': batteryData.data_age }) }}
|
{{ $t('battery.DataAge') }} {{ $t('battery.Seconds', { 'val': batteryData.data_age }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row flex-row flex-wrap align-items-start g-3">
|
<div class="row flex-row flex-wrap align-items-start g-3">
|
||||||
<div v-for="(values, section) in batteryData.values" v-bind:key="section" class="col order-0">
|
<div v-for="(values, section) in batteryData.values" v-bind:key="section" class="col order-0">
|
||||||
<div class="card" :class="{ 'border-info': true }">
|
<div class="card" :class="{ 'border-info': true }">
|
||||||
<div class="card-header text-bg-info">{{ $t('battery.' + section) }}</div>
|
<div class="card-header text-bg-info">{{ $t('battery.' + section) }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{{ $t('battery.Property') }}</th>
|
<th scope="col">{{ $t('battery.Property') }}</th>
|
||||||
<th style="text-align: right" scope="col">{{ $t('battery.Value') }}</th>
|
<th style="text-align: right" scope="col">{{ $t('battery.Value') }}</th>
|
||||||
<th scope="col">{{ $t('battery.Unit') }}</th>
|
<th scope="col">{{ $t('battery.Unit') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(prop, key) in values" v-bind:key="key">
|
<tr v-for="(prop, key) in values" v-bind:key="key">
|
||||||
<th scope="row">{{ $t('battery.' + key) }}</th>
|
<th scope="row">{{ $t('battery.' + key) }}</th>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
<template v-if="typeof prop === 'string'">
|
<template v-if="typeof prop === 'string'">
|
||||||
{{ $t('battery.' + prop) }}
|
{{ $t('battery.' + prop) }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ $n(prop.v, 'decimal', {
|
{{ $n(prop.v, 'decimal', {
|
||||||
minimumFractionDigits: prop.d,
|
minimumFractionDigits: prop.d,
|
||||||
maximumFractionDigits: prop.d})
|
maximumFractionDigits: prop.d})
|
||||||
}}
|
}}
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="typeof prop === 'string'"></td>
|
<td v-if="typeof prop === 'string'"></td>
|
||||||
<td v-else>{{prop.u}}</td>
|
<td v-else>{{prop.u}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col order-1">
|
<div class="col order-1">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div :class="{'card-header': true, 'border-bottom-0': maxIssueValue === 0}">
|
<div :class="{'card-header': true, 'border-bottom-0': maxIssueValue === 0}">
|
||||||
<div class="d-flex flex-row justify-content-between align-items-baseline">
|
<div class="d-flex flex-row justify-content-between align-items-baseline">
|
||||||
{{ $t('battery.issues') }}
|
{{ $t('battery.issues') }}
|
||||||
<div v-if="maxIssueValue === 0" class="badge text-bg-success">{{ $t('battery.noIssues') }}</div>
|
<div v-if="maxIssueValue === 0" class="badge text-bg-success">{{ $t('battery.noIssues') }}</div>
|
||||||
<div v-else-if="maxIssueValue === 1" class="badge text-bg-warning text-dark">{{ $t('battery.warning') }}</div>
|
<div v-else-if="maxIssueValue === 1" class="badge text-bg-warning text-dark">{{ $t('battery.warning') }}</div>
|
||||||
<div v-else-if="maxIssueValue === 2" class="badge text-bg-danger">{{ $t('battery.alarm') }}</div>
|
<div v-else-if="maxIssueValue === 2" class="badge text-bg-danger">{{ $t('battery.alarm') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" v-if="'issues' in batteryData">
|
<div class="card-body" v-if="'issues' in batteryData">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{{ $t('battery.issueName') }}</th>
|
<th scope="col">{{ $t('battery.issueName') }}</th>
|
||||||
<th scope="col">{{ $t('battery.issueType') }}</th>
|
<th scope="col">{{ $t('battery.issueType') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(prop, key) in batteryData.issues" v-bind:key="key">
|
<tr v-for="(prop, key) in batteryData.issues" v-bind:key="key">
|
||||||
<th scope="row">{{ $t('battery.' + key) }}</th>
|
<th scope="row">{{ $t('battery.' + key) }}</th>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge" :class="{
|
<span class="badge" :class="{
|
||||||
'text-bg-warning text-dark': prop === 1,
|
'text-bg-warning text-dark': prop === 1,
|
||||||
'text-bg-danger': prop === 2
|
'text-bg-danger': prop === 2
|
||||||
}">
|
}">
|
||||||
<template v-if="prop === 1">{{ $t('battery.warning') }}</template>
|
<template v-if="prop === 1">{{ $t('battery.warning') }}</template>
|
||||||
<template v-else>{{ $t('battery.alarm') }}</template>
|
<template v-else>{{ $t('battery.alarm') }}</template>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import type { Battery } from '@/types/BatteryDataStatus';
|
import type { Battery } from '@/types/BatteryDataStatus';
|
||||||
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
|
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
socket: {} as WebSocket,
|
socket: {} as WebSocket,
|
||||||
heartInterval: 0,
|
heartInterval: 0,
|
||||||
dataAgeInterval: 0,
|
dataAgeInterval: 0,
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
batteryData: {} as Battery,
|
batteryData: {} as Battery,
|
||||||
isFirstFetchAfterConnect: true,
|
isFirstFetchAfterConnect: true,
|
||||||
|
|
||||||
alertMessageLimit: "",
|
alertMessageLimit: "",
|
||||||
alertTypeLimit: "info",
|
alertTypeLimit: "info",
|
||||||
showAlertLimit: false,
|
showAlertLimit: false,
|
||||||
checked: false,
|
checked: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getInitialData();
|
this.getInitialData();
|
||||||
this.initSocket();
|
this.initSocket();
|
||||||
this.initDataAgeing();
|
this.initDataAgeing();
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getInitialData() {
|
getInitialData() {
|
||||||
console.log("Get initalData for Battery");
|
console.log("Get initalData for Battery");
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
|
|
||||||
fetch("/api/batterylivedata/status", { headers: authHeader() })
|
fetch("/api/batterylivedata/status", { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.batteryData = data;
|
this.batteryData = data;
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initSocket() {
|
initSocket() {
|
||||||
console.log("Starting connection to Battery WebSocket Server");
|
console.log("Starting connection to Battery WebSocket Server");
|
||||||
|
|
||||||
const { protocol, host } = location;
|
const { protocol, host } = location;
|
||||||
const authString = authUrl();
|
const authString = authUrl();
|
||||||
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
||||||
}://${authString}${host}/batterylivedata`;
|
}://${authString}${host}/batterylivedata`;
|
||||||
|
|
||||||
this.socket = new WebSocket(webSocketUrl);
|
this.socket = new WebSocket(webSocketUrl);
|
||||||
|
|
||||||
this.socket.onmessage = (event) => {
|
this.socket.onmessage = (event) => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
this.batteryData = JSON.parse(event.data);
|
this.batteryData = JSON.parse(event.data);
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
this.heartCheck(); // Reset heartbeat detection
|
this.heartCheck(); // Reset heartbeat detection
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onopen = function (event) {
|
this.socket.onopen = function (event) {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
console.log("Successfully connected to the Battery websocket server...");
|
console.log("Successfully connected to the Battery websocket server...");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||||
window.onbeforeunload = () => {
|
window.onbeforeunload = () => {
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
initDataAgeing() {
|
initDataAgeing() {
|
||||||
this.dataAgeInterval = setInterval(() => {
|
this.dataAgeInterval = setInterval(() => {
|
||||||
if (this.batteryData) {
|
if (this.batteryData) {
|
||||||
this.batteryData.data_age++;
|
this.batteryData.data_age++;
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
},
|
},
|
||||||
// Send heartbeat packets regularly * 59s Send a heartbeat
|
// Send heartbeat packets regularly * 59s Send a heartbeat
|
||||||
heartCheck() {
|
heartCheck() {
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
this.heartInterval && clearTimeout(this.heartInterval);
|
||||||
this.heartInterval = setInterval(() => {
|
this.heartInterval = setInterval(() => {
|
||||||
if (this.socket.readyState === 1) {
|
if (this.socket.readyState === 1) {
|
||||||
// Connection status
|
// Connection status
|
||||||
this.socket.send("ping");
|
this.socket.send("ping");
|
||||||
} else {
|
} else {
|
||||||
this.initSocket(); // Breakpoint reconnection 5 Time
|
this.initSocket(); // Breakpoint reconnection 5 Time
|
||||||
}
|
}
|
||||||
}, 59 * 1000);
|
}, 59 * 1000);
|
||||||
},
|
},
|
||||||
/** To break off websocket Connect */
|
/** To break off websocket Connect */
|
||||||
closeSocket() {
|
closeSocket() {
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
this.heartInterval && clearTimeout(this.heartInterval);
|
||||||
this.isFirstFetchAfterConnect = true;
|
this.isFirstFetchAfterConnect = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
maxIssueValue() {
|
maxIssueValue() {
|
||||||
return ('issues' in this.batteryData)?Math.max(...Object.values(this.batteryData.issues)):0;
|
return ('issues' in this.batteryData)?Math.max(...Object.values(this.batteryData.issues)):0;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -1,358 +1,358 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-center" v-if="dataLoading">
|
<div class="text-center" v-if="dataLoading">
|
||||||
<div class="spinner-border" role="status">
|
<div class="spinner-border" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="row gy-3 mt-0">
|
<div class="row gy-3 mt-0">
|
||||||
<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">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center" :class="{
|
<div class="card-header d-flex justify-content-between align-items-center" :class="{
|
||||||
'text-bg-danger': huaweiData.data_age > 20,
|
'text-bg-danger': huaweiData.data_age > 20,
|
||||||
'text-bg-primary': huaweiData.data_age < 19,
|
'text-bg-primary': huaweiData.data_age < 19,
|
||||||
}">
|
}">
|
||||||
<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;">
|
||||||
Huawei R4850G2
|
Huawei R4850G2
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('huawei.DataAge') }} {{ $t('huawei.Seconds', { 'val': huaweiData.data_age }) }}
|
{{ $t('huawei.DataAge') }} {{ $t('huawei.Seconds', { 'val': huaweiData.data_age }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-toolbar p-2" role="toolbar">
|
<div class="btn-toolbar p-2" role="toolbar">
|
||||||
<div class="btn-group me-2" role="group">
|
<div class="btn-group me-2" role="group">
|
||||||
<button :disabled="false" type="button" class="btn btn-sm btn-danger" @click="onShowLimitSettings()"
|
<button :disabled="false" type="button" class="btn btn-sm btn-danger" @click="onShowLimitSettings()"
|
||||||
v-tooltip :title="$t('huawei.ShowSetLimit')">
|
v-tooltip :title="$t('huawei.ShowSetLimit')">
|
||||||
<BIconSpeedometer style="font-size:24px;" />
|
<BIconSpeedometer style="font-size:24px;" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row flex-row flex-wrap align-items-start g-3">
|
<div class="row flex-row flex-wrap align-items-start g-3">
|
||||||
<div class="col order-0">
|
<div class="col order-0">
|
||||||
<div class="card" :class="{ 'border-info': true }">
|
<div class="card" :class="{ 'border-info': true }">
|
||||||
<div class="card-header bg-info">{{ $t('huawei.Input') }}</div>
|
<div class="card-header bg-info">{{ $t('huawei.Input') }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{{ $t('huawei.Property') }}</th>
|
<th scope="col">{{ $t('huawei.Property') }}</th>
|
||||||
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
|
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
|
||||||
<th scope="col">{{ $t('huawei.Unit') }}</th>
|
<th scope="col">{{ $t('huawei.Unit') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.input_voltage') }}</th>
|
<th scope="row">{{ $t('huawei.input_voltage') }}</th>
|
||||||
<td style="text-align: right">{{ formatNumber(huaweiData.input_voltage.v) }}</td>
|
<td style="text-align: right">{{ formatNumber(huaweiData.input_voltage.v) }}</td>
|
||||||
<td>{{ huaweiData.input_voltage.u }}</td>
|
<td>{{ huaweiData.input_voltage.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.input_current') }}</th>
|
<th scope="row">{{ $t('huawei.input_current') }}</th>
|
||||||
<td style="text-align: right">{{ formatNumber(huaweiData.input_current.v) }}</td>
|
<td style="text-align: right">{{ formatNumber(huaweiData.input_current.v) }}</td>
|
||||||
<td>{{ huaweiData.input_current.u }}</td>
|
<td>{{ huaweiData.input_current.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.input_power') }}</th>
|
<th scope="row">{{ $t('huawei.input_power') }}</th>
|
||||||
<td style="text-align: right">{{ formatNumber(huaweiData.input_power.v) }}</td>
|
<td style="text-align: right">{{ formatNumber(huaweiData.input_power.v) }}</td>
|
||||||
<td>{{ huaweiData.input_power.u }}</td>
|
<td>{{ huaweiData.input_power.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.input_temp') }}</th>
|
<th scope="row">{{ $t('huawei.input_temp') }}</th>
|
||||||
<td style="text-align: right">{{ Math.round(huaweiData.input_temp.v) }}</td>
|
<td style="text-align: right">{{ Math.round(huaweiData.input_temp.v) }}</td>
|
||||||
<td>{{ huaweiData.input_temp.u }}</td>
|
<td>{{ huaweiData.input_temp.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.efficiency') }}</th>
|
<th scope="row">{{ $t('huawei.efficiency') }}</th>
|
||||||
<td style="text-align: right">{{ huaweiData.efficiency.v.toFixed(1) }}</td>
|
<td style="text-align: right">{{ huaweiData.efficiency.v.toFixed(1) }}</td>
|
||||||
<td>{{ huaweiData.efficiency.u }}</td>
|
<td>{{ huaweiData.efficiency.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col order-1">
|
<div class="col order-1">
|
||||||
<div class="card" :class="{ 'border-info': false }">
|
<div class="card" :class="{ 'border-info': false }">
|
||||||
<div class="card-header bg-info">{{ $t('huawei.Output') }}</div>
|
<div class="card-header bg-info">{{ $t('huawei.Output') }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{{ $t('huawei.Property') }}</th>
|
<th scope="col">{{ $t('huawei.Property') }}</th>
|
||||||
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
|
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
|
||||||
<th scope="col">{{ $t('huawei.Unit') }}</th>
|
<th scope="col">{{ $t('huawei.Unit') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.output_voltage') }}</th>
|
<th scope="row">{{ $t('huawei.output_voltage') }}</th>
|
||||||
<td style="text-align: right">{{ huaweiData.output_voltage.v.toFixed(1) }}</td>
|
<td style="text-align: right">{{ huaweiData.output_voltage.v.toFixed(1) }}</td>
|
||||||
<td>{{ huaweiData.output_voltage.u }}</td>
|
<td>{{ huaweiData.output_voltage.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.output_current') }}</th>
|
<th scope="row">{{ $t('huawei.output_current') }}</th>
|
||||||
<td style="text-align: right">{{ huaweiData.output_current.v.toFixed(2) }}</td>
|
<td style="text-align: right">{{ huaweiData.output_current.v.toFixed(2) }}</td>
|
||||||
<td>{{ huaweiData.output_current.u }}</td>
|
<td>{{ huaweiData.output_current.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.max_output_current') }}</th>
|
<th scope="row">{{ $t('huawei.max_output_current') }}</th>
|
||||||
<td style="text-align: right">{{ huaweiData.max_output_current.v.toFixed(1) }}</td>
|
<td style="text-align: right">{{ huaweiData.max_output_current.v.toFixed(1) }}</td>
|
||||||
<td>{{ huaweiData.max_output_current.u }}</td>
|
<td>{{ huaweiData.max_output_current.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.output_power') }}</th>
|
<th scope="row">{{ $t('huawei.output_power') }}</th>
|
||||||
<td style="text-align: right">{{ huaweiData.output_power.v.toFixed(1) }}</td>
|
<td style="text-align: right">{{ huaweiData.output_power.v.toFixed(1) }}</td>
|
||||||
<td>{{ huaweiData.output_power.u }}</td>
|
<td>{{ huaweiData.output_power.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ $t('huawei.output_temp') }}</th>
|
<th scope="row">{{ $t('huawei.output_temp') }}</th>
|
||||||
<td style="text-align: right">{{ Math.round(huaweiData.output_temp.v) }}</td>
|
<td style="text-align: right">{{ Math.round(huaweiData.output_temp.v) }}</td>
|
||||||
<td>{{ huaweiData.output_temp.u }}</td>
|
<td>{{ huaweiData.output_temp.u }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal" id="huaweiLimitSettingView" ref="huaweiLimitSettingView" tabindex="-1">
|
<div class="modal" id="huaweiLimitSettingView" ref="huaweiLimitSettingView" tabindex="-1">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<form @submit="onSubmitLimit">
|
<form @submit="onSubmitLimit">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">{{ $t('huawei.LimitSettings') }}</h5>
|
<h5 class="modal-title">{{ $t('huawei.LimitSettings') }}</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label for="inputCurrentLimit" class="col-sm-3 col-form-label">{{ $t('huawei.CurrentLimit') }} </label>
|
<label for="inputCurrentLimit" class="col-sm-3 col-form-label">{{ $t('huawei.CurrentLimit') }} </label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3 align-items-center">
|
<div class="row mb-3 align-items-center">
|
||||||
<label for="inputVoltageTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetVoltageLimit')
|
<label for="inputVoltageTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetVoltageLimit')
|
||||||
}}</label>
|
}}</label>
|
||||||
|
|
||||||
<div class="col-sm-1">
|
<div class="col-sm-1">
|
||||||
<div class="form-switch form-check-inline">
|
<div class="form-switch form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="flexSwitchVoltage"
|
<input class="form-check-input" type="checkbox" id="flexSwitchVoltage"
|
||||||
v-model="targetLimitList.voltage_valid">
|
v-model="targetLimitList.voltage_valid">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-7">
|
<div class="col-sm-7">
|
||||||
<input type="number" step="0.01" name="inputVoltageTargetLimit" class="form-control" id="inputVoltageTargetLimit"
|
<input type="number" step="0.01" name="inputVoltageTargetLimit" class="form-control" id="inputVoltageTargetLimit"
|
||||||
:min="targetVoltageLimitMin" :max="targetVoltageLimitMax" v-model="targetLimitList.voltage"
|
:min="targetVoltageLimitMin" :max="targetVoltageLimitMax" v-model="targetLimitList.voltage"
|
||||||
:disabled=!targetLimitList.voltage_valid>
|
:disabled=!targetLimitList.voltage_valid>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div v-if="targetLimitList.voltage < targetVoltageLimitMinOffline" class="alert alert-secondary mt-3"
|
<div v-if="targetLimitList.voltage < targetVoltageLimitMinOffline" class="alert alert-secondary mt-3"
|
||||||
role="alert" v-html="$t('huawei.LimitHint')"></div>
|
role="alert" v-html="$t('huawei.LimitHint')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3 align-items-center">
|
<div class="row mb-3 align-items-center">
|
||||||
<label for="inputCurrentTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetCurrentLimit')
|
<label for="inputCurrentTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetCurrentLimit')
|
||||||
}}</label>
|
}}</label>
|
||||||
|
|
||||||
<div class="col-sm-1">
|
<div class="col-sm-1">
|
||||||
<div class="form-switch form-check-inline">
|
<div class="form-switch form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="flexSwitchCurrentt"
|
<input class="form-check-input" type="checkbox" id="flexSwitchCurrentt"
|
||||||
v-model="targetLimitList.current_valid">
|
v-model="targetLimitList.current_valid">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-7">
|
<div class="col-sm-7">
|
||||||
<input type="number" step="0.1" name="inputCurrentTargetLimit" class="form-control" id="inputCurrentTargetLimit"
|
<input type="number" step="0.1" name="inputCurrentTargetLimit" class="form-control" id="inputCurrentTargetLimit"
|
||||||
:min="targetCurrentLimitMin" :max="targetCurrentLimitMax" v-model="targetLimitList.current"
|
:min="targetCurrentLimitMin" :max="targetCurrentLimitMax" v-model="targetLimitList.current"
|
||||||
:disabled=!targetLimitList.current_valid>
|
:disabled=!targetLimitList.current_valid>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(true)">{{ $t('huawei.SetOnline')
|
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(true)">{{ $t('huawei.SetOnline')
|
||||||
}}</button>
|
}}</button>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(false)">{{
|
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(false)">{{
|
||||||
$t('huawei.SetOffline')
|
$t('huawei.SetOffline')
|
||||||
}}</button>
|
}}</button>
|
||||||
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t('huawei.Close') }}</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t('huawei.Close') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import type { Huawei } from '@/types/HuaweiDataStatus';
|
import type { Huawei } from '@/types/HuaweiDataStatus';
|
||||||
import type { HuaweiLimitConfig } from '@/types/HuaweiLimitConfig';
|
import type { HuaweiLimitConfig } from '@/types/HuaweiLimitConfig';
|
||||||
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
|
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
|
||||||
|
|
||||||
import * as bootstrap from 'bootstrap';
|
import * as bootstrap from 'bootstrap';
|
||||||
import {
|
import {
|
||||||
BIconSpeedometer,
|
BIconSpeedometer,
|
||||||
} from 'bootstrap-icons-vue';
|
} from 'bootstrap-icons-vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
BIconSpeedometer
|
BIconSpeedometer
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
socket: {} as WebSocket,
|
socket: {} as WebSocket,
|
||||||
heartInterval: 0,
|
heartInterval: 0,
|
||||||
dataAgeInterval: 0,
|
dataAgeInterval: 0,
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
huaweiData: {} as Huawei,
|
huaweiData: {} as Huawei,
|
||||||
isFirstFetchAfterConnect: true,
|
isFirstFetchAfterConnect: true,
|
||||||
targetVoltageLimitMin: 42,
|
targetVoltageLimitMin: 42,
|
||||||
targetVoltageLimitMinOffline: 48,
|
targetVoltageLimitMinOffline: 48,
|
||||||
targetVoltageLimitMax: 58,
|
targetVoltageLimitMax: 58,
|
||||||
targetCurrentLimitMin: 0,
|
targetCurrentLimitMin: 0,
|
||||||
targetCurrentLimitMax: 60,
|
targetCurrentLimitMax: 60,
|
||||||
targetLimitList: {} as HuaweiLimitConfig,
|
targetLimitList: {} as HuaweiLimitConfig,
|
||||||
targetLimitPersistent: false,
|
targetLimitPersistent: false,
|
||||||
huaweiLimitSettingView: {} as bootstrap.Modal,
|
huaweiLimitSettingView: {} as bootstrap.Modal,
|
||||||
|
|
||||||
alertMessageLimit: "",
|
alertMessageLimit: "",
|
||||||
alertTypeLimit: "info",
|
alertTypeLimit: "info",
|
||||||
showAlertLimit: false,
|
showAlertLimit: false,
|
||||||
checked: false,
|
checked: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getInitialData();
|
this.getInitialData();
|
||||||
this.initSocket();
|
this.initSocket();
|
||||||
this.initDataAgeing();
|
this.initDataAgeing();
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getInitialData() {
|
getInitialData() {
|
||||||
console.log("Get initalData for Huawei");
|
console.log("Get initalData for Huawei");
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
|
|
||||||
fetch("/api/huaweilivedata/status", { headers: authHeader() })
|
fetch("/api/huaweilivedata/status", { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.huaweiData = data;
|
this.huaweiData = data;
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initSocket() {
|
initSocket() {
|
||||||
console.log("Starting connection to Huawei WebSocket Server");
|
console.log("Starting connection to Huawei WebSocket Server");
|
||||||
|
|
||||||
const { protocol, host } = location;
|
const { protocol, host } = location;
|
||||||
const authString = authUrl();
|
const authString = authUrl();
|
||||||
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
||||||
}://${authString}${host}/huaweilivedata`;
|
}://${authString}${host}/huaweilivedata`;
|
||||||
|
|
||||||
this.socket = new WebSocket(webSocketUrl);
|
this.socket = new WebSocket(webSocketUrl);
|
||||||
|
|
||||||
this.socket.onmessage = (event) => {
|
this.socket.onmessage = (event) => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
this.huaweiData = JSON.parse(event.data);
|
this.huaweiData = JSON.parse(event.data);
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
this.heartCheck(); // Reset heartbeat detection
|
this.heartCheck(); // Reset heartbeat detection
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onopen = function (event) {
|
this.socket.onopen = function (event) {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
console.log("Successfully connected to the Huawei websocket server...");
|
console.log("Successfully connected to the Huawei websocket server...");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||||
window.onbeforeunload = () => {
|
window.onbeforeunload = () => {
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
initDataAgeing() {
|
initDataAgeing() {
|
||||||
this.dataAgeInterval = setInterval(() => {
|
this.dataAgeInterval = setInterval(() => {
|
||||||
if (this.huaweiData) {
|
if (this.huaweiData) {
|
||||||
this.huaweiData.data_age++;
|
this.huaweiData.data_age++;
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
},
|
},
|
||||||
// Send heartbeat packets regularly * 59s Send a heartbeat
|
// Send heartbeat packets regularly * 59s Send a heartbeat
|
||||||
heartCheck() {
|
heartCheck() {
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
this.heartInterval && clearTimeout(this.heartInterval);
|
||||||
this.heartInterval = setInterval(() => {
|
this.heartInterval = setInterval(() => {
|
||||||
if (this.socket.readyState === 1) {
|
if (this.socket.readyState === 1) {
|
||||||
// Connection status
|
// Connection status
|
||||||
this.socket.send("ping");
|
this.socket.send("ping");
|
||||||
} else {
|
} else {
|
||||||
this.initSocket(); // Breakpoint reconnection 5 Time
|
this.initSocket(); // Breakpoint reconnection 5 Time
|
||||||
}
|
}
|
||||||
}, 59 * 1000);
|
}, 59 * 1000);
|
||||||
},
|
},
|
||||||
/** To break off websocket Connect */
|
/** To break off websocket Connect */
|
||||||
closeSocket() {
|
closeSocket() {
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
this.heartInterval && clearTimeout(this.heartInterval);
|
||||||
this.isFirstFetchAfterConnect = true;
|
this.isFirstFetchAfterConnect = true;
|
||||||
},
|
},
|
||||||
formatNumber(num: number) {
|
formatNumber(num: number) {
|
||||||
return new Intl.NumberFormat(
|
return new Intl.NumberFormat(
|
||||||
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
|
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
|
||||||
).format(num);
|
).format(num);
|
||||||
},
|
},
|
||||||
onHideLimitSettings() {
|
onHideLimitSettings() {
|
||||||
this.showAlertLimit = false;
|
this.showAlertLimit = false;
|
||||||
},
|
},
|
||||||
onShowLimitSettings() {
|
onShowLimitSettings() {
|
||||||
this.huaweiLimitSettingView = new bootstrap.Modal('#huaweiLimitSettingView');
|
this.huaweiLimitSettingView = new bootstrap.Modal('#huaweiLimitSettingView');
|
||||||
this.huaweiLimitSettingView.show();
|
this.huaweiLimitSettingView.show();
|
||||||
},
|
},
|
||||||
onSetLimitSettings(online: boolean) {
|
onSetLimitSettings(online: boolean) {
|
||||||
this.targetLimitList.online = online;
|
this.targetLimitList.online = online;
|
||||||
},
|
},
|
||||||
onSubmitLimit(e: Event) {
|
onSubmitLimit(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.targetLimitList));
|
formData.append("data", JSON.stringify(this.targetLimitList));
|
||||||
|
|
||||||
console.log(this.targetLimitList);
|
console.log(this.targetLimitList);
|
||||||
|
|
||||||
fetch("/api/huawei/limit/config", {
|
fetch("/api/huawei/limit/config", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then(
|
||||||
(response) => {
|
(response) => {
|
||||||
if (response.type == "success") {
|
if (response.type == "success") {
|
||||||
this.huaweiLimitSettingView.hide();
|
this.huaweiLimitSettingView.hide();
|
||||||
} else {
|
} else {
|
||||||
this.alertMessageLimit = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessageLimit = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertTypeLimit = response.type;
|
this.alertTypeLimit = response.type;
|
||||||
this.showAlertLimit = true;
|
this.showAlertLimit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,14 @@
|
|||||||
export interface AcChargerConfig {
|
export interface AcChargerConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
verbose_logging: boolean;
|
verbose_logging: boolean;
|
||||||
can_controller_frequency: number;
|
can_controller_frequency: number;
|
||||||
auto_power_enabled: boolean;
|
auto_power_enabled: boolean;
|
||||||
auto_power_batterysoc_limits_enabled: boolean;
|
auto_power_batterysoc_limits_enabled: boolean;
|
||||||
voltage_limit: number;
|
voltage_limit: number;
|
||||||
enable_voltage_limit: number;
|
enable_voltage_limit: number;
|
||||||
lower_power_limit: number;
|
lower_power_limit: number;
|
||||||
upper_power_limit: number;
|
upper_power_limit: number;
|
||||||
emergency_charge_enabled: boolean;
|
emergency_charge_enabled: boolean;
|
||||||
stop_batterysoc_threshold: number;
|
stop_batterysoc_threshold: number;
|
||||||
target_power_consumption: number;
|
target_power_consumption: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import type { ValueObject } from '@/types/LiveDataStatus';
|
import type { ValueObject } from '@/types/LiveDataStatus';
|
||||||
|
|
||||||
type BatteryData = (ValueObject | string)[];
|
type BatteryData = (ValueObject | string)[];
|
||||||
|
|
||||||
export interface Battery {
|
export interface Battery {
|
||||||
manufacturer: string;
|
manufacturer: string;
|
||||||
fwversion: string;
|
fwversion: string;
|
||||||
hwversion: string;
|
hwversion: string;
|
||||||
data_age: number;
|
data_age: number;
|
||||||
values: BatteryData[];
|
values: BatteryData[];
|
||||||
issues: number[];
|
issues: number[];
|
||||||
}
|
}
|
||||||
@ -1,18 +1,18 @@
|
|||||||
import type { ValueObject } from '@/types/LiveDataStatus';
|
import type { ValueObject } from '@/types/LiveDataStatus';
|
||||||
|
|
||||||
// Huawei
|
// Huawei
|
||||||
export interface Huawei {
|
export interface Huawei {
|
||||||
data_age: 0;
|
data_age: 0;
|
||||||
input_voltage: ValueObject;
|
input_voltage: ValueObject;
|
||||||
input_frequency: ValueObject;
|
input_frequency: ValueObject;
|
||||||
input_current: ValueObject;
|
input_current: ValueObject;
|
||||||
input_power: ValueObject;
|
input_power: ValueObject;
|
||||||
input_temp: ValueObject;
|
input_temp: ValueObject;
|
||||||
efficiency: ValueObject;
|
efficiency: ValueObject;
|
||||||
output_voltage: ValueObject;
|
output_voltage: ValueObject;
|
||||||
output_current: ValueObject;
|
output_current: ValueObject;
|
||||||
max_output_current: ValueObject;
|
max_output_current: ValueObject;
|
||||||
output_power: ValueObject;
|
output_power: ValueObject;
|
||||||
output_temp: ValueObject;
|
output_temp: ValueObject;
|
||||||
amp_hour: ValueObject;
|
amp_hour: ValueObject;
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
export interface HuaweiLimitConfig {
|
export interface HuaweiLimitConfig {
|
||||||
voltage: number;
|
voltage: number;
|
||||||
voltage_valid: boolean;
|
voltage_valid: boolean;
|
||||||
current: number;
|
current: number;
|
||||||
current_valid: boolean;
|
current_valid: boolean;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user