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:
Bernhard Kirchen 2024-06-24 21:53:22 +02:00
parent 2cc086335f
commit 83c59d7811
22 changed files with 3657 additions and 3657 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}; };

View File

@ -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();
}; };

View File

@ -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();
}; };

File diff suppressed because it is too large Load Diff

View File

@ -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();
} }
} }

View File

@ -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;
} }
} }

View File

@ -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();
} }

View File

@ -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);
} }

View File

@ -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);
} }
} }

View File

@ -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);
} }
} }

View File

@ -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();
} }

View File

@ -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>

View File

@ -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

View File

@ -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;
} }

View File

@ -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[];
} }

View File

@ -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;
} }

View File

@ -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;
} }