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
#pragma once
#include <cstdint>
#include "SPI.h"
#include <mcp_can.h>
#include <mutex>
#include <TaskSchedulerDeclarations.h>
#ifndef HUAWEI_PIN_MISO
#define HUAWEI_PIN_MISO 12
#endif
#ifndef HUAWEI_PIN_MOSI
#define HUAWEI_PIN_MOSI 13
#endif
#ifndef HUAWEI_PIN_SCLK
#define HUAWEI_PIN_SCLK 26
#endif
#ifndef HUAWEI_PIN_IRQ
#define HUAWEI_PIN_IRQ 25
#endif
#ifndef HUAWEI_PIN_CS
#define HUAWEI_PIN_CS 15
#endif
#ifndef HUAWEI_PIN_POWER
#define HUAWEI_PIN_POWER 33
#endif
#define HUAWEI_MINIMAL_OFFLINE_VOLTAGE 48
#define HUAWEI_MINIMAL_ONLINE_VOLTAGE 42
#define MAX_CURRENT_MULTIPLIER 20
// Index values for rec_values array
#define HUAWEI_INPUT_POWER_IDX 0
#define HUAWEI_INPUT_FREQ_IDX 1
#define HUAWEI_INPUT_CURRENT_IDX 2
#define HUAWEI_OUTPUT_POWER_IDX 3
#define HUAWEI_EFFICIENCY_IDX 4
#define HUAWEI_OUTPUT_VOLTAGE_IDX 5
#define HUAWEI_OUTPUT_CURRENT_MAX_IDX 6
#define HUAWEI_INPUT_VOLTAGE_IDX 7
#define HUAWEI_OUTPUT_TEMPERATURE_IDX 8
#define HUAWEI_INPUT_TEMPERATURE_IDX 9
#define HUAWEI_OUTPUT_CURRENT_IDX 10
#define HUAWEI_OUTPUT_CURRENT1_IDX 11
// Defines and index values for tx_values array
#define HUAWEI_OFFLINE_VOLTAGE 0x01
#define HUAWEI_ONLINE_VOLTAGE 0x00
#define HUAWEI_OFFLINE_CURRENT 0x04
#define HUAWEI_ONLINE_CURRENT 0x03
// Modes of operation
#define HUAWEI_MODE_OFF 0
#define HUAWEI_MODE_ON 1
#define HUAWEI_MODE_AUTO_EXT 2
#define HUAWEI_MODE_AUTO_INT 3
// Error codes
#define HUAWEI_ERROR_CODE_RX 0x01
#define HUAWEI_ERROR_CODE_TX 0x02
// Wait time/current before shuting down the PSU / charger
// This is set to allow the fan to run for some time
#define HUAWEI_AUTO_MODE_SHUTDOWN_DELAY 60000
#define HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT 0.75
// Updateinterval used to request new values from the PSU
#define HUAWEI_DATA_REQUEST_INTERVAL_MS 2500
typedef struct RectifierParameters {
float input_voltage;
float input_frequency;
float input_current;
float input_power;
float input_temp;
float efficiency;
float output_voltage;
float output_current;
float max_output_current;
float output_power;
float output_temp;
float amp_hour;
} RectifierParameters_t;
class HuaweiCanCommClass {
public:
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);
void loop();
bool gotNewRxDataFrame(bool clear);
uint8_t getErrorCode(bool clear);
uint32_t getParameterValue(uint8_t parameter);
void setParameterValue(uint16_t in, uint8_t parameterType);
private:
void sendRequest();
SPIClass *SPI;
MCP_CAN *_CAN;
uint8_t _huaweiIrq; // IRQ pin
uint32_t _nextRequestMillis = 0; // When to send next data request to PSU
std::mutex _mutex;
uint32_t _recValues[12];
uint16_t _txValues[5];
bool _hasNewTxValue[5];
uint8_t _errorCode;
bool _completeUpdateReceived;
};
class HuaweiCanClass {
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 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 setMode(uint8_t mode);
RectifierParameters_t * get();
uint32_t getLastUpdate() const { return _lastUpdateReceivedMillis; };
bool getAutoPowerStatus() const { return _autoPowerEnabled; };
uint8_t getMode() const { return _mode; };
private:
void loop();
void processReceivedParameters();
void _setValue(float in, uint8_t parameterType);
Task _loopTask;
TaskHandle_t _HuaweiCanCommunicationTaskHdl = NULL;
bool _initialized = false;
uint8_t _huaweiPower; // Power pin
uint8_t _mode = HUAWEI_MODE_AUTO_EXT;
RectifierParameters_t _rp;
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 _nextAutoModePeriodicIntMillis; // When to set the next output voltage in automatic mode
uint32_t _lastPowerMeterUpdateReceivedMillis; // Timestamp of last seen power meter value
uint32_t _autoModeBlockedTillMillis = 0; // Timestamp to block running auto mode for some time
uint8_t _autoPowerEnabledCounter = 0;
bool _autoPowerEnabled = false;
bool _batteryEmergencyCharging = false;
};
extern HuaweiCanClass HuaweiCan;
extern HuaweiCanCommClass HuaweiCanComm;
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include "SPI.h"
#include <mcp_can.h>
#include <mutex>
#include <TaskSchedulerDeclarations.h>
#ifndef HUAWEI_PIN_MISO
#define HUAWEI_PIN_MISO 12
#endif
#ifndef HUAWEI_PIN_MOSI
#define HUAWEI_PIN_MOSI 13
#endif
#ifndef HUAWEI_PIN_SCLK
#define HUAWEI_PIN_SCLK 26
#endif
#ifndef HUAWEI_PIN_IRQ
#define HUAWEI_PIN_IRQ 25
#endif
#ifndef HUAWEI_PIN_CS
#define HUAWEI_PIN_CS 15
#endif
#ifndef HUAWEI_PIN_POWER
#define HUAWEI_PIN_POWER 33
#endif
#define HUAWEI_MINIMAL_OFFLINE_VOLTAGE 48
#define HUAWEI_MINIMAL_ONLINE_VOLTAGE 42
#define MAX_CURRENT_MULTIPLIER 20
// Index values for rec_values array
#define HUAWEI_INPUT_POWER_IDX 0
#define HUAWEI_INPUT_FREQ_IDX 1
#define HUAWEI_INPUT_CURRENT_IDX 2
#define HUAWEI_OUTPUT_POWER_IDX 3
#define HUAWEI_EFFICIENCY_IDX 4
#define HUAWEI_OUTPUT_VOLTAGE_IDX 5
#define HUAWEI_OUTPUT_CURRENT_MAX_IDX 6
#define HUAWEI_INPUT_VOLTAGE_IDX 7
#define HUAWEI_OUTPUT_TEMPERATURE_IDX 8
#define HUAWEI_INPUT_TEMPERATURE_IDX 9
#define HUAWEI_OUTPUT_CURRENT_IDX 10
#define HUAWEI_OUTPUT_CURRENT1_IDX 11
// Defines and index values for tx_values array
#define HUAWEI_OFFLINE_VOLTAGE 0x01
#define HUAWEI_ONLINE_VOLTAGE 0x00
#define HUAWEI_OFFLINE_CURRENT 0x04
#define HUAWEI_ONLINE_CURRENT 0x03
// Modes of operation
#define HUAWEI_MODE_OFF 0
#define HUAWEI_MODE_ON 1
#define HUAWEI_MODE_AUTO_EXT 2
#define HUAWEI_MODE_AUTO_INT 3
// Error codes
#define HUAWEI_ERROR_CODE_RX 0x01
#define HUAWEI_ERROR_CODE_TX 0x02
// Wait time/current before shuting down the PSU / charger
// This is set to allow the fan to run for some time
#define HUAWEI_AUTO_MODE_SHUTDOWN_DELAY 60000
#define HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT 0.75
// Updateinterval used to request new values from the PSU
#define HUAWEI_DATA_REQUEST_INTERVAL_MS 2500
typedef struct RectifierParameters {
float input_voltage;
float input_frequency;
float input_current;
float input_power;
float input_temp;
float efficiency;
float output_voltage;
float output_current;
float max_output_current;
float output_power;
float output_temp;
float amp_hour;
} RectifierParameters_t;
class HuaweiCanCommClass {
public:
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);
void loop();
bool gotNewRxDataFrame(bool clear);
uint8_t getErrorCode(bool clear);
uint32_t getParameterValue(uint8_t parameter);
void setParameterValue(uint16_t in, uint8_t parameterType);
private:
void sendRequest();
SPIClass *SPI;
MCP_CAN *_CAN;
uint8_t _huaweiIrq; // IRQ pin
uint32_t _nextRequestMillis = 0; // When to send next data request to PSU
std::mutex _mutex;
uint32_t _recValues[12];
uint16_t _txValues[5];
bool _hasNewTxValue[5];
uint8_t _errorCode;
bool _completeUpdateReceived;
};
class HuaweiCanClass {
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 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 setMode(uint8_t mode);
RectifierParameters_t * get();
uint32_t getLastUpdate() const { return _lastUpdateReceivedMillis; };
bool getAutoPowerStatus() const { return _autoPowerEnabled; };
uint8_t getMode() const { return _mode; };
private:
void loop();
void processReceivedParameters();
void _setValue(float in, uint8_t parameterType);
Task _loopTask;
TaskHandle_t _HuaweiCanCommunicationTaskHdl = NULL;
bool _initialized = false;
uint8_t _huaweiPower; // Power pin
uint8_t _mode = HUAWEI_MODE_AUTO_EXT;
RectifierParameters_t _rp;
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 _nextAutoModePeriodicIntMillis; // When to set the next output voltage in automatic mode
uint32_t _lastPowerMeterUpdateReceivedMillis; // Timestamp of last seen power meter value
uint32_t _autoModeBlockedTillMillis = 0; // Timestamp to block running auto mode for some time
uint8_t _autoPowerEnabledCounter = 0;
bool _autoPowerEnabled = false;
bool _batteryEmergencyCharging = false;
};
extern HuaweiCanClass HuaweiCan;
extern HuaweiCanCommClass HuaweiCanComm;

View File

@ -1,41 +1,41 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <AsyncWebSocket.h>
#include <TaskSchedulerDeclarations.h>
#include <Print.h>
#include <freertos/task.h>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <queue>
class MessageOutputClass : public Print {
public:
MessageOutputClass();
void init(Scheduler& scheduler);
size_t write(uint8_t c) override;
size_t write(const uint8_t* buffer, size_t size) override;
void register_ws_output(AsyncWebSocket* output);
private:
void loop();
Task _loopTask;
using message_t = std::vector<uint8_t>;
// 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.
// this way we prevent mangling of messages from different contexts.
std::unordered_map<TaskHandle_t, message_t> _task_messages;
std::queue<message_t> _lines;
AsyncWebSocket* _ws = nullptr;
std::mutex _msgLock;
void serialWrite(message_t const& m);
};
extern MessageOutputClass MessageOutput;
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <AsyncWebSocket.h>
#include <TaskSchedulerDeclarations.h>
#include <Print.h>
#include <freertos/task.h>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <queue>
class MessageOutputClass : public Print {
public:
MessageOutputClass();
void init(Scheduler& scheduler);
size_t write(uint8_t c) override;
size_t write(const uint8_t* buffer, size_t size) override;
void register_ws_output(AsyncWebSocket* output);
private:
void loop();
Task _loopTask;
using message_t = std::vector<uint8_t>;
// 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.
// this way we prevent mangling of messages from different contexts.
std::unordered_map<TaskHandle_t, message_t> _task_messages;
std::queue<message_t> _lines;
AsyncWebSocket* _ws = nullptr;
std::mutex _msgLock;
void serialWrite(message_t const& m);
};
extern MessageOutputClass MessageOutput;

View File

@ -1,44 +1,44 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <Huawei_can.h>
#include <espMqttClient.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <deque>
#include <functional>
class MqttHandleHuaweiClass {
public:
void init(Scheduler& scheduler);
private:
void loop();
enum class Topic : unsigned {
LimitOnlineVoltage,
LimitOnlineCurrent,
LimitOfflineVoltage,
LimitOfflineCurrent,
Mode
};
void onMqttMessage(Topic t,
const espMqttClientTypes::MessageProperties& properties,
const char* topic, const uint8_t* payload, size_t len,
size_t index, size_t total);
Task _loopTask;
uint32_t _lastPublishStats;
uint32_t _lastPublish;
// MQTT callbacks to process updates on subscribed topics are executed in
// the MQTT thread's context. we use this queue to switch processing the
// user requests into the main loop's context (TaskScheduler context).
mutable std::mutex _mqttMutex;
std::deque<std::function<void()>> _mqttCallbacks;
};
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <Huawei_can.h>
#include <espMqttClient.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <deque>
#include <functional>
class MqttHandleHuaweiClass {
public:
void init(Scheduler& scheduler);
private:
void loop();
enum class Topic : unsigned {
LimitOnlineVoltage,
LimitOnlineCurrent,
LimitOfflineVoltage,
LimitOfflineCurrent,
Mode
};
void onMqttMessage(Topic t,
const espMqttClientTypes::MessageProperties& properties,
const char* topic, const uint8_t* payload, size_t len,
size_t index, size_t total);
Task _loopTask;
uint32_t _lastPublishStats;
uint32_t _lastPublish;
// MQTT callbacks to process updates on subscribed topics are executed in
// the MQTT thread's context. we use this queue to switch processing the
// user requests into the main loop's context (TaskScheduler context).
mutable std::mutex _mqttMutex;
std::deque<std::function<void()>> _mqttCallbacks;
};
extern MqttHandleHuaweiClass MqttHandleHuawei;

View File

@ -1,45 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <espMqttClient.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <deque>
#include <functional>
class MqttHandlePowerLimiterClass {
public:
void init(Scheduler& scheduler);
private:
void loop();
enum class MqttPowerLimiterCommand : unsigned {
Mode,
BatterySoCStartThreshold,
BatterySoCStopThreshold,
FullSolarPassthroughSoC,
VoltageStartThreshold,
VoltageStopThreshold,
FullSolarPassThroughStartVoltage,
FullSolarPassThroughStopVoltage,
UpperPowerLimit,
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);
Task _loopTask;
uint32_t _lastPublishStats;
uint32_t _lastPublish;
// MQTT callbacks to process updates on subscribed topics are executed in
// the MQTT thread's context. we use this queue to switch processing the
// user requests into the main loop's context (TaskScheduler context).
mutable std::mutex _mqttMutex;
std::deque<std::function<void()>> _mqttCallbacks;
};
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Configuration.h"
#include <espMqttClient.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
#include <deque>
#include <functional>
class MqttHandlePowerLimiterClass {
public:
void init(Scheduler& scheduler);
private:
void loop();
enum class MqttPowerLimiterCommand : unsigned {
Mode,
BatterySoCStartThreshold,
BatterySoCStopThreshold,
FullSolarPassthroughSoC,
VoltageStartThreshold,
VoltageStopThreshold,
FullSolarPassThroughStartVoltage,
FullSolarPassThroughStopVoltage,
UpperPowerLimit,
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);
Task _loopTask;
uint32_t _lastPublishStats;
uint32_t _lastPublish;
// MQTT callbacks to process updates on subscribed topics are executed in
// the MQTT thread's context. we use this queue to switch processing the
// user requests into the main loop's context (TaskScheduler context).
mutable std::mutex _mqttMutex;
std::deque<std::function<void()>> _mqttCallbacks;
};
extern MqttHandlePowerLimiterClass MqttHandlePowerLimiter;

View File

@ -1,19 +1,19 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <AsyncJson.h>
class WebApiHuaweiClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);
void getJsonData(JsonVariant& root);
private:
void onStatus(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);
void onPost(AsyncWebServerRequest* request);
AsyncWebServer* _server;
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <AsyncJson.h>
class WebApiHuaweiClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);
void getJsonData(JsonVariant& root);
private:
void onStatus(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);
void onPost(AsyncWebServerRequest* request);
AsyncWebServer* _server;
};

View File

@ -1,29 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "ArduinoJson.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
class WebApiWsHuaweiLiveClass {
public:
WebApiWsHuaweiLiveClass();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void generateCommonJsonResponse(JsonVariant& root);
void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
AsyncWebServer* _server;
AsyncWebSocket _ws;
std::mutex _mutex;
Task _wsCleanupTask;
void wsCleanupTaskCb();
Task _sendDataTask;
void sendDataTaskCb();
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "ArduinoJson.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
class WebApiWsHuaweiLiveClass {
public:
WebApiWsHuaweiLiveClass();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void generateCommonJsonResponse(JsonVariant& root);
void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
AsyncWebServer* _server;
AsyncWebSocket _ws;
std::mutex _mutex;
Task _wsCleanupTask;
void wsCleanupTaskCb();
Task _sendDataTask;
void sendDataTaskCb();
};

View File

@ -1,32 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "ArduinoJson.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
class WebApiWsBatteryLiveClass {
public:
WebApiWsBatteryLiveClass();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void generateCommonJsonResponse(JsonVariant& root);
void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
AsyncWebServer* _server;
AsyncWebSocket _ws;
uint32_t _lastUpdateCheck = 0;
static constexpr uint16_t _responseSize = 1024 + 512;
std::mutex _mutex;
Task _wsCleanupTask;
void wsCleanupTaskCb();
Task _sendDataTask;
void sendDataTaskCb();
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "ArduinoJson.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
class WebApiWsBatteryLiveClass {
public:
WebApiWsBatteryLiveClass();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void generateCommonJsonResponse(JsonVariant& root);
void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
AsyncWebServer* _server;
AsyncWebSocket _ws;
uint32_t _lastUpdateCheck = 0;
static constexpr uint16_t _responseSize = 1024 + 512;
std::mutex _mutex;
Task _wsCleanupTask;
void wsCleanupTaskCb();
Task _sendDataTask;
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
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include <HardwareSerial.h>
#include "MessageOutput.h"
MessageOutputClass MessageOutput;
MessageOutputClass::MessageOutputClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MessageOutputClass::loop, this))
{
}
void MessageOutputClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.enable();
}
void MessageOutputClass::register_ws_output(AsyncWebSocket* output)
{
std::lock_guard<std::mutex> lock(_msgLock);
_ws = output;
}
void MessageOutputClass::serialWrite(MessageOutputClass::message_t const& m)
{
// 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
// default serial port is not ready.
if (!Serial) { return; }
size_t written = 0;
while (written < m.size()) {
written += Serial.write(m.data() + written, m.size() - written);
}
}
size_t MessageOutputClass::write(uint8_t c)
{
std::lock_guard<std::mutex> lock(_msgLock);
auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t());
auto iter = res.first;
auto& message = iter->second;
message.push_back(c);
if (c == '\n') {
serialWrite(message);
_lines.emplace(std::move(message));
_task_messages.erase(iter);
}
return 1;
}
size_t MessageOutputClass::write(const uint8_t *buffer, size_t size)
{
std::lock_guard<std::mutex> lock(_msgLock);
auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t());
auto iter = res.first;
auto& message = iter->second;
message.reserve(message.size() + size);
for (size_t idx = 0; idx < size; ++idx) {
uint8_t c = buffer[idx];
message.push_back(c);
if (c == '\n') {
serialWrite(message);
_lines.emplace(std::move(message));
message.clear();
message.reserve(size - idx - 1);
}
}
if (message.empty()) { _task_messages.erase(iter); }
return size;
}
void MessageOutputClass::loop()
{
std::lock_guard<std::mutex> lock(_msgLock);
// clean up (possibly filled) buffers of deleted tasks
auto map_iter = _task_messages.begin();
while (map_iter != _task_messages.end()) {
if (eTaskGetState(map_iter->first) == eDeleted) {
map_iter = _task_messages.erase(map_iter);
continue;
}
++map_iter;
}
if (!_ws) {
while (!_lines.empty()) {
_lines.pop(); // do not hog memory
}
return;
}
while (!_lines.empty() && _ws->availableForWriteAll()) {
_ws->textAll(std::make_shared<message_t>(std::move(_lines.front())));
_lines.pop();
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include <HardwareSerial.h>
#include "MessageOutput.h"
MessageOutputClass MessageOutput;
MessageOutputClass::MessageOutputClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MessageOutputClass::loop, this))
{
}
void MessageOutputClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.enable();
}
void MessageOutputClass::register_ws_output(AsyncWebSocket* output)
{
std::lock_guard<std::mutex> lock(_msgLock);
_ws = output;
}
void MessageOutputClass::serialWrite(MessageOutputClass::message_t const& m)
{
// 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
// default serial port is not ready.
if (!Serial) { return; }
size_t written = 0;
while (written < m.size()) {
written += Serial.write(m.data() + written, m.size() - written);
}
}
size_t MessageOutputClass::write(uint8_t c)
{
std::lock_guard<std::mutex> lock(_msgLock);
auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t());
auto iter = res.first;
auto& message = iter->second;
message.push_back(c);
if (c == '\n') {
serialWrite(message);
_lines.emplace(std::move(message));
_task_messages.erase(iter);
}
return 1;
}
size_t MessageOutputClass::write(const uint8_t *buffer, size_t size)
{
std::lock_guard<std::mutex> lock(_msgLock);
auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t());
auto iter = res.first;
auto& message = iter->second;
message.reserve(message.size() + size);
for (size_t idx = 0; idx < size; ++idx) {
uint8_t c = buffer[idx];
message.push_back(c);
if (c == '\n') {
serialWrite(message);
_lines.emplace(std::move(message));
message.clear();
message.reserve(size - idx - 1);
}
}
if (message.empty()) { _task_messages.erase(iter); }
return size;
}
void MessageOutputClass::loop()
{
std::lock_guard<std::mutex> lock(_msgLock);
// clean up (possibly filled) buffers of deleted tasks
auto map_iter = _task_messages.begin();
while (map_iter != _task_messages.end()) {
if (eTaskGetState(map_iter->first) == eDeleted) {
map_iter = _task_messages.erase(map_iter);
continue;
}
++map_iter;
}
if (!_ws) {
while (!_lines.empty()) {
_lines.pop(); // do not hog memory
}
return;
}
while (!_lines.empty() && _ws->availableForWriteAll()) {
_ws->textAll(std::make_shared<message_t>(std::move(_lines.front())));
_lines.pop();
}
}

View File

@ -1,162 +1,162 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "MqttHandleHuawei.h"
#include "MessageOutput.h"
#include "MqttSettings.h"
#include "Huawei_can.h"
// #include "Failsafe.h"
#include "WebApi_Huawei.h"
#include <ctime>
MqttHandleHuaweiClass MqttHandleHuawei;
void MqttHandleHuaweiClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MqttHandleHuaweiClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
String const& prefix = MqttSettings.getPrefix();
auto subscribe = [&prefix, this](char const* subTopic, Topic t) {
String fullTopic(prefix + "huawei/cmd/" + subTopic);
MqttSettings.subscribe(fullTopic.c_str(), 0,
std::bind(&MqttHandleHuaweiClass::onMqttMessage, this, t,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4,
std::placeholders::_5, std::placeholders::_6));
};
subscribe("limit_online_voltage", Topic::LimitOnlineVoltage);
subscribe("limit_online_current", Topic::LimitOnlineCurrent);
subscribe("limit_offline_voltage", Topic::LimitOfflineVoltage);
subscribe("limit_offline_current", Topic::LimitOfflineCurrent);
subscribe("mode", Topic::Mode);
_lastPublish = millis();
}
void MqttHandleHuaweiClass::loop()
{
const CONFIG_T& config = Configuration.get();
std::unique_lock<std::mutex> mqttLock(_mqttMutex);
if (!config.Huawei.Enabled) {
_mqttCallbacks.clear();
return;
}
for (auto& callback : _mqttCallbacks) { callback(); }
_mqttCallbacks.clear();
mqttLock.unlock();
if (!MqttSettings.getConnected() ) {
return;
}
const RectifierParameters_t *rp = HuaweiCan.get();
if ((millis() - _lastPublish) > (config.Mqtt.PublishInterval * 1000) ) {
MqttSettings.publish("huawei/data_age", String((millis() - HuaweiCan.getLastUpdate()) / 1000));
MqttSettings.publish("huawei/input_voltage", String(rp->input_voltage));
MqttSettings.publish("huawei/input_current", String(rp->input_current));
MqttSettings.publish("huawei/input_power", String(rp->input_power));
MqttSettings.publish("huawei/output_voltage", String(rp->output_voltage));
MqttSettings.publish("huawei/output_current", String(rp->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/input_temp", String(rp->input_temp));
MqttSettings.publish("huawei/output_temp", String(rp->output_temp));
MqttSettings.publish("huawei/efficiency", String(rp->efficiency));
MqttSettings.publish("huawei/mode", String(HuaweiCan.getMode()));
yield();
_lastPublish = millis();
}
}
void MqttHandleHuaweiClass::onMqttMessage(Topic t,
const espMqttClientTypes::MessageProperties& properties,
const char* topic, const uint8_t* payload, size_t len,
size_t index, size_t total)
{
std::string strValue(reinterpret_cast<const char*>(payload), len);
float payload_val = -1;
try {
payload_val = std::stof(strValue);
}
catch (std::invalid_argument const& e) {
MessageOutput.printf("Huawei MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
topic, strValue.c_str());
return;
}
std::lock_guard<std::mutex> mqttLock(_mqttMutex);
switch (t) {
case Topic::LimitOnlineVoltage:
MessageOutput.printf("Limit Voltage: %f V\r\n", payload_val);
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
&HuaweiCan, payload_val, HUAWEI_ONLINE_VOLTAGE));
break;
case Topic::LimitOfflineVoltage:
MessageOutput.printf("Offline Limit Voltage: %f V\r\n", payload_val);
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
&HuaweiCan, payload_val, HUAWEI_OFFLINE_VOLTAGE));
break;
case Topic::LimitOnlineCurrent:
MessageOutput.printf("Limit Current: %f A\r\n", payload_val);
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
&HuaweiCan, payload_val, HUAWEI_ONLINE_CURRENT));
break;
case Topic::LimitOfflineCurrent:
MessageOutput.printf("Offline Limit Current: %f A\r\n", payload_val);
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
&HuaweiCan, payload_val, HUAWEI_OFFLINE_CURRENT));
break;
case Topic::Mode:
switch (static_cast<int>(payload_val)) {
case 3:
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Full internal control");
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
&HuaweiCan, HUAWEI_MODE_AUTO_INT));
break;
case 2:
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Internal on/off control, external power limit");
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
&HuaweiCan, HUAWEI_MODE_AUTO_EXT));
break;
case 1:
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned ON");
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
&HuaweiCan, HUAWEI_MODE_ON));
break;
case 0:
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned OFF");
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
&HuaweiCan, HUAWEI_MODE_OFF));
break;
default:
MessageOutput.printf("[Huawei MQTT::] Invalid mode %.0f\r\n", payload_val);
break;
}
break;
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "MqttHandleHuawei.h"
#include "MessageOutput.h"
#include "MqttSettings.h"
#include "Huawei_can.h"
// #include "Failsafe.h"
#include "WebApi_Huawei.h"
#include <ctime>
MqttHandleHuaweiClass MqttHandleHuawei;
void MqttHandleHuaweiClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MqttHandleHuaweiClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
String const& prefix = MqttSettings.getPrefix();
auto subscribe = [&prefix, this](char const* subTopic, Topic t) {
String fullTopic(prefix + "huawei/cmd/" + subTopic);
MqttSettings.subscribe(fullTopic.c_str(), 0,
std::bind(&MqttHandleHuaweiClass::onMqttMessage, this, t,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4,
std::placeholders::_5, std::placeholders::_6));
};
subscribe("limit_online_voltage", Topic::LimitOnlineVoltage);
subscribe("limit_online_current", Topic::LimitOnlineCurrent);
subscribe("limit_offline_voltage", Topic::LimitOfflineVoltage);
subscribe("limit_offline_current", Topic::LimitOfflineCurrent);
subscribe("mode", Topic::Mode);
_lastPublish = millis();
}
void MqttHandleHuaweiClass::loop()
{
const CONFIG_T& config = Configuration.get();
std::unique_lock<std::mutex> mqttLock(_mqttMutex);
if (!config.Huawei.Enabled) {
_mqttCallbacks.clear();
return;
}
for (auto& callback : _mqttCallbacks) { callback(); }
_mqttCallbacks.clear();
mqttLock.unlock();
if (!MqttSettings.getConnected() ) {
return;
}
const RectifierParameters_t *rp = HuaweiCan.get();
if ((millis() - _lastPublish) > (config.Mqtt.PublishInterval * 1000) ) {
MqttSettings.publish("huawei/data_age", String((millis() - HuaweiCan.getLastUpdate()) / 1000));
MqttSettings.publish("huawei/input_voltage", String(rp->input_voltage));
MqttSettings.publish("huawei/input_current", String(rp->input_current));
MqttSettings.publish("huawei/input_power", String(rp->input_power));
MqttSettings.publish("huawei/output_voltage", String(rp->output_voltage));
MqttSettings.publish("huawei/output_current", String(rp->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/input_temp", String(rp->input_temp));
MqttSettings.publish("huawei/output_temp", String(rp->output_temp));
MqttSettings.publish("huawei/efficiency", String(rp->efficiency));
MqttSettings.publish("huawei/mode", String(HuaweiCan.getMode()));
yield();
_lastPublish = millis();
}
}
void MqttHandleHuaweiClass::onMqttMessage(Topic t,
const espMqttClientTypes::MessageProperties& properties,
const char* topic, const uint8_t* payload, size_t len,
size_t index, size_t total)
{
std::string strValue(reinterpret_cast<const char*>(payload), len);
float payload_val = -1;
try {
payload_val = std::stof(strValue);
}
catch (std::invalid_argument const& e) {
MessageOutput.printf("Huawei MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
topic, strValue.c_str());
return;
}
std::lock_guard<std::mutex> mqttLock(_mqttMutex);
switch (t) {
case Topic::LimitOnlineVoltage:
MessageOutput.printf("Limit Voltage: %f V\r\n", payload_val);
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
&HuaweiCan, payload_val, HUAWEI_ONLINE_VOLTAGE));
break;
case Topic::LimitOfflineVoltage:
MessageOutput.printf("Offline Limit Voltage: %f V\r\n", payload_val);
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
&HuaweiCan, payload_val, HUAWEI_OFFLINE_VOLTAGE));
break;
case Topic::LimitOnlineCurrent:
MessageOutput.printf("Limit Current: %f A\r\n", payload_val);
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
&HuaweiCan, payload_val, HUAWEI_ONLINE_CURRENT));
break;
case Topic::LimitOfflineCurrent:
MessageOutput.printf("Offline Limit Current: %f A\r\n", payload_val);
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setValue,
&HuaweiCan, payload_val, HUAWEI_OFFLINE_CURRENT));
break;
case Topic::Mode:
switch (static_cast<int>(payload_val)) {
case 3:
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Full internal control");
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
&HuaweiCan, HUAWEI_MODE_AUTO_INT));
break;
case 2:
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Internal on/off control, external power limit");
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
&HuaweiCan, HUAWEI_MODE_AUTO_EXT));
break;
case 1:
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned ON");
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
&HuaweiCan, HUAWEI_MODE_ON));
break;
case 0:
MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned OFF");
_mqttCallbacks.push_back(std::bind(&HuaweiCanClass::setMode,
&HuaweiCan, HUAWEI_MODE_OFF));
break;
default:
MessageOutput.printf("[Huawei MQTT::] Invalid mode %.0f\r\n", payload_val);
break;
}
break;
}
}

View File

@ -1,197 +1,197 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler, Malte Schmidt and others
*/
#include "MessageOutput.h"
#include "MqttSettings.h"
#include "MqttHandlePowerLimiter.h"
#include "PowerLimiter.h"
#include <ctime>
#include <string>
MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
void MqttHandlePowerLimiterClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MqttHandlePowerLimiterClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
String const& prefix = MqttSettings.getPrefix();
auto subscribe = [&prefix, this](char const* subTopic, MqttPowerLimiterCommand command) {
String fullTopic(prefix + "powerlimiter/cmd/" + subTopic);
MqttSettings.subscribe(fullTopic.c_str(), 0,
std::bind(&MqttHandlePowerLimiterClass::onMqttCmd, this, command,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4,
std::placeholders::_5, std::placeholders::_6));
};
subscribe("threshold/soc/start", MqttPowerLimiterCommand::BatterySoCStartThreshold);
subscribe("threshold/soc/stop", MqttPowerLimiterCommand::BatterySoCStopThreshold);
subscribe("threshold/soc/full_solar_passthrough", MqttPowerLimiterCommand::FullSolarPassthroughSoC);
subscribe("threshold/voltage/start", MqttPowerLimiterCommand::VoltageStartThreshold);
subscribe("threshold/voltage/stop", MqttPowerLimiterCommand::VoltageStopThreshold);
subscribe("threshold/voltage/full_solar_passthrough_start", MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage);
subscribe("threshold/voltage/full_solar_passthrough_stop", MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage);
subscribe("mode", MqttPowerLimiterCommand::Mode);
subscribe("upper_power_limit", MqttPowerLimiterCommand::UpperPowerLimit);
subscribe("target_power_consumption", MqttPowerLimiterCommand::TargetPowerConsumption);
_lastPublish = millis();
}
void MqttHandlePowerLimiterClass::loop()
{
std::unique_lock<std::mutex> mqttLock(_mqttMutex);
const CONFIG_T& config = Configuration.get();
if (!config.PowerLimiter.Enabled) {
_mqttCallbacks.clear();
return;
}
for (auto& callback : _mqttCallbacks) { callback(); }
_mqttCallbacks.clear();
mqttLock.unlock();
if (!MqttSettings.getConnected() ) { return; }
if ((millis() - _lastPublish) < (config.Mqtt.PublishInterval * 1000)) {
return;
}
_lastPublish = millis();
auto val = static_cast<unsigned>(PowerLimiter.getMode());
MqttSettings.publish("powerlimiter/status/mode", String(val));
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/inverter_update_timeouts", String(PowerLimiter.getInverterUpdateTimeouts()));
// no thresholds are relevant for setups without a battery
if (config.PowerLimiter.IsInverterSolarPowered) { return; }
MqttSettings.publish("powerlimiter/status/threshold/voltage/start", String(config.PowerLimiter.VoltageStartThreshold));
MqttSettings.publish("powerlimiter/status/threshold/voltage/stop", String(config.PowerLimiter.VoltageStopThreshold));
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_stop", String(config.PowerLimiter.FullSolarPassThroughStopVoltage));
}
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/stop", String(config.PowerLimiter.BatterySocStopThreshold));
if (config.Vedirect.Enabled) {
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)
{
CONFIG_T& config = Configuration.get();
std::string strValue(reinterpret_cast<const char*>(payload), len);
float payload_val = -1;
try {
payload_val = std::stof(strValue);
}
catch (std::invalid_argument const& e) {
MessageOutput.printf("PowerLimiter MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
topic, strValue.c_str());
return;
}
const int intValue = static_cast<int>(payload_val);
std::lock_guard<std::mutex> mqttLock(_mqttMutex);
switch (command) {
case MqttPowerLimiterCommand::Mode:
{
using Mode = PowerLimiterClass::Mode;
Mode mode = static_cast<Mode>(intValue);
if (mode == Mode::UnconditionalFullSolarPassthrough) {
MessageOutput.println("Power limiter unconditional full solar PT");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::UnconditionalFullSolarPassthrough));
} else if (mode == Mode::Disabled) {
MessageOutput.println("Power limiter disabled (override)");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::Disabled));
} else if (mode == Mode::Normal) {
MessageOutput.println("Power limiter normal operation");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::Normal));
} else {
MessageOutput.printf("PowerLimiter - unknown mode %d\r\n", intValue);
}
return;
}
case MqttPowerLimiterCommand::BatterySoCStartThreshold:
if (config.PowerLimiter.BatterySocStartThreshold == intValue) { return; }
MessageOutput.printf("Setting battery SoC start threshold to: %d %%\r\n", intValue);
config.PowerLimiter.BatterySocStartThreshold = intValue;
break;
case MqttPowerLimiterCommand::BatterySoCStopThreshold:
if (config.PowerLimiter.BatterySocStopThreshold == intValue) { return; }
MessageOutput.printf("Setting battery SoC stop threshold to: %d %%\r\n", intValue);
config.PowerLimiter.BatterySocStopThreshold = intValue;
break;
case MqttPowerLimiterCommand::FullSolarPassthroughSoC:
if (config.PowerLimiter.FullSolarPassThroughSoc == intValue) { return; }
MessageOutput.printf("Setting full solar passthrough SoC to: %d %%\r\n", intValue);
config.PowerLimiter.FullSolarPassThroughSoc = intValue;
break;
case MqttPowerLimiterCommand::VoltageStartThreshold:
if (config.PowerLimiter.VoltageStartThreshold == payload_val) { return; }
MessageOutput.printf("Setting voltage start threshold to: %.2f V\r\n", payload_val);
config.PowerLimiter.VoltageStartThreshold = payload_val;
break;
case MqttPowerLimiterCommand::VoltageStopThreshold:
if (config.PowerLimiter.VoltageStopThreshold == payload_val) { return; }
MessageOutput.printf("Setting voltage stop threshold to: %.2f V\r\n", payload_val);
config.PowerLimiter.VoltageStopThreshold = payload_val;
break;
case MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage:
if (config.PowerLimiter.FullSolarPassThroughStartVoltage == payload_val) { return; }
MessageOutput.printf("Setting full solar passthrough start voltage to: %.2f V\r\n", payload_val);
config.PowerLimiter.FullSolarPassThroughStartVoltage = payload_val;
break;
case MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage:
if (config.PowerLimiter.FullSolarPassThroughStopVoltage == payload_val) { return; }
MessageOutput.printf("Setting full solar passthrough stop voltage to: %.2f V\r\n", payload_val);
config.PowerLimiter.FullSolarPassThroughStopVoltage = payload_val;
break;
case MqttPowerLimiterCommand::UpperPowerLimit:
if (config.PowerLimiter.UpperPowerLimit == intValue) { return; }
MessageOutput.printf("Setting upper power limit to: %d W\r\n", intValue);
config.PowerLimiter.UpperPowerLimit = intValue;
break;
case MqttPowerLimiterCommand::TargetPowerConsumption:
if (config.PowerLimiter.TargetPowerConsumption == intValue) { return; }
MessageOutput.printf("Setting target power consumption to: %d W\r\n", intValue);
config.PowerLimiter.TargetPowerConsumption = intValue;
break;
}
// not reached if the value did not change
Configuration.write();
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler, Malte Schmidt and others
*/
#include "MessageOutput.h"
#include "MqttSettings.h"
#include "MqttHandlePowerLimiter.h"
#include "PowerLimiter.h"
#include <ctime>
#include <string>
MqttHandlePowerLimiterClass MqttHandlePowerLimiter;
void MqttHandlePowerLimiterClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MqttHandlePowerLimiterClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
String const& prefix = MqttSettings.getPrefix();
auto subscribe = [&prefix, this](char const* subTopic, MqttPowerLimiterCommand command) {
String fullTopic(prefix + "powerlimiter/cmd/" + subTopic);
MqttSettings.subscribe(fullTopic.c_str(), 0,
std::bind(&MqttHandlePowerLimiterClass::onMqttCmd, this, command,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4,
std::placeholders::_5, std::placeholders::_6));
};
subscribe("threshold/soc/start", MqttPowerLimiterCommand::BatterySoCStartThreshold);
subscribe("threshold/soc/stop", MqttPowerLimiterCommand::BatterySoCStopThreshold);
subscribe("threshold/soc/full_solar_passthrough", MqttPowerLimiterCommand::FullSolarPassthroughSoC);
subscribe("threshold/voltage/start", MqttPowerLimiterCommand::VoltageStartThreshold);
subscribe("threshold/voltage/stop", MqttPowerLimiterCommand::VoltageStopThreshold);
subscribe("threshold/voltage/full_solar_passthrough_start", MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage);
subscribe("threshold/voltage/full_solar_passthrough_stop", MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage);
subscribe("mode", MqttPowerLimiterCommand::Mode);
subscribe("upper_power_limit", MqttPowerLimiterCommand::UpperPowerLimit);
subscribe("target_power_consumption", MqttPowerLimiterCommand::TargetPowerConsumption);
_lastPublish = millis();
}
void MqttHandlePowerLimiterClass::loop()
{
std::unique_lock<std::mutex> mqttLock(_mqttMutex);
const CONFIG_T& config = Configuration.get();
if (!config.PowerLimiter.Enabled) {
_mqttCallbacks.clear();
return;
}
for (auto& callback : _mqttCallbacks) { callback(); }
_mqttCallbacks.clear();
mqttLock.unlock();
if (!MqttSettings.getConnected() ) { return; }
if ((millis() - _lastPublish) < (config.Mqtt.PublishInterval * 1000)) {
return;
}
_lastPublish = millis();
auto val = static_cast<unsigned>(PowerLimiter.getMode());
MqttSettings.publish("powerlimiter/status/mode", String(val));
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/inverter_update_timeouts", String(PowerLimiter.getInverterUpdateTimeouts()));
// no thresholds are relevant for setups without a battery
if (config.PowerLimiter.IsInverterSolarPowered) { return; }
MqttSettings.publish("powerlimiter/status/threshold/voltage/start", String(config.PowerLimiter.VoltageStartThreshold));
MqttSettings.publish("powerlimiter/status/threshold/voltage/stop", String(config.PowerLimiter.VoltageStopThreshold));
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_stop", String(config.PowerLimiter.FullSolarPassThroughStopVoltage));
}
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/stop", String(config.PowerLimiter.BatterySocStopThreshold));
if (config.Vedirect.Enabled) {
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)
{
CONFIG_T& config = Configuration.get();
std::string strValue(reinterpret_cast<const char*>(payload), len);
float payload_val = -1;
try {
payload_val = std::stof(strValue);
}
catch (std::invalid_argument const& e) {
MessageOutput.printf("PowerLimiter MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
topic, strValue.c_str());
return;
}
const int intValue = static_cast<int>(payload_val);
std::lock_guard<std::mutex> mqttLock(_mqttMutex);
switch (command) {
case MqttPowerLimiterCommand::Mode:
{
using Mode = PowerLimiterClass::Mode;
Mode mode = static_cast<Mode>(intValue);
if (mode == Mode::UnconditionalFullSolarPassthrough) {
MessageOutput.println("Power limiter unconditional full solar PT");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::UnconditionalFullSolarPassthrough));
} else if (mode == Mode::Disabled) {
MessageOutput.println("Power limiter disabled (override)");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::Disabled));
} else if (mode == Mode::Normal) {
MessageOutput.println("Power limiter normal operation");
_mqttCallbacks.push_back(std::bind(&PowerLimiterClass::setMode,
&PowerLimiter, Mode::Normal));
} else {
MessageOutput.printf("PowerLimiter - unknown mode %d\r\n", intValue);
}
return;
}
case MqttPowerLimiterCommand::BatterySoCStartThreshold:
if (config.PowerLimiter.BatterySocStartThreshold == intValue) { return; }
MessageOutput.printf("Setting battery SoC start threshold to: %d %%\r\n", intValue);
config.PowerLimiter.BatterySocStartThreshold = intValue;
break;
case MqttPowerLimiterCommand::BatterySoCStopThreshold:
if (config.PowerLimiter.BatterySocStopThreshold == intValue) { return; }
MessageOutput.printf("Setting battery SoC stop threshold to: %d %%\r\n", intValue);
config.PowerLimiter.BatterySocStopThreshold = intValue;
break;
case MqttPowerLimiterCommand::FullSolarPassthroughSoC:
if (config.PowerLimiter.FullSolarPassThroughSoc == intValue) { return; }
MessageOutput.printf("Setting full solar passthrough SoC to: %d %%\r\n", intValue);
config.PowerLimiter.FullSolarPassThroughSoc = intValue;
break;
case MqttPowerLimiterCommand::VoltageStartThreshold:
if (config.PowerLimiter.VoltageStartThreshold == payload_val) { return; }
MessageOutput.printf("Setting voltage start threshold to: %.2f V\r\n", payload_val);
config.PowerLimiter.VoltageStartThreshold = payload_val;
break;
case MqttPowerLimiterCommand::VoltageStopThreshold:
if (config.PowerLimiter.VoltageStopThreshold == payload_val) { return; }
MessageOutput.printf("Setting voltage stop threshold to: %.2f V\r\n", payload_val);
config.PowerLimiter.VoltageStopThreshold = payload_val;
break;
case MqttPowerLimiterCommand::FullSolarPassThroughStartVoltage:
if (config.PowerLimiter.FullSolarPassThroughStartVoltage == payload_val) { return; }
MessageOutput.printf("Setting full solar passthrough start voltage to: %.2f V\r\n", payload_val);
config.PowerLimiter.FullSolarPassThroughStartVoltage = payload_val;
break;
case MqttPowerLimiterCommand::FullSolarPassThroughStopVoltage:
if (config.PowerLimiter.FullSolarPassThroughStopVoltage == payload_val) { return; }
MessageOutput.printf("Setting full solar passthrough stop voltage to: %.2f V\r\n", payload_val);
config.PowerLimiter.FullSolarPassThroughStopVoltage = payload_val;
break;
case MqttPowerLimiterCommand::UpperPowerLimit:
if (config.PowerLimiter.UpperPowerLimit == intValue) { return; }
MessageOutput.printf("Setting upper power limit to: %d W\r\n", intValue);
config.PowerLimiter.UpperPowerLimit = intValue;
break;
case MqttPowerLimiterCommand::TargetPowerConsumption:
if (config.PowerLimiter.TargetPowerConsumption == intValue) { return; }
MessageOutput.printf("Setting target power consumption to: %d W\r\n", intValue);
config.PowerLimiter.TargetPowerConsumption = intValue;
break;
}
// not reached if the value did not change
Configuration.write();
}

View File

@ -1,259 +1,259 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_Huawei.h"
#include "Huawei_can.h"
#include "Configuration.h"
#include "MessageOutput.h"
#include "PinMapping.h"
#include "WebApi.h"
#include "WebApi_errors.h"
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiHuaweiClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
_server = &server;
_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_POST, std::bind(&WebApiHuaweiClass::onAdminPost, this, _1));
_server->on("/api/huawei/limit/config", HTTP_POST, std::bind(&WebApiHuaweiClass::onPost, this, _1));
}
void WebApiHuaweiClass::getJsonData(JsonVariant& root) {
const RectifierParameters_t * rp = HuaweiCan.get();
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
root["input_voltage"]["v"] = rp->input_voltage;
root["input_voltage"]["u"] = "V";
root["input_current"]["v"] = rp->input_current;
root["input_current"]["u"] = "A";
root["input_power"]["v"] = rp->input_power;
root["input_power"]["u"] = "W";
root["output_voltage"]["v"] = rp->output_voltage;
root["output_voltage"]["u"] = "V";
root["output_current"]["v"] = rp->output_current;
root["output_current"]["u"] = "A";
root["max_output_current"]["v"] = rp->max_output_current;
root["max_output_current"]["u"] = "A";
root["output_power"]["v"] = rp->output_power;
root["output_power"]["u"] = "W";
root["input_temp"]["v"] = rp->input_temp;
root["input_temp"]["u"] = "°C";
root["output_temp"]["v"] = rp->output_temp;
root["output_temp"]["u"] = "°C";
root["efficiency"]["v"] = rp->efficiency * 100;
root["efficiency"]["u"] = "%";
}
void WebApiHuaweiClass::onStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
getJsonData(root);
response->setLength();
request->send(response);
}
void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
float value;
uint8_t online = true;
float minimal_voltage;
auto& retMsg = response->getRoot();
if (root.containsKey("online")) {
online = root["online"].as<bool>();
if (online) {
minimal_voltage = HUAWEI_MINIMAL_ONLINE_VOLTAGE;
} else {
minimal_voltage = HUAWEI_MINIMAL_OFFLINE_VOLTAGE;
}
} else {
retMsg["message"] = "Could not read info if data should be set for online/offline operation!";
retMsg["code"] = WebApiError::LimitInvalidType;
response->setLength();
request->send(response);
return;
}
if (root.containsKey("voltage_valid")) {
if (root["voltage_valid"].as<bool>()) {
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["code"] = WebApiError::LimitInvalidLimit;
retMsg["param"]["max"] = 58;
retMsg["param"]["min"] = minimal_voltage;
response->setLength();
request->send(response);
return;
} else {
value = root["voltage"].as<float>();
if (online) {
HuaweiCan.setValue(value, HUAWEI_ONLINE_VOLTAGE);
} else {
HuaweiCan.setValue(value, HUAWEI_OFFLINE_VOLTAGE);
}
}
}
}
if (root.containsKey("current_valid")) {
if (root["current_valid"].as<bool>()) {
if (root["current"].as<float>() < 0 || root["current"].as<float>() > 60) {
retMsg["message"] = "current must be in range between 0 and 60!";
retMsg["code"] = WebApiError::LimitInvalidLimit;
retMsg["param"]["max"] = 60;
retMsg["param"]["min"] = 0;
response->setLength();
request->send(response);
return;
} else {
value = root["current"].as<float>();
if (online) {
HuaweiCan.setValue(value, HUAWEI_ONLINE_CURRENT);
} else {
HuaweiCan.setValue(value, HUAWEI_OFFLINE_CURRENT);
}
}
}
}
WebApi.writeConfig(retMsg);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
}
void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
const CONFIG_T& config = Configuration.get();
root["enabled"] = config.Huawei.Enabled;
root["verbose_logging"] = config.Huawei.VerboseLogging;
root["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency;
root["auto_power_enabled"] = config.Huawei.Auto_Power_Enabled;
root["auto_power_batterysoc_limits_enabled"] = config.Huawei.Auto_Power_BatterySoC_Limits_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["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["upper_power_limit"] = config.Huawei.Auto_Power_Upper_Power_Limit;
root["stop_batterysoc_threshold"] = config.Huawei.Auto_Power_Stop_BatterySoC_Threshold;
root["target_power_consumption"] = config.Huawei.Auto_Power_Target_Power_Consumption;
response->setLength();
request->send(response);
}
void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot();
if (!(root.containsKey("enabled")) ||
!(root.containsKey("can_controller_frequency")) ||
!(root.containsKey("auto_power_enabled")) ||
!(root.containsKey("emergency_charge_enabled")) ||
!(root.containsKey("voltage_limit")) ||
!(root.containsKey("lower_power_limit")) ||
!(root.containsKey("upper_power_limit"))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();
request->send(response);
return;
}
CONFIG_T& config = Configuration.get();
config.Huawei.Enabled = root["enabled"].as<bool>();
config.Huawei.VerboseLogging = root["verbose_logging"];
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_BatterySoC_Limits_Enabled = root["auto_power_batterysoc_limits_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_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_Upper_Power_Limit = root["upper_power_limit"].as<float>();
config.Huawei.Auto_Power_Stop_BatterySoC_Threshold = root["stop_batterysoc_threshold"];
config.Huawei.Auto_Power_Target_Power_Consumption = root["target_power_consumption"];
WebApi.writeConfig(retMsg);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
// TODO(schlimmchen): HuaweiCan has no real concept of the fact that the
// config might change. at least not regarding CAN parameters. until that
// changes, the ESP must restart for configuration changes to take effect.
yield();
delay(1000);
yield();
ESP.restart();
const PinMapping_t& pin = PinMapping.get();
// Properly turn this on
if (config.Huawei.Enabled) {
MessageOutput.println("Initialize Huawei AC charger interface... ");
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);
HuaweiCan.updateSettings(pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
MessageOutput.println("done");
} else {
MessageOutput.println("Invalid pin config");
}
}
// Properly turn this off
if (!config.Huawei.Enabled) {
HuaweiCan.setValue(0, HUAWEI_ONLINE_CURRENT);
delay(500);
HuaweiCan.setMode(HUAWEI_MODE_OFF);
return;
}
if (config.Huawei.Auto_Power_Enabled) {
HuaweiCan.setMode(HUAWEI_MODE_AUTO_INT);
return;
}
HuaweiCan.setMode(HUAWEI_MODE_AUTO_EXT);
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_Huawei.h"
#include "Huawei_can.h"
#include "Configuration.h"
#include "MessageOutput.h"
#include "PinMapping.h"
#include "WebApi.h"
#include "WebApi_errors.h"
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiHuaweiClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
_server = &server;
_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_POST, std::bind(&WebApiHuaweiClass::onAdminPost, this, _1));
_server->on("/api/huawei/limit/config", HTTP_POST, std::bind(&WebApiHuaweiClass::onPost, this, _1));
}
void WebApiHuaweiClass::getJsonData(JsonVariant& root) {
const RectifierParameters_t * rp = HuaweiCan.get();
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
root["input_voltage"]["v"] = rp->input_voltage;
root["input_voltage"]["u"] = "V";
root["input_current"]["v"] = rp->input_current;
root["input_current"]["u"] = "A";
root["input_power"]["v"] = rp->input_power;
root["input_power"]["u"] = "W";
root["output_voltage"]["v"] = rp->output_voltage;
root["output_voltage"]["u"] = "V";
root["output_current"]["v"] = rp->output_current;
root["output_current"]["u"] = "A";
root["max_output_current"]["v"] = rp->max_output_current;
root["max_output_current"]["u"] = "A";
root["output_power"]["v"] = rp->output_power;
root["output_power"]["u"] = "W";
root["input_temp"]["v"] = rp->input_temp;
root["input_temp"]["u"] = "°C";
root["output_temp"]["v"] = rp->output_temp;
root["output_temp"]["u"] = "°C";
root["efficiency"]["v"] = rp->efficiency * 100;
root["efficiency"]["u"] = "%";
}
void WebApiHuaweiClass::onStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
getJsonData(root);
response->setLength();
request->send(response);
}
void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
float value;
uint8_t online = true;
float minimal_voltage;
auto& retMsg = response->getRoot();
if (root.containsKey("online")) {
online = root["online"].as<bool>();
if (online) {
minimal_voltage = HUAWEI_MINIMAL_ONLINE_VOLTAGE;
} else {
minimal_voltage = HUAWEI_MINIMAL_OFFLINE_VOLTAGE;
}
} else {
retMsg["message"] = "Could not read info if data should be set for online/offline operation!";
retMsg["code"] = WebApiError::LimitInvalidType;
response->setLength();
request->send(response);
return;
}
if (root.containsKey("voltage_valid")) {
if (root["voltage_valid"].as<bool>()) {
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["code"] = WebApiError::LimitInvalidLimit;
retMsg["param"]["max"] = 58;
retMsg["param"]["min"] = minimal_voltage;
response->setLength();
request->send(response);
return;
} else {
value = root["voltage"].as<float>();
if (online) {
HuaweiCan.setValue(value, HUAWEI_ONLINE_VOLTAGE);
} else {
HuaweiCan.setValue(value, HUAWEI_OFFLINE_VOLTAGE);
}
}
}
}
if (root.containsKey("current_valid")) {
if (root["current_valid"].as<bool>()) {
if (root["current"].as<float>() < 0 || root["current"].as<float>() > 60) {
retMsg["message"] = "current must be in range between 0 and 60!";
retMsg["code"] = WebApiError::LimitInvalidLimit;
retMsg["param"]["max"] = 60;
retMsg["param"]["min"] = 0;
response->setLength();
request->send(response);
return;
} else {
value = root["current"].as<float>();
if (online) {
HuaweiCan.setValue(value, HUAWEI_ONLINE_CURRENT);
} else {
HuaweiCan.setValue(value, HUAWEI_OFFLINE_CURRENT);
}
}
}
}
WebApi.writeConfig(retMsg);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
}
void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
const CONFIG_T& config = Configuration.get();
root["enabled"] = config.Huawei.Enabled;
root["verbose_logging"] = config.Huawei.VerboseLogging;
root["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency;
root["auto_power_enabled"] = config.Huawei.Auto_Power_Enabled;
root["auto_power_batterysoc_limits_enabled"] = config.Huawei.Auto_Power_BatterySoC_Limits_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["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["upper_power_limit"] = config.Huawei.Auto_Power_Upper_Power_Limit;
root["stop_batterysoc_threshold"] = config.Huawei.Auto_Power_Stop_BatterySoC_Threshold;
root["target_power_consumption"] = config.Huawei.Auto_Power_Target_Power_Consumption;
response->setLength();
request->send(response);
}
void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot();
if (!(root.containsKey("enabled")) ||
!(root.containsKey("can_controller_frequency")) ||
!(root.containsKey("auto_power_enabled")) ||
!(root.containsKey("emergency_charge_enabled")) ||
!(root.containsKey("voltage_limit")) ||
!(root.containsKey("lower_power_limit")) ||
!(root.containsKey("upper_power_limit"))) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();
request->send(response);
return;
}
CONFIG_T& config = Configuration.get();
config.Huawei.Enabled = root["enabled"].as<bool>();
config.Huawei.VerboseLogging = root["verbose_logging"];
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_BatterySoC_Limits_Enabled = root["auto_power_batterysoc_limits_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_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_Upper_Power_Limit = root["upper_power_limit"].as<float>();
config.Huawei.Auto_Power_Stop_BatterySoC_Threshold = root["stop_batterysoc_threshold"];
config.Huawei.Auto_Power_Target_Power_Consumption = root["target_power_consumption"];
WebApi.writeConfig(retMsg);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
// TODO(schlimmchen): HuaweiCan has no real concept of the fact that the
// config might change. at least not regarding CAN parameters. until that
// changes, the ESP must restart for configuration changes to take effect.
yield();
delay(1000);
yield();
ESP.restart();
const PinMapping_t& pin = PinMapping.get();
// Properly turn this on
if (config.Huawei.Enabled) {
MessageOutput.println("Initialize Huawei AC charger interface... ");
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);
HuaweiCan.updateSettings(pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
MessageOutput.println("done");
} else {
MessageOutput.println("Invalid pin config");
}
}
// Properly turn this off
if (!config.Huawei.Enabled) {
HuaweiCan.setValue(0, HUAWEI_ONLINE_CURRENT);
delay(500);
HuaweiCan.setMode(HUAWEI_MODE_OFF);
return;
}
if (config.Huawei.Auto_Power_Enabled) {
HuaweiCan.setMode(HUAWEI_MODE_AUTO_INT);
return;
}
HuaweiCan.setMode(HUAWEI_MODE_AUTO_EXT);
}

View File

@ -1,144 +1,144 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_ws_Huawei.h"
#include "AsyncJson.h"
#include "Configuration.h"
#include "Huawei_can.h"
#include "MessageOutput.h"
#include "Utils.h"
#include "WebApi.h"
#include "defaults.h"
WebApiWsHuaweiLiveClass::WebApiWsHuaweiLiveClass()
: _ws("/huaweilivedata")
{
}
void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
_server = &server;
_server->on("/api/huaweilivedata/status", HTTP_GET, std::bind(&WebApiWsHuaweiLiveClass::onLivedataStatus, this, _1));
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsHuaweiLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::wsCleanupTaskCb, this));
_wsCleanupTask.setIterations(TASK_FOREVER);
_wsCleanupTask.setInterval(1 * TASK_SECOND);
_wsCleanupTask.enable();
scheduler.addTask(_sendDataTask);
_sendDataTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::sendDataTaskCb, this));
_sendDataTask.setIterations(TASK_FOREVER);
_sendDataTask.setInterval(1 * TASK_SECOND);
_sendDataTask.enable();
}
void WebApiWsHuaweiLiveClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
_ws.cleanupClients();
}
void WebApiWsHuaweiLiveClass::sendDataTaskCb()
{
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;
}
try {
std::lock_guard<std::mutex> lock(_mutex);
JsonDocument root;
JsonVariant var = root;
generateCommonJsonResponse(var);
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
String buffer;
serializeJson(root, buffer);
_ws.textAll(buffer);
}
} 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());
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what());
}
}
void WebApiWsHuaweiLiveClass::generateCommonJsonResponse(JsonVariant& root)
{
const RectifierParameters_t * rp = HuaweiCan.get();
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
root["input_voltage"]["v"] = rp->input_voltage;
root["input_voltage"]["u"] = "V";
root["input_current"]["v"] = rp->input_current;
root["input_current"]["u"] = "A";
root["input_power"]["v"] = rp->input_power;
root["input_power"]["u"] = "W";
root["output_voltage"]["v"] = rp->output_voltage;
root["output_voltage"]["u"] = "V";
root["output_current"]["v"] = rp->output_current;
root["output_current"]["u"] = "A";
root["max_output_current"]["v"] = rp->max_output_current;
root["max_output_current"]["u"] = "A";
root["output_power"]["v"] = rp->output_power;
root["output_power"]["u"] = "W";
root["input_temp"]["v"] = rp->input_temp;
root["input_temp"]["u"] = "°C";
root["output_temp"]["v"] = rp->output_temp;
root["output_temp"]["u"] = "°C";
root["efficiency"]["v"] = rp->efficiency * 100;
root["efficiency"]["u"] = "%";
}
void WebApiWsHuaweiLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{
if (type == WS_EVT_CONNECT) {
char str[64];
snprintf(str, sizeof(str), "Websocket: [%s][%u] connect", server->url(), client->id());
Serial.println(str);
MessageOutput.println(str);
} else if (type == WS_EVT_DISCONNECT) {
char str[64];
snprintf(str, sizeof(str), "Websocket: [%s][%u] disconnect", server->url(), client->id());
Serial.println(str);
MessageOutput.println(str);
}
}
void WebApiWsHuaweiLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
try {
std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
generateCommonJsonResponse(root);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
} 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());
WebApi.sendTooManyRequests(request);
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what());
WebApi.sendTooManyRequests(request);
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_ws_Huawei.h"
#include "AsyncJson.h"
#include "Configuration.h"
#include "Huawei_can.h"
#include "MessageOutput.h"
#include "Utils.h"
#include "WebApi.h"
#include "defaults.h"
WebApiWsHuaweiLiveClass::WebApiWsHuaweiLiveClass()
: _ws("/huaweilivedata")
{
}
void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
_server = &server;
_server->on("/api/huaweilivedata/status", HTTP_GET, std::bind(&WebApiWsHuaweiLiveClass::onLivedataStatus, this, _1));
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsHuaweiLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::wsCleanupTaskCb, this));
_wsCleanupTask.setIterations(TASK_FOREVER);
_wsCleanupTask.setInterval(1 * TASK_SECOND);
_wsCleanupTask.enable();
scheduler.addTask(_sendDataTask);
_sendDataTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::sendDataTaskCb, this));
_sendDataTask.setIterations(TASK_FOREVER);
_sendDataTask.setInterval(1 * TASK_SECOND);
_sendDataTask.enable();
}
void WebApiWsHuaweiLiveClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
_ws.cleanupClients();
}
void WebApiWsHuaweiLiveClass::sendDataTaskCb()
{
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;
}
try {
std::lock_guard<std::mutex> lock(_mutex);
JsonDocument root;
JsonVariant var = root;
generateCommonJsonResponse(var);
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
String buffer;
serializeJson(root, buffer);
_ws.textAll(buffer);
}
} 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());
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what());
}
}
void WebApiWsHuaweiLiveClass::generateCommonJsonResponse(JsonVariant& root)
{
const RectifierParameters_t * rp = HuaweiCan.get();
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
root["input_voltage"]["v"] = rp->input_voltage;
root["input_voltage"]["u"] = "V";
root["input_current"]["v"] = rp->input_current;
root["input_current"]["u"] = "A";
root["input_power"]["v"] = rp->input_power;
root["input_power"]["u"] = "W";
root["output_voltage"]["v"] = rp->output_voltage;
root["output_voltage"]["u"] = "V";
root["output_current"]["v"] = rp->output_current;
root["output_current"]["u"] = "A";
root["max_output_current"]["v"] = rp->max_output_current;
root["max_output_current"]["u"] = "A";
root["output_power"]["v"] = rp->output_power;
root["output_power"]["u"] = "W";
root["input_temp"]["v"] = rp->input_temp;
root["input_temp"]["u"] = "°C";
root["output_temp"]["v"] = rp->output_temp;
root["output_temp"]["u"] = "°C";
root["efficiency"]["v"] = rp->efficiency * 100;
root["efficiency"]["u"] = "%";
}
void WebApiWsHuaweiLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{
if (type == WS_EVT_CONNECT) {
char str[64];
snprintf(str, sizeof(str), "Websocket: [%s][%u] connect", server->url(), client->id());
Serial.println(str);
MessageOutput.println(str);
} else if (type == WS_EVT_DISCONNECT) {
char str[64];
snprintf(str, sizeof(str), "Websocket: [%s][%u] disconnect", server->url(), client->id());
Serial.println(str);
MessageOutput.println(str);
}
}
void WebApiWsHuaweiLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
try {
std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
generateCommonJsonResponse(root);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
} 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());
WebApi.sendTooManyRequests(request);
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what());
WebApi.sendTooManyRequests(request);
}
}

View File

@ -1,126 +1,126 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_ws_battery.h"
#include "AsyncJson.h"
#include "Configuration.h"
#include "Battery.h"
#include "MessageOutput.h"
#include "WebApi.h"
#include "defaults.h"
#include "Utils.h"
WebApiWsBatteryLiveClass::WebApiWsBatteryLiveClass()
: _ws("/batterylivedata")
{
}
void WebApiWsBatteryLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
_server = &server;
_server->on("/api/batterylivedata/status", HTTP_GET, std::bind(&WebApiWsBatteryLiveClass::onLivedataStatus, this, _1));
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsBatteryLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::wsCleanupTaskCb, this));
_wsCleanupTask.setIterations(TASK_FOREVER);
_wsCleanupTask.setInterval(1 * TASK_SECOND);
_wsCleanupTask.enable();
scheduler.addTask(_sendDataTask);
_sendDataTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::sendDataTaskCb, this));
_sendDataTask.setIterations(TASK_FOREVER);
_sendDataTask.setInterval(1 * TASK_SECOND);
_sendDataTask.enable();
}
void WebApiWsBatteryLiveClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
_ws.cleanupClients();
}
void WebApiWsBatteryLiveClass::sendDataTaskCb()
{
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;
}
if (!Battery.getStats()->updateAvailable(_lastUpdateCheck)) { return; }
_lastUpdateCheck = millis();
try {
std::lock_guard<std::mutex> lock(_mutex);
JsonDocument root;
JsonVariant var = root;
generateCommonJsonResponse(var);
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
// battery provider does not generate a card, e.g., MQTT provider
if (root.isNull()) { return; }
String buffer;
serializeJson(root, buffer);
if (Configuration.get().Security.AllowReadonly) {
_ws.setAuthentication("", "");
} else {
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
}
_ws.textAll(buffer);
}
} 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());
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what());
}
}
void WebApiWsBatteryLiveClass::generateCommonJsonResponse(JsonVariant& root)
{
Battery.getStats()->getLiveViewData(root);
}
void WebApiWsBatteryLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{
if (type == WS_EVT_CONNECT) {
MessageOutput.printf("Websocket: [%s][%u] connect\r\n", server->url(), client->id());
} else if (type == WS_EVT_DISCONNECT) {
MessageOutput.printf("Websocket: [%s][%u] disconnect\r\n", server->url(), client->id());
}
}
void WebApiWsBatteryLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
try {
std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
generateCommonJsonResponse(root);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
} 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());
WebApi.sendTooManyRequests(request);
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what());
WebApi.sendTooManyRequests(request);
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_ws_battery.h"
#include "AsyncJson.h"
#include "Configuration.h"
#include "Battery.h"
#include "MessageOutput.h"
#include "WebApi.h"
#include "defaults.h"
#include "Utils.h"
WebApiWsBatteryLiveClass::WebApiWsBatteryLiveClass()
: _ws("/batterylivedata")
{
}
void WebApiWsBatteryLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
_server = &server;
_server->on("/api/batterylivedata/status", HTTP_GET, std::bind(&WebApiWsBatteryLiveClass::onLivedataStatus, this, _1));
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsBatteryLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::wsCleanupTaskCb, this));
_wsCleanupTask.setIterations(TASK_FOREVER);
_wsCleanupTask.setInterval(1 * TASK_SECOND);
_wsCleanupTask.enable();
scheduler.addTask(_sendDataTask);
_sendDataTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::sendDataTaskCb, this));
_sendDataTask.setIterations(TASK_FOREVER);
_sendDataTask.setInterval(1 * TASK_SECOND);
_sendDataTask.enable();
}
void WebApiWsBatteryLiveClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
_ws.cleanupClients();
}
void WebApiWsBatteryLiveClass::sendDataTaskCb()
{
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;
}
if (!Battery.getStats()->updateAvailable(_lastUpdateCheck)) { return; }
_lastUpdateCheck = millis();
try {
std::lock_guard<std::mutex> lock(_mutex);
JsonDocument root;
JsonVariant var = root;
generateCommonJsonResponse(var);
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
// battery provider does not generate a card, e.g., MQTT provider
if (root.isNull()) { return; }
String buffer;
serializeJson(root, buffer);
if (Configuration.get().Security.AllowReadonly) {
_ws.setAuthentication("", "");
} else {
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
}
_ws.textAll(buffer);
}
} 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());
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what());
}
}
void WebApiWsBatteryLiveClass::generateCommonJsonResponse(JsonVariant& root)
{
Battery.getStats()->getLiveViewData(root);
}
void WebApiWsBatteryLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{
if (type == WS_EVT_CONNECT) {
MessageOutput.printf("Websocket: [%s][%u] connect\r\n", server->url(), client->id());
} else if (type == WS_EVT_DISCONNECT) {
MessageOutput.printf("Websocket: [%s][%u] disconnect\r\n", server->url(), client->id());
}
}
void WebApiWsBatteryLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
try {
std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
generateCommonJsonResponse(root);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
} 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());
WebApi.sendTooManyRequests(request);
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what());
WebApi.sendTooManyRequests(request);
}
}

View File

@ -1,203 +1,203 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "Configuration.h"
#include "Datastore.h"
#include "Display_Graphic.h"
#include "InverterSettings.h"
#include "Led_Single.h"
#include "MessageOutput.h"
#include "SerialPortManager.h"
#include "VictronMppt.h"
#include "Battery.h"
#include "Huawei_can.h"
#include "MqttHandleDtu.h"
#include "MqttHandleHass.h"
#include "MqttHandleVedirectHass.h"
#include "MqttHandleBatteryHass.h"
#include "MqttHandleInverter.h"
#include "MqttHandleInverterTotal.h"
#include "MqttHandleVedirect.h"
#include "MqttHandleHuawei.h"
#include "MqttHandlePowerLimiter.h"
#include "MqttHandlePowerLimiterHass.h"
#include "MqttSettings.h"
#include "NetworkSettings.h"
#include "NtpSettings.h"
#include "PinMapping.h"
#include "Scheduler.h"
#include "SunPosition.h"
#include "Utils.h"
#include "WebApi.h"
#include "PowerMeter.h"
#include "PowerLimiter.h"
#include "defaults.h"
#include <Arduino.h>
#include <LittleFS.h>
#include <TaskScheduler.h>
#include <esp_heap_caps.h>
void setup()
{
// Move all dynamic allocations >512byte to psram (if available)
heap_caps_malloc_extmem_enable(512);
// Initialize serial output
Serial.begin(SERIAL_BAUDRATE);
#if ARDUINO_USB_CDC_ON_BOOT
Serial.setTxTimeoutMs(0);
delay(100);
#else
while (!Serial)
yield();
#endif
MessageOutput.init(scheduler);
MessageOutput.println();
MessageOutput.println("Starting OpenDTU");
// Initialize file system
MessageOutput.print("Initialize FS... ");
if (!LittleFS.begin(false)) { // Do not format if mount failed
MessageOutput.print("failed... trying to format...");
if (!LittleFS.begin(true)) {
MessageOutput.print("success");
} else {
MessageOutput.print("failed");
}
} else {
MessageOutput.println("done");
}
// Read configuration values
MessageOutput.print("Reading configuration... ");
if (!Configuration.read()) {
MessageOutput.print("initializing... ");
Configuration.init();
if (Configuration.write()) {
MessageOutput.print("written... ");
} else {
MessageOutput.print("failed... ");
}
}
if (Configuration.get().Cfg.Version != CONFIG_VERSION) {
MessageOutput.print("migrated... ");
Configuration.migrate();
}
auto& config = Configuration.get();
MessageOutput.println("done");
// Load PinMapping
MessageOutput.print("Reading PinMapping... ");
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
MessageOutput.print("found valid mapping ");
} else {
MessageOutput.print("using default config ");
}
const auto& pin = PinMapping.get();
MessageOutput.println("done");
SerialPortManager.init();
// Initialize WiFi
MessageOutput.print("Initialize Network... ");
NetworkSettings.init(scheduler);
MessageOutput.println("done");
NetworkSettings.applyConfig();
// Initialize NTP
MessageOutput.print("Initialize NTP... ");
NtpSettings.init();
MessageOutput.println("done");
// Initialize SunPosition
MessageOutput.print("Initialize SunPosition... ");
SunPosition.init(scheduler);
MessageOutput.println("done");
// Initialize MqTT
MessageOutput.print("Initialize MqTT... ");
MqttSettings.init();
MqttHandleDtu.init(scheduler);
MqttHandleInverter.init(scheduler);
MqttHandleInverterTotal.init(scheduler);
MqttHandleVedirect.init(scheduler);
MqttHandleHass.init(scheduler);
MqttHandleVedirectHass.init(scheduler);
MqttHandleBatteryHass.init(scheduler);
MqttHandleHuawei.init(scheduler);
MqttHandlePowerLimiter.init(scheduler);
MqttHandlePowerLimiterHass.init(scheduler);
MessageOutput.println("done");
// Initialize WebApi
MessageOutput.print("Initialize WebApi... ");
WebApi.init(scheduler);
MessageOutput.println("done");
// Initialize Display
MessageOutput.print("Initialize Display... ");
Display.init(
scheduler,
static_cast<DisplayType_t>(pin.display_type),
pin.display_data,
pin.display_clk,
pin.display_cs,
pin.display_reset);
Display.setDiagramMode(static_cast<DiagramMode_t>(config.Display.Diagram.Mode));
Display.setOrientation(config.Display.Rotation);
Display.enablePowerSafe = config.Display.PowerSafe;
Display.enableScreensaver = config.Display.ScreenSaver;
Display.setContrast(config.Display.Contrast);
Display.setLanguage(config.Display.Language);
Display.setStartupDisplay();
MessageOutput.println("done");
// Initialize Single LEDs
MessageOutput.print("Initialize LEDs... ");
LedSingle.init(scheduler);
MessageOutput.println("done");
// Check for default DTU serial
MessageOutput.print("Check for default DTU serial... ");
if (config.Dtu.Serial == DTU_SERIAL) {
MessageOutput.print("generate serial based on ESP chip id: ");
const uint64_t dtuId = Utils::generateDtuSerial();
MessageOutput.printf("%0x%08x... ",
((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
((uint32_t)(dtuId & 0xFFFFFFFF)));
config.Dtu.Serial = dtuId;
Configuration.write();
}
MessageOutput.println("done");
MessageOutput.println("done");
InverterSettings.init(scheduler);
Datastore.init(scheduler);
VictronMppt.init(scheduler);
// Power meter
PowerMeter.init(scheduler);
// Dynamic power limiter
PowerLimiter.init(scheduler);
// Initialize Huawei AC-charger PSU / CAN bus
MessageOutput.println("Initialize Huawei AC charger interface... ");
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);
HuaweiCan.init(scheduler, pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
MessageOutput.println("done");
} else {
MessageOutput.println("Invalid pin config");
}
Battery.init(scheduler);
}
void loop()
{
scheduler.execute();
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "Configuration.h"
#include "Datastore.h"
#include "Display_Graphic.h"
#include "InverterSettings.h"
#include "Led_Single.h"
#include "MessageOutput.h"
#include "SerialPortManager.h"
#include "VictronMppt.h"
#include "Battery.h"
#include "Huawei_can.h"
#include "MqttHandleDtu.h"
#include "MqttHandleHass.h"
#include "MqttHandleVedirectHass.h"
#include "MqttHandleBatteryHass.h"
#include "MqttHandleInverter.h"
#include "MqttHandleInverterTotal.h"
#include "MqttHandleVedirect.h"
#include "MqttHandleHuawei.h"
#include "MqttHandlePowerLimiter.h"
#include "MqttHandlePowerLimiterHass.h"
#include "MqttSettings.h"
#include "NetworkSettings.h"
#include "NtpSettings.h"
#include "PinMapping.h"
#include "Scheduler.h"
#include "SunPosition.h"
#include "Utils.h"
#include "WebApi.h"
#include "PowerMeter.h"
#include "PowerLimiter.h"
#include "defaults.h"
#include <Arduino.h>
#include <LittleFS.h>
#include <TaskScheduler.h>
#include <esp_heap_caps.h>
void setup()
{
// Move all dynamic allocations >512byte to psram (if available)
heap_caps_malloc_extmem_enable(512);
// Initialize serial output
Serial.begin(SERIAL_BAUDRATE);
#if ARDUINO_USB_CDC_ON_BOOT
Serial.setTxTimeoutMs(0);
delay(100);
#else
while (!Serial)
yield();
#endif
MessageOutput.init(scheduler);
MessageOutput.println();
MessageOutput.println("Starting OpenDTU");
// Initialize file system
MessageOutput.print("Initialize FS... ");
if (!LittleFS.begin(false)) { // Do not format if mount failed
MessageOutput.print("failed... trying to format...");
if (!LittleFS.begin(true)) {
MessageOutput.print("success");
} else {
MessageOutput.print("failed");
}
} else {
MessageOutput.println("done");
}
// Read configuration values
MessageOutput.print("Reading configuration... ");
if (!Configuration.read()) {
MessageOutput.print("initializing... ");
Configuration.init();
if (Configuration.write()) {
MessageOutput.print("written... ");
} else {
MessageOutput.print("failed... ");
}
}
if (Configuration.get().Cfg.Version != CONFIG_VERSION) {
MessageOutput.print("migrated... ");
Configuration.migrate();
}
auto& config = Configuration.get();
MessageOutput.println("done");
// Load PinMapping
MessageOutput.print("Reading PinMapping... ");
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
MessageOutput.print("found valid mapping ");
} else {
MessageOutput.print("using default config ");
}
const auto& pin = PinMapping.get();
MessageOutput.println("done");
SerialPortManager.init();
// Initialize WiFi
MessageOutput.print("Initialize Network... ");
NetworkSettings.init(scheduler);
MessageOutput.println("done");
NetworkSettings.applyConfig();
// Initialize NTP
MessageOutput.print("Initialize NTP... ");
NtpSettings.init();
MessageOutput.println("done");
// Initialize SunPosition
MessageOutput.print("Initialize SunPosition... ");
SunPosition.init(scheduler);
MessageOutput.println("done");
// Initialize MqTT
MessageOutput.print("Initialize MqTT... ");
MqttSettings.init();
MqttHandleDtu.init(scheduler);
MqttHandleInverter.init(scheduler);
MqttHandleInverterTotal.init(scheduler);
MqttHandleVedirect.init(scheduler);
MqttHandleHass.init(scheduler);
MqttHandleVedirectHass.init(scheduler);
MqttHandleBatteryHass.init(scheduler);
MqttHandleHuawei.init(scheduler);
MqttHandlePowerLimiter.init(scheduler);
MqttHandlePowerLimiterHass.init(scheduler);
MessageOutput.println("done");
// Initialize WebApi
MessageOutput.print("Initialize WebApi... ");
WebApi.init(scheduler);
MessageOutput.println("done");
// Initialize Display
MessageOutput.print("Initialize Display... ");
Display.init(
scheduler,
static_cast<DisplayType_t>(pin.display_type),
pin.display_data,
pin.display_clk,
pin.display_cs,
pin.display_reset);
Display.setDiagramMode(static_cast<DiagramMode_t>(config.Display.Diagram.Mode));
Display.setOrientation(config.Display.Rotation);
Display.enablePowerSafe = config.Display.PowerSafe;
Display.enableScreensaver = config.Display.ScreenSaver;
Display.setContrast(config.Display.Contrast);
Display.setLanguage(config.Display.Language);
Display.setStartupDisplay();
MessageOutput.println("done");
// Initialize Single LEDs
MessageOutput.print("Initialize LEDs... ");
LedSingle.init(scheduler);
MessageOutput.println("done");
// Check for default DTU serial
MessageOutput.print("Check for default DTU serial... ");
if (config.Dtu.Serial == DTU_SERIAL) {
MessageOutput.print("generate serial based on ESP chip id: ");
const uint64_t dtuId = Utils::generateDtuSerial();
MessageOutput.printf("%0x%08x... ",
((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
((uint32_t)(dtuId & 0xFFFFFFFF)));
config.Dtu.Serial = dtuId;
Configuration.write();
}
MessageOutput.println("done");
MessageOutput.println("done");
InverterSettings.init(scheduler);
Datastore.init(scheduler);
VictronMppt.init(scheduler);
// Power meter
PowerMeter.init(scheduler);
// Dynamic power limiter
PowerLimiter.init(scheduler);
// Initialize Huawei AC-charger PSU / CAN bus
MessageOutput.println("Initialize Huawei AC charger interface... ");
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);
HuaweiCan.init(scheduler, pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power);
MessageOutput.println("done");
} else {
MessageOutput.println("Invalid pin config");
}
Battery.init(scheduler);
}
void loop()
{
scheduler.execute();
}

View File

@ -1,217 +1,217 @@
<template>
<div class="text-center" v-if="dataLoading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div v-else-if="'values' in batteryData"> <!-- suppress the card for MQTT battery provider -->
<div class="row gy-3 mt-0">
<div class="tab-content col-sm-12 col-md-12" id="v-pills-tabContent">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center" :class="{
'text-bg-danger': batteryData.data_age >= 20,
'text-bg-primary': batteryData.data_age < 20,
}">
<div class="p-1 flex-grow-1">
<div class="d-flex flex-wrap">
<div style="padding-right: 2em;">
{{ $t('battery.battery') }}: {{ batteryData.manufacturer }}
</div>
<div style="padding-right: 2em;" v-if="'fwversion' in batteryData">
{{ $t('battery.FwVersion') }}: {{ batteryData.fwversion }}
</div>
<div style="padding-right: 2em;" v-if="'hwversion' in batteryData">
{{ $t('battery.HwVersion') }}: {{ batteryData.hwversion }}
</div>
<div style="padding-right: 2em;">
{{ $t('battery.DataAge') }} {{ $t('battery.Seconds', { 'val': batteryData.data_age }) }}
</div>
</div>
</div>
</div>
<div class="card-body">
<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 class="card" :class="{ 'border-info': true }">
<div class="card-header text-bg-info">{{ $t('battery.' + section) }}</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('battery.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('battery.Value') }}</th>
<th scope="col">{{ $t('battery.Unit') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(prop, key) in values" v-bind:key="key">
<th scope="row">{{ $t('battery.' + key) }}</th>
<td style="text-align: right">
<template v-if="typeof prop === 'string'">
{{ $t('battery.' + prop) }}
</template>
<template v-else>
{{ $n(prop.v, 'decimal', {
minimumFractionDigits: prop.d,
maximumFractionDigits: prop.d})
}}
</template>
</td>
<td v-if="typeof prop === 'string'"></td>
<td v-else>{{prop.u}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col order-1">
<div class="card">
<div :class="{'card-header': true, 'border-bottom-0': maxIssueValue === 0}">
<div class="d-flex flex-row justify-content-between align-items-baseline">
{{ $t('battery.issues') }}
<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 === 2" class="badge text-bg-danger">{{ $t('battery.alarm') }}</div>
</div>
</div>
<div class="card-body" v-if="'issues' in batteryData">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('battery.issueName') }}</th>
<th scope="col">{{ $t('battery.issueType') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(prop, key) in batteryData.issues" v-bind:key="key">
<th scope="row">{{ $t('battery.' + key) }}</th>
<td>
<span class="badge" :class="{
'text-bg-warning text-dark': prop === 1,
'text-bg-danger': prop === 2
}">
<template v-if="prop === 1">{{ $t('battery.warning') }}</template>
<template v-else>{{ $t('battery.alarm') }}</template>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { Battery } from '@/types/BatteryDataStatus';
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
export default defineComponent({
components: {
},
data() {
return {
socket: {} as WebSocket,
heartInterval: 0,
dataAgeInterval: 0,
dataLoading: true,
batteryData: {} as Battery,
isFirstFetchAfterConnect: true,
alertMessageLimit: "",
alertTypeLimit: "info",
showAlertLimit: false,
checked: false,
};
},
created() {
this.getInitialData();
this.initSocket();
this.initDataAgeing();
},
unmounted() {
this.closeSocket();
},
methods: {
getInitialData() {
console.log("Get initalData for Battery");
this.dataLoading = true;
fetch("/api/batterylivedata/status", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.batteryData = data;
this.dataLoading = false;
});
},
initSocket() {
console.log("Starting connection to Battery WebSocket Server");
const { protocol, host } = location;
const authString = authUrl();
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
}://${authString}${host}/batterylivedata`;
this.socket = new WebSocket(webSocketUrl);
this.socket.onmessage = (event) => {
console.log(event);
this.batteryData = JSON.parse(event.data);
this.dataLoading = false;
this.heartCheck(); // Reset heartbeat detection
};
this.socket.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the Battery websocket server...");
};
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
window.onbeforeunload = () => {
this.closeSocket();
};
},
initDataAgeing() {
this.dataAgeInterval = setInterval(() => {
if (this.batteryData) {
this.batteryData.data_age++;
}
}, 1000);
},
// Send heartbeat packets regularly * 59s Send a heartbeat
heartCheck() {
this.heartInterval && clearTimeout(this.heartInterval);
this.heartInterval = setInterval(() => {
if (this.socket.readyState === 1) {
// Connection status
this.socket.send("ping");
} else {
this.initSocket(); // Breakpoint reconnection 5 Time
}
}, 59 * 1000);
},
/** To break off websocket Connect */
closeSocket() {
this.socket.close();
this.heartInterval && clearTimeout(this.heartInterval);
this.isFirstFetchAfterConnect = true;
}
},
computed: {
maxIssueValue() {
return ('issues' in this.batteryData)?Math.max(...Object.values(this.batteryData.issues)):0;
},
},
});
<template>
<div class="text-center" v-if="dataLoading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div v-else-if="'values' in batteryData"> <!-- suppress the card for MQTT battery provider -->
<div class="row gy-3 mt-0">
<div class="tab-content col-sm-12 col-md-12" id="v-pills-tabContent">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center" :class="{
'text-bg-danger': batteryData.data_age >= 20,
'text-bg-primary': batteryData.data_age < 20,
}">
<div class="p-1 flex-grow-1">
<div class="d-flex flex-wrap">
<div style="padding-right: 2em;">
{{ $t('battery.battery') }}: {{ batteryData.manufacturer }}
</div>
<div style="padding-right: 2em;" v-if="'fwversion' in batteryData">
{{ $t('battery.FwVersion') }}: {{ batteryData.fwversion }}
</div>
<div style="padding-right: 2em;" v-if="'hwversion' in batteryData">
{{ $t('battery.HwVersion') }}: {{ batteryData.hwversion }}
</div>
<div style="padding-right: 2em;">
{{ $t('battery.DataAge') }} {{ $t('battery.Seconds', { 'val': batteryData.data_age }) }}
</div>
</div>
</div>
</div>
<div class="card-body">
<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 class="card" :class="{ 'border-info': true }">
<div class="card-header text-bg-info">{{ $t('battery.' + section) }}</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('battery.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('battery.Value') }}</th>
<th scope="col">{{ $t('battery.Unit') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(prop, key) in values" v-bind:key="key">
<th scope="row">{{ $t('battery.' + key) }}</th>
<td style="text-align: right">
<template v-if="typeof prop === 'string'">
{{ $t('battery.' + prop) }}
</template>
<template v-else>
{{ $n(prop.v, 'decimal', {
minimumFractionDigits: prop.d,
maximumFractionDigits: prop.d})
}}
</template>
</td>
<td v-if="typeof prop === 'string'"></td>
<td v-else>{{prop.u}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col order-1">
<div class="card">
<div :class="{'card-header': true, 'border-bottom-0': maxIssueValue === 0}">
<div class="d-flex flex-row justify-content-between align-items-baseline">
{{ $t('battery.issues') }}
<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 === 2" class="badge text-bg-danger">{{ $t('battery.alarm') }}</div>
</div>
</div>
<div class="card-body" v-if="'issues' in batteryData">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('battery.issueName') }}</th>
<th scope="col">{{ $t('battery.issueType') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(prop, key) in batteryData.issues" v-bind:key="key">
<th scope="row">{{ $t('battery.' + key) }}</th>
<td>
<span class="badge" :class="{
'text-bg-warning text-dark': prop === 1,
'text-bg-danger': prop === 2
}">
<template v-if="prop === 1">{{ $t('battery.warning') }}</template>
<template v-else>{{ $t('battery.alarm') }}</template>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { Battery } from '@/types/BatteryDataStatus';
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
export default defineComponent({
components: {
},
data() {
return {
socket: {} as WebSocket,
heartInterval: 0,
dataAgeInterval: 0,
dataLoading: true,
batteryData: {} as Battery,
isFirstFetchAfterConnect: true,
alertMessageLimit: "",
alertTypeLimit: "info",
showAlertLimit: false,
checked: false,
};
},
created() {
this.getInitialData();
this.initSocket();
this.initDataAgeing();
},
unmounted() {
this.closeSocket();
},
methods: {
getInitialData() {
console.log("Get initalData for Battery");
this.dataLoading = true;
fetch("/api/batterylivedata/status", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.batteryData = data;
this.dataLoading = false;
});
},
initSocket() {
console.log("Starting connection to Battery WebSocket Server");
const { protocol, host } = location;
const authString = authUrl();
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
}://${authString}${host}/batterylivedata`;
this.socket = new WebSocket(webSocketUrl);
this.socket.onmessage = (event) => {
console.log(event);
this.batteryData = JSON.parse(event.data);
this.dataLoading = false;
this.heartCheck(); // Reset heartbeat detection
};
this.socket.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the Battery websocket server...");
};
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
window.onbeforeunload = () => {
this.closeSocket();
};
},
initDataAgeing() {
this.dataAgeInterval = setInterval(() => {
if (this.batteryData) {
this.batteryData.data_age++;
}
}, 1000);
},
// Send heartbeat packets regularly * 59s Send a heartbeat
heartCheck() {
this.heartInterval && clearTimeout(this.heartInterval);
this.heartInterval = setInterval(() => {
if (this.socket.readyState === 1) {
// Connection status
this.socket.send("ping");
} else {
this.initSocket(); // Breakpoint reconnection 5 Time
}
}, 59 * 1000);
},
/** To break off websocket Connect */
closeSocket() {
this.socket.close();
this.heartInterval && clearTimeout(this.heartInterval);
this.isFirstFetchAfterConnect = true;
}
},
computed: {
maxIssueValue() {
return ('issues' in this.batteryData)?Math.max(...Object.values(this.batteryData.issues)):0;
},
},
});
</script>

View File

@ -1,358 +1,358 @@
<template>
<div class="text-center" v-if="dataLoading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<template v-else>
<div class="row gy-3 mt-0">
<div class="tab-content col-sm-12 col-md-12" id="v-pills-tabContent">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center" :class="{
'text-bg-danger': huaweiData.data_age > 20,
'text-bg-primary': huaweiData.data_age < 19,
}">
<div class="p-1 flex-grow-1">
<div class="d-flex flex-wrap">
<div style="padding-right: 2em;">
Huawei R4850G2
</div>
<div style="padding-right: 2em;">
{{ $t('huawei.DataAge') }} {{ $t('huawei.Seconds', { 'val': huaweiData.data_age }) }}
</div>
</div>
</div>
<div class="btn-toolbar p-2" role="toolbar">
<div class="btn-group me-2" role="group">
<button :disabled="false" type="button" class="btn btn-sm btn-danger" @click="onShowLimitSettings()"
v-tooltip :title="$t('huawei.ShowSetLimit')">
<BIconSpeedometer style="font-size:24px;" />
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row flex-row flex-wrap align-items-start g-3">
<div class="col order-0">
<div class="card" :class="{ 'border-info': true }">
<div class="card-header bg-info">{{ $t('huawei.Input') }}</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('huawei.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
<th scope="col">{{ $t('huawei.Unit') }}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{ $t('huawei.input_voltage') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_voltage.v) }}</td>
<td>{{ huaweiData.input_voltage.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_current') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_current.v) }}</td>
<td>{{ huaweiData.input_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_power') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_power.v) }}</td>
<td>{{ huaweiData.input_power.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_temp') }}</th>
<td style="text-align: right">{{ Math.round(huaweiData.input_temp.v) }}</td>
<td>{{ huaweiData.input_temp.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.efficiency') }}</th>
<td style="text-align: right">{{ huaweiData.efficiency.v.toFixed(1) }}</td>
<td>{{ huaweiData.efficiency.u }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col order-1">
<div class="card" :class="{ 'border-info': false }">
<div class="card-header bg-info">{{ $t('huawei.Output') }}</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('huawei.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
<th scope="col">{{ $t('huawei.Unit') }}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{ $t('huawei.output_voltage') }}</th>
<td style="text-align: right">{{ huaweiData.output_voltage.v.toFixed(1) }}</td>
<td>{{ huaweiData.output_voltage.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_current') }}</th>
<td style="text-align: right">{{ huaweiData.output_current.v.toFixed(2) }}</td>
<td>{{ huaweiData.output_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.max_output_current') }}</th>
<td style="text-align: right">{{ huaweiData.max_output_current.v.toFixed(1) }}</td>
<td>{{ huaweiData.max_output_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_power') }}</th>
<td style="text-align: right">{{ huaweiData.output_power.v.toFixed(1) }}</td>
<td>{{ huaweiData.output_power.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_temp') }}</th>
<td style="text-align: right">{{ Math.round(huaweiData.output_temp.v) }}</td>
<td>{{ huaweiData.output_temp.u }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" id="huaweiLimitSettingView" ref="huaweiLimitSettingView" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form @submit="onSubmitLimit">
<div class="modal-header">
<h5 class="modal-title">{{ $t('huawei.LimitSettings') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<label for="inputCurrentLimit" class="col-sm-3 col-form-label">{{ $t('huawei.CurrentLimit') }} </label>
</div>
<div class="row mb-3 align-items-center">
<label for="inputVoltageTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetVoltageLimit')
}}</label>
<div class="col-sm-1">
<div class="form-switch form-check-inline">
<input class="form-check-input" type="checkbox" id="flexSwitchVoltage"
v-model="targetLimitList.voltage_valid">
</div>
</div>
<div class="col-sm-7">
<input type="number" step="0.01" name="inputVoltageTargetLimit" class="form-control" id="inputVoltageTargetLimit"
:min="targetVoltageLimitMin" :max="targetVoltageLimitMax" v-model="targetLimitList.voltage"
:disabled=!targetLimitList.voltage_valid>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-9">
<div v-if="targetLimitList.voltage < targetVoltageLimitMinOffline" class="alert alert-secondary mt-3"
role="alert" v-html="$t('huawei.LimitHint')"></div>
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="inputCurrentTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetCurrentLimit')
}}</label>
<div class="col-sm-1">
<div class="form-switch form-check-inline">
<input class="form-check-input" type="checkbox" id="flexSwitchCurrentt"
v-model="targetLimitList.current_valid">
</div>
</div>
<div class="col-sm-7">
<input type="number" step="0.1" name="inputCurrentTargetLimit" class="form-control" id="inputCurrentTargetLimit"
:min="targetCurrentLimitMin" :max="targetCurrentLimitMax" v-model="targetLimitList.current"
:disabled=!targetLimitList.current_valid>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(true)">{{ $t('huawei.SetOnline')
}}</button>
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(false)">{{
$t('huawei.SetOffline')
}}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t('huawei.Close') }}</button>
</div>
</form>
</div>
</div>
</div>
</template>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { Huawei } from '@/types/HuaweiDataStatus';
import type { HuaweiLimitConfig } from '@/types/HuaweiLimitConfig';
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
import * as bootstrap from 'bootstrap';
import {
BIconSpeedometer,
} from 'bootstrap-icons-vue';
export default defineComponent({
components: {
BIconSpeedometer
},
data() {
return {
socket: {} as WebSocket,
heartInterval: 0,
dataAgeInterval: 0,
dataLoading: true,
huaweiData: {} as Huawei,
isFirstFetchAfterConnect: true,
targetVoltageLimitMin: 42,
targetVoltageLimitMinOffline: 48,
targetVoltageLimitMax: 58,
targetCurrentLimitMin: 0,
targetCurrentLimitMax: 60,
targetLimitList: {} as HuaweiLimitConfig,
targetLimitPersistent: false,
huaweiLimitSettingView: {} as bootstrap.Modal,
alertMessageLimit: "",
alertTypeLimit: "info",
showAlertLimit: false,
checked: false,
};
},
created() {
this.getInitialData();
this.initSocket();
this.initDataAgeing();
},
unmounted() {
this.closeSocket();
},
methods: {
getInitialData() {
console.log("Get initalData for Huawei");
this.dataLoading = true;
fetch("/api/huaweilivedata/status", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.huaweiData = data;
this.dataLoading = false;
});
},
initSocket() {
console.log("Starting connection to Huawei WebSocket Server");
const { protocol, host } = location;
const authString = authUrl();
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
}://${authString}${host}/huaweilivedata`;
this.socket = new WebSocket(webSocketUrl);
this.socket.onmessage = (event) => {
console.log(event);
this.huaweiData = JSON.parse(event.data);
this.dataLoading = false;
this.heartCheck(); // Reset heartbeat detection
};
this.socket.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the Huawei websocket server...");
};
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
window.onbeforeunload = () => {
this.closeSocket();
};
},
initDataAgeing() {
this.dataAgeInterval = setInterval(() => {
if (this.huaweiData) {
this.huaweiData.data_age++;
}
}, 1000);
},
// Send heartbeat packets regularly * 59s Send a heartbeat
heartCheck() {
this.heartInterval && clearTimeout(this.heartInterval);
this.heartInterval = setInterval(() => {
if (this.socket.readyState === 1) {
// Connection status
this.socket.send("ping");
} else {
this.initSocket(); // Breakpoint reconnection 5 Time
}
}, 59 * 1000);
},
/** To break off websocket Connect */
closeSocket() {
this.socket.close();
this.heartInterval && clearTimeout(this.heartInterval);
this.isFirstFetchAfterConnect = true;
},
formatNumber(num: number) {
return new Intl.NumberFormat(
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
).format(num);
},
onHideLimitSettings() {
this.showAlertLimit = false;
},
onShowLimitSettings() {
this.huaweiLimitSettingView = new bootstrap.Modal('#huaweiLimitSettingView');
this.huaweiLimitSettingView.show();
},
onSetLimitSettings(online: boolean) {
this.targetLimitList.online = online;
},
onSubmitLimit(e: Event) {
e.preventDefault();
const formData = new FormData();
formData.append("data", JSON.stringify(this.targetLimitList));
console.log(this.targetLimitList);
fetch("/api/huawei/limit/config", {
method: "POST",
headers: authHeader(),
body: formData,
})
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then(
(response) => {
if (response.type == "success") {
this.huaweiLimitSettingView.hide();
} else {
this.alertMessageLimit = this.$t('apiresponse.' + response.code, response.param);
this.alertTypeLimit = response.type;
this.showAlertLimit = true;
}
}
)
},
},
});
<template>
<div class="text-center" v-if="dataLoading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<template v-else>
<div class="row gy-3 mt-0">
<div class="tab-content col-sm-12 col-md-12" id="v-pills-tabContent">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center" :class="{
'text-bg-danger': huaweiData.data_age > 20,
'text-bg-primary': huaweiData.data_age < 19,
}">
<div class="p-1 flex-grow-1">
<div class="d-flex flex-wrap">
<div style="padding-right: 2em;">
Huawei R4850G2
</div>
<div style="padding-right: 2em;">
{{ $t('huawei.DataAge') }} {{ $t('huawei.Seconds', { 'val': huaweiData.data_age }) }}
</div>
</div>
</div>
<div class="btn-toolbar p-2" role="toolbar">
<div class="btn-group me-2" role="group">
<button :disabled="false" type="button" class="btn btn-sm btn-danger" @click="onShowLimitSettings()"
v-tooltip :title="$t('huawei.ShowSetLimit')">
<BIconSpeedometer style="font-size:24px;" />
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row flex-row flex-wrap align-items-start g-3">
<div class="col order-0">
<div class="card" :class="{ 'border-info': true }">
<div class="card-header bg-info">{{ $t('huawei.Input') }}</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('huawei.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
<th scope="col">{{ $t('huawei.Unit') }}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{ $t('huawei.input_voltage') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_voltage.v) }}</td>
<td>{{ huaweiData.input_voltage.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_current') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_current.v) }}</td>
<td>{{ huaweiData.input_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_power') }}</th>
<td style="text-align: right">{{ formatNumber(huaweiData.input_power.v) }}</td>
<td>{{ huaweiData.input_power.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.input_temp') }}</th>
<td style="text-align: right">{{ Math.round(huaweiData.input_temp.v) }}</td>
<td>{{ huaweiData.input_temp.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.efficiency') }}</th>
<td style="text-align: right">{{ huaweiData.efficiency.v.toFixed(1) }}</td>
<td>{{ huaweiData.efficiency.u }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col order-1">
<div class="card" :class="{ 'border-info': false }">
<div class="card-header bg-info">{{ $t('huawei.Output') }}</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('huawei.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('huawei.Value') }}</th>
<th scope="col">{{ $t('huawei.Unit') }}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{ $t('huawei.output_voltage') }}</th>
<td style="text-align: right">{{ huaweiData.output_voltage.v.toFixed(1) }}</td>
<td>{{ huaweiData.output_voltage.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_current') }}</th>
<td style="text-align: right">{{ huaweiData.output_current.v.toFixed(2) }}</td>
<td>{{ huaweiData.output_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.max_output_current') }}</th>
<td style="text-align: right">{{ huaweiData.max_output_current.v.toFixed(1) }}</td>
<td>{{ huaweiData.max_output_current.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_power') }}</th>
<td style="text-align: right">{{ huaweiData.output_power.v.toFixed(1) }}</td>
<td>{{ huaweiData.output_power.u }}</td>
</tr>
<tr>
<th scope="row">{{ $t('huawei.output_temp') }}</th>
<td style="text-align: right">{{ Math.round(huaweiData.output_temp.v) }}</td>
<td>{{ huaweiData.output_temp.u }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" id="huaweiLimitSettingView" ref="huaweiLimitSettingView" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form @submit="onSubmitLimit">
<div class="modal-header">
<h5 class="modal-title">{{ $t('huawei.LimitSettings') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<label for="inputCurrentLimit" class="col-sm-3 col-form-label">{{ $t('huawei.CurrentLimit') }} </label>
</div>
<div class="row mb-3 align-items-center">
<label for="inputVoltageTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetVoltageLimit')
}}</label>
<div class="col-sm-1">
<div class="form-switch form-check-inline">
<input class="form-check-input" type="checkbox" id="flexSwitchVoltage"
v-model="targetLimitList.voltage_valid">
</div>
</div>
<div class="col-sm-7">
<input type="number" step="0.01" name="inputVoltageTargetLimit" class="form-control" id="inputVoltageTargetLimit"
:min="targetVoltageLimitMin" :max="targetVoltageLimitMax" v-model="targetLimitList.voltage"
:disabled=!targetLimitList.voltage_valid>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-9">
<div v-if="targetLimitList.voltage < targetVoltageLimitMinOffline" class="alert alert-secondary mt-3"
role="alert" v-html="$t('huawei.LimitHint')"></div>
</div>
</div>
<div class="row mb-3 align-items-center">
<label for="inputCurrentTargetLimit" class="col-sm-3 col-form-label">{{ $t('huawei.SetCurrentLimit')
}}</label>
<div class="col-sm-1">
<div class="form-switch form-check-inline">
<input class="form-check-input" type="checkbox" id="flexSwitchCurrentt"
v-model="targetLimitList.current_valid">
</div>
</div>
<div class="col-sm-7">
<input type="number" step="0.1" name="inputCurrentTargetLimit" class="form-control" id="inputCurrentTargetLimit"
:min="targetCurrentLimitMin" :max="targetCurrentLimitMax" v-model="targetLimitList.current"
:disabled=!targetLimitList.current_valid>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(true)">{{ $t('huawei.SetOnline')
}}</button>
<button type="submit" class="btn btn-danger" @click="onSetLimitSettings(false)">{{
$t('huawei.SetOffline')
}}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t('huawei.Close') }}</button>
</div>
</form>
</div>
</div>
</div>
</template>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { Huawei } from '@/types/HuaweiDataStatus';
import type { HuaweiLimitConfig } from '@/types/HuaweiLimitConfig';
import { handleResponse, authHeader, authUrl } from '@/utils/authentication';
import * as bootstrap from 'bootstrap';
import {
BIconSpeedometer,
} from 'bootstrap-icons-vue';
export default defineComponent({
components: {
BIconSpeedometer
},
data() {
return {
socket: {} as WebSocket,
heartInterval: 0,
dataAgeInterval: 0,
dataLoading: true,
huaweiData: {} as Huawei,
isFirstFetchAfterConnect: true,
targetVoltageLimitMin: 42,
targetVoltageLimitMinOffline: 48,
targetVoltageLimitMax: 58,
targetCurrentLimitMin: 0,
targetCurrentLimitMax: 60,
targetLimitList: {} as HuaweiLimitConfig,
targetLimitPersistent: false,
huaweiLimitSettingView: {} as bootstrap.Modal,
alertMessageLimit: "",
alertTypeLimit: "info",
showAlertLimit: false,
checked: false,
};
},
created() {
this.getInitialData();
this.initSocket();
this.initDataAgeing();
},
unmounted() {
this.closeSocket();
},
methods: {
getInitialData() {
console.log("Get initalData for Huawei");
this.dataLoading = true;
fetch("/api/huaweilivedata/status", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.huaweiData = data;
this.dataLoading = false;
});
},
initSocket() {
console.log("Starting connection to Huawei WebSocket Server");
const { protocol, host } = location;
const authString = authUrl();
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
}://${authString}${host}/huaweilivedata`;
this.socket = new WebSocket(webSocketUrl);
this.socket.onmessage = (event) => {
console.log(event);
this.huaweiData = JSON.parse(event.data);
this.dataLoading = false;
this.heartCheck(); // Reset heartbeat detection
};
this.socket.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the Huawei websocket server...");
};
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
window.onbeforeunload = () => {
this.closeSocket();
};
},
initDataAgeing() {
this.dataAgeInterval = setInterval(() => {
if (this.huaweiData) {
this.huaweiData.data_age++;
}
}, 1000);
},
// Send heartbeat packets regularly * 59s Send a heartbeat
heartCheck() {
this.heartInterval && clearTimeout(this.heartInterval);
this.heartInterval = setInterval(() => {
if (this.socket.readyState === 1) {
// Connection status
this.socket.send("ping");
} else {
this.initSocket(); // Breakpoint reconnection 5 Time
}
}, 59 * 1000);
},
/** To break off websocket Connect */
closeSocket() {
this.socket.close();
this.heartInterval && clearTimeout(this.heartInterval);
this.isFirstFetchAfterConnect = true;
},
formatNumber(num: number) {
return new Intl.NumberFormat(
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
).format(num);
},
onHideLimitSettings() {
this.showAlertLimit = false;
},
onShowLimitSettings() {
this.huaweiLimitSettingView = new bootstrap.Modal('#huaweiLimitSettingView');
this.huaweiLimitSettingView.show();
},
onSetLimitSettings(online: boolean) {
this.targetLimitList.online = online;
},
onSubmitLimit(e: Event) {
e.preventDefault();
const formData = new FormData();
formData.append("data", JSON.stringify(this.targetLimitList));
console.log(this.targetLimitList);
fetch("/api/huawei/limit/config", {
method: "POST",
headers: authHeader(),
body: formData,
})
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then(
(response) => {
if (response.type == "success") {
this.huaweiLimitSettingView.hide();
} else {
this.alertMessageLimit = this.$t('apiresponse.' + response.code, response.param);
this.alertTypeLimit = response.type;
this.showAlertLimit = true;
}
}
)
},
},
});
</script>

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
export interface AcChargerConfig {
enabled: boolean;
verbose_logging: boolean;
can_controller_frequency: number;
auto_power_enabled: boolean;
auto_power_batterysoc_limits_enabled: boolean;
voltage_limit: number;
enable_voltage_limit: number;
lower_power_limit: number;
upper_power_limit: number;
emergency_charge_enabled: boolean;
stop_batterysoc_threshold: number;
target_power_consumption: number;
}
export interface AcChargerConfig {
enabled: boolean;
verbose_logging: boolean;
can_controller_frequency: number;
auto_power_enabled: boolean;
auto_power_batterysoc_limits_enabled: boolean;
voltage_limit: number;
enable_voltage_limit: number;
lower_power_limit: number;
upper_power_limit: number;
emergency_charge_enabled: boolean;
stop_batterysoc_threshold: number;
target_power_consumption: number;
}

View File

@ -1,12 +1,12 @@
import type { ValueObject } from '@/types/LiveDataStatus';
type BatteryData = (ValueObject | string)[];
export interface Battery {
manufacturer: string;
fwversion: string;
hwversion: string;
data_age: number;
values: BatteryData[];
issues: number[];
import type { ValueObject } from '@/types/LiveDataStatus';
type BatteryData = (ValueObject | string)[];
export interface Battery {
manufacturer: string;
fwversion: string;
hwversion: string;
data_age: number;
values: BatteryData[];
issues: number[];
}

View File

@ -1,18 +1,18 @@
import type { ValueObject } from '@/types/LiveDataStatus';
// Huawei
export interface Huawei {
data_age: 0;
input_voltage: ValueObject;
input_frequency: ValueObject;
input_current: ValueObject;
input_power: ValueObject;
input_temp: ValueObject;
efficiency: ValueObject;
output_voltage: ValueObject;
output_current: ValueObject;
max_output_current: ValueObject;
output_power: ValueObject;
output_temp: ValueObject;
amp_hour: ValueObject;
import type { ValueObject } from '@/types/LiveDataStatus';
// Huawei
export interface Huawei {
data_age: 0;
input_voltage: ValueObject;
input_frequency: ValueObject;
input_current: ValueObject;
input_power: ValueObject;
input_temp: ValueObject;
efficiency: ValueObject;
output_voltage: ValueObject;
output_current: ValueObject;
max_output_current: ValueObject;
output_power: ValueObject;
output_temp: ValueObject;
amp_hour: ValueObject;
}

View File

@ -1,7 +1,7 @@
export interface HuaweiLimitConfig {
voltage: number;
voltage_valid: boolean;
current: number;
current_valid: boolean;
online: boolean;
export interface HuaweiLimitConfig {
voltage: number;
voltage_valid: boolean;
current: number;
current_valid: boolean;
online: boolean;
}