Merge remote-tracking branch 'tbnobody/OpenDTU/master'
This commit is contained in:
commit
e49bbe0faf
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,4 +4,5 @@
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
.vscode/settings.json
|
||||
platformio-device-monitor*.log
|
||||
platformio-device-monitor*.log
|
||||
platformio_override.ini
|
||||
|
||||
22
README.md
22
README.md
@ -59,9 +59,15 @@ Sends text raw data as difined in VE.Direct spec.
|
||||
* Hoymiles HM-1000
|
||||
* Hoymiles HM-1200
|
||||
* Hoymiles HM-1500
|
||||
* TSUN TSOL-M350 (Maybe depending on firmware on the inverter)
|
||||
* TSUN TSOL-M800 (Maybe depending on firmware on the inverter)
|
||||
* TSUN TSOL-M1600 (Maybe depending on firmware on the inverter)
|
||||
* TSUN TSOL-M350 (Maybe depending on firmware/serial number on the inverter)
|
||||
* TSUN TSOL-M800 (Maybe depending on firmware/serial number on the inverter)
|
||||
* TSUN TSOL-M1600 (Maybe depending on firmware/serial number on the inverter)
|
||||
|
||||
**TSUN compatibility remark:**
|
||||
Compatibility with OpenDTU seems to be related to serial numbers. Current findings indicate that TSUN inverters with a serial number starting with "11" are supported, whereby inverters with a serial number starting with "10" are not.
|
||||
Firmware version seems to play not a significant role and cannot be read from the stickers. For completeness, the following firmware version have been reported to work with OpenDTU:
|
||||
* v1.0.10 TSOL-M800 (DE)
|
||||
* v1.0.12 TSOL-M1600
|
||||
|
||||
## Features for end users
|
||||
* Read live data from inverter
|
||||
@ -80,6 +86,7 @@ Sends text raw data as difined in VE.Direct spec.
|
||||
* Ve.Direct interface (via web-interface, REST-api, or MQTT)
|
||||
* Ethernet support
|
||||
* Prometheus API endpoint (/api/prometheus/metrics)
|
||||
* English and german web interface
|
||||
|
||||
## Features for developers
|
||||
* The microcontroller part
|
||||
@ -132,7 +139,7 @@ Use a power suppy with 5 V and 1 A. The USB cable connected to your PC/Notebook
|
||||
|
||||
### Change pin assignment
|
||||
Its possible to change all the pins of the NRF24L01+ module.
|
||||
This can be achieved by editing the 'platformio.ini' file and add/change one or more of the following lines to the 'build_flags' parameter:
|
||||
This can be achieved by copying one of the [env:....] sections from 'platformio.ini' to 'platformio_override.ini' and editing the 'platformio_override.ini' file and add/change one or more of the following lines to the 'build_flags' parameter:
|
||||
```
|
||||
-DHOYMILES_PIN_MISO=19
|
||||
-DHOYMILES_PIN_MOSI=23
|
||||
@ -143,6 +150,7 @@ This can be achieved by editing the 'platformio.ini' file and add/change one or
|
||||
-DVICTRON_PIN_TX=21
|
||||
-DVICTRON_PIN_RX=22
|
||||
```
|
||||
It is recommended to make all changes only in the 'platformio_override.ini', this is your personal copy.
|
||||
|
||||
## Flashing and starting up
|
||||
### with Visual Studio Code
|
||||
@ -150,8 +158,8 @@ This can be achieved by editing the 'platformio.ini' file and add/change one or
|
||||
* In Visual Studio Code, install the [PlatformIO Extension](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide)
|
||||
* Install git and enable git in vscode - [git download](https://git-scm.com/downloads/) - [Instructions](https://www.jcchouinard.com/install-git-in-vscode/)
|
||||
* Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur): Inside vscode open the command palette by pressing `CTRL` + `SHIFT` + `P`. Enter `git clone`, add the repository-URL `https://github.com/tbnobody/OpenDTU`. Next you have to choose (or create) a target directory.
|
||||
* In vscode, choose File --> Open Folder and select the previously downloaded source code. (You have to select the folder which contains the "platformio.ini" file)
|
||||
* Adjust the COM port in the file "platformio.ini" for your USB-serial-converter. It occurs twice:
|
||||
* In vscode, choose File --> Open Folder and select the previously downloaded source code. (You have to select the folder which contains the "platformio.ini" and "platformio_override.ini" file)
|
||||
* Adjust the COM port in the file "platformio_override.ini" for your USB-to-serial-converter. It occurs twice:
|
||||
* upload_port
|
||||
* monitor_port
|
||||
* Select the arrow button in the blue bottom status bar (PlatformIO: Upload) to compile and upload the firmware. During the compilation, all required libraries are downloaded automatically.
|
||||
@ -162,7 +170,7 @@ This can be achieved by editing the 'platformio.ini' file and add/change one or
|
||||
### on the commandline with PlatformIO Core
|
||||
* Install [PlatformIO Core](https://platformio.org/install/cli)
|
||||
* Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur)
|
||||
* Adjust the COM port in the file "platformio.ini". It occurs twice:
|
||||
* Adjust the COM port in the file "platformio_override.ini". It occurs twice:
|
||||
* upload_port
|
||||
* monitor_port
|
||||
* build: `platformio run -e generic`
|
||||
|
||||
@ -67,7 +67,7 @@ cmd topics are used to set values. Status topics are updated from values set in
|
||||
| [serial]/status/limit_absolute | R | Current applied production limit of the inverter | Watt (W) |
|
||||
| [serial]/cmd/limit_persistent_relative | W | Set the inverter limit as a percentage of total production capability. The value will survive the night without power. The updated value will show up in the web GUI and limit_relative topic immediatly. | % |
|
||||
| [serial]/cmd/limit_persistent_absolute | W | Set the inverter limit as a absolute value. The value will survive the night without power. The updated value will set immediatly within the inverter but show up in the web GUI and limit_relative topic after around 4 minutes. If you are using a already known inverter (known Hardware ID), the updated value will show up within a few seconds. | Watt (W) |
|
||||
| [serial]/cmd/limit_nonpersistent_relative | W | Set the inverter limit as a percentage of total production capability. The value will reset to the last persistent value at night without power. The updated value will show up in the web GUI and limit_relative topic immediatly. | % |
|
||||
| [serial]/cmd/limit_nonpersistent_absolute | W | Set the inverter limit as a absolute value. The value will reset to the last persistent value at night without power. The updated value will set immediatly within the inverter but show up in the web GUI and limit_relative topic after around 4 minutes. If you are using a already known inverter (known Hardware ID), the updated value will show up within a few seconds. | Watt (W) |
|
||||
| [serial]/cmd/limit_nonpersistent_relative | W | Set the inverter limit as a percentage of total production capability. The value will reset to the last persistent value at night without power. The updated value will show up in the web GUI and limit_relative topic immediatly. The value must be published non-retained, otherwise it will be ignored! | % |
|
||||
| [serial]/cmd/limit_nonpersistent_absolute | W | Set the inverter limit as a absolute value. The value will reset to the last persistent value at night without power. The updated value will set immediatly within the inverter but show up in the web GUI and limit_relative topic after around 4 minutes. If you are using a already known inverter (known Hardware ID), the updated value will show up within a few seconds. The value must be published non-retained, otherwise it will be ignored! | Watt (W) |
|
||||
| [serial]/cmd/power | W | Turn the inverter on (1) or off (0) | 0 or 1 |
|
||||
| [serial]/cmd/restart | W | Restarts the inverters (also resets YieldDay) | 1 |
|
||||
|
||||
27
include/MessageOutput.h
Normal file
27
include/MessageOutput.h
Normal file
@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <AsyncWebSocket.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <Stream.h>
|
||||
|
||||
#define BUFFER_SIZE 500
|
||||
|
||||
class MessageOutputClass : public Print {
|
||||
public:
|
||||
MessageOutputClass();
|
||||
void loop();
|
||||
size_t write(uint8_t c);
|
||||
void register_ws_output(AsyncWebSocket* output);
|
||||
|
||||
private:
|
||||
AsyncWebSocket* _ws = NULL;
|
||||
char _buffer[BUFFER_SIZE];
|
||||
uint16_t _buff_pos = 0;
|
||||
uint32_t _lastSend = 0;
|
||||
bool _forceSend = false;
|
||||
|
||||
SemaphoreHandle_t _lock;
|
||||
};
|
||||
|
||||
extern MessageOutputClass MessageOutput;
|
||||
15
include/MqttHandleDtu.h
Normal file
15
include/MqttHandleDtu.h
Normal file
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class MqttHandleDtuClass {
|
||||
public:
|
||||
void init();
|
||||
void loop();
|
||||
|
||||
private:
|
||||
uint32_t _lastPublish;
|
||||
};
|
||||
|
||||
extern MqttHandleDtuClass MqttHandleDtu;
|
||||
@ -1,11 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "Configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <Hoymiles.h>
|
||||
#include <memory>
|
||||
|
||||
// mqtt discovery device classes
|
||||
enum {
|
||||
@ -51,7 +48,7 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
|
||||
};
|
||||
#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass_t))
|
||||
|
||||
class MqttHassPublishingClass {
|
||||
class MqttHandleHassClass {
|
||||
public:
|
||||
void init();
|
||||
void loop();
|
||||
@ -59,6 +56,7 @@ public:
|
||||
void forceUpdate();
|
||||
|
||||
private:
|
||||
void publish(const String& subtopic, const String& payload);
|
||||
void publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear = false);
|
||||
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
|
||||
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, int16_t min = 1, int16_t max = 100);
|
||||
@ -69,4 +67,4 @@ private:
|
||||
bool _updateForced = false;
|
||||
};
|
||||
|
||||
extern MqttHassPublishingClass MqttHassPublishing;
|
||||
extern MqttHandleHassClass MqttHandleHass;
|
||||
@ -2,11 +2,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "Configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <Hoymiles.h>
|
||||
#include <memory>
|
||||
#include <espMqttClient.h>
|
||||
|
||||
class MqttPublishingClass {
|
||||
class MqttHandleInverterClass {
|
||||
public:
|
||||
void init();
|
||||
void loop();
|
||||
@ -15,6 +14,7 @@ public:
|
||||
|
||||
private:
|
||||
void publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId);
|
||||
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
|
||||
|
||||
uint32_t _lastPublishStats[INV_MAX_COUNT];
|
||||
uint32_t _lastPublish;
|
||||
@ -37,4 +37,4 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
extern MqttPublishingClass MqttPublishing;
|
||||
extern MqttHandleInverterClass MqttHandleInverter;
|
||||
@ -14,7 +14,7 @@
|
||||
#define VICTRON_PIN_TX 21
|
||||
#endif
|
||||
|
||||
class MqttVedirectPublishingClass {
|
||||
class MqttHandleVedirectClass {
|
||||
public:
|
||||
void init();
|
||||
void loop();
|
||||
@ -23,4 +23,4 @@ private:
|
||||
uint32_t _lastPublish;
|
||||
};
|
||||
|
||||
extern MqttVedirectPublishingClass MqttVedirectPublishing;
|
||||
extern MqttHandleVedirectClass MqttHandleVedirect;
|
||||
@ -2,10 +2,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "NetworkSettings.h"
|
||||
#include <Arduino.h>
|
||||
#include <MqttSubscribeParser.h>
|
||||
#include <Ticker.h>
|
||||
#include <espMqttClient.h>
|
||||
#include <memory>
|
||||
|
||||
class MqttSettingsClass {
|
||||
public:
|
||||
@ -14,7 +13,10 @@ public:
|
||||
void performReconnect();
|
||||
bool getConnected();
|
||||
void publish(const String& subtopic, const String& payload);
|
||||
void publishHass(const String& subtopic, const String& payload);
|
||||
void publishGeneric(const String& topic, const String& payload, bool retain, uint8_t qos = 0);
|
||||
|
||||
void subscribe(const String& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb);
|
||||
void unsubscribe(const String& topic);
|
||||
|
||||
String getPrefix();
|
||||
|
||||
@ -34,6 +36,7 @@ private:
|
||||
String clientId;
|
||||
String willTopic;
|
||||
Ticker mqttReconnectTimer;
|
||||
MqttSubscribeParser _mqttSubscribeParser;
|
||||
};
|
||||
|
||||
extern MqttSettingsClass MqttSettings;
|
||||
@ -3,7 +3,6 @@
|
||||
|
||||
#include <DNSServer.h>
|
||||
#include <WiFi.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
enum class network_mode {
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class NtpSettingsClass {
|
||||
public:
|
||||
NtpSettingsClass();
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "WebApi_security.h"
|
||||
#include "WebApi_sysstatus.h"
|
||||
#include "WebApi_webapp.h"
|
||||
#include "WebApi_ws_console.h"
|
||||
#include "WebApi_ws_live.h"
|
||||
#include "WebApi_ws_vedirect_live.h"
|
||||
#include "WebApi_vedirect.h"
|
||||
@ -51,6 +52,7 @@ private:
|
||||
WebApiSecurityClass _webApiSecurity;
|
||||
WebApiSysstatusClass _webApiSysstatus;
|
||||
WebApiWebappClass _webApiWebapp;
|
||||
WebApiWsConsoleClass _webApiWsConsole;
|
||||
WebApiWsLiveClass _webApiWsLive;
|
||||
WebApiWsVedirectLiveClass _webApiWsVedirectLive;
|
||||
WebApiVedirectClass _webApiVedirect;
|
||||
|
||||
84
include/WebApi_errors.h
Normal file
84
include/WebApi_errors.h
Normal file
@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
enum WebApiError {
|
||||
GenericBase = 1000,
|
||||
GenericSuccess,
|
||||
GenericNoValueFound,
|
||||
GenericDataTooLarge,
|
||||
GenericParseError,
|
||||
GenericValueMissing,
|
||||
|
||||
DtuBase = 2000,
|
||||
DtuSerialZero,
|
||||
DtuPollZero,
|
||||
DtuInvalidPowerLevel,
|
||||
|
||||
ConfigBase = 3000,
|
||||
ConfigNotDeleted,
|
||||
ConfigSuccess,
|
||||
|
||||
InverterBase = 4000,
|
||||
InverterSerialZero,
|
||||
InverterNameLength,
|
||||
InverterCount,
|
||||
InverterAdded,
|
||||
InverterInvalidId,
|
||||
InverterInvalidMaxChannel,
|
||||
InverterChanged,
|
||||
InverterDeleted,
|
||||
|
||||
LimitBase = 5000,
|
||||
LimitSerialZero,
|
||||
LimitInvalidLimit,
|
||||
LimitInvalidType,
|
||||
LimitInvalidInverter,
|
||||
|
||||
MaintenanceBase = 6000,
|
||||
MaintenanceRebootTriggered,
|
||||
MaintenanceRebootCancled,
|
||||
|
||||
MqttBase = 7000,
|
||||
MqttHostnameLength,
|
||||
MqttUsernameLength,
|
||||
MqttPasswordLength,
|
||||
MqttTopicLength,
|
||||
MqttTopicCharacter,
|
||||
MqttTopicTrailingSlash,
|
||||
MqttPort,
|
||||
MqttCertificateLength,
|
||||
MqttLwtTopicLength,
|
||||
MqttLwtTopicCharacter,
|
||||
MqttLwtOnlineLength,
|
||||
MqttLwtOfflineLength,
|
||||
MqttPublishInterval,
|
||||
MqttHassTopicLength,
|
||||
MqttHassTopicCharacter,
|
||||
|
||||
NetworkBase = 8000,
|
||||
NetworkIpInvalid,
|
||||
NetworkNetmaskInvalid,
|
||||
NetworkGatewayInvalid,
|
||||
NetworkDns1Invalid,
|
||||
NetworkDns2Invalid,
|
||||
|
||||
NtpBase = 9000,
|
||||
NtpServerLength,
|
||||
NtpTimezoneLength,
|
||||
NtpTimezoneDescriptionLength,
|
||||
NtpYearInvalid,
|
||||
NtpMonthInvalid,
|
||||
NtpDayInvalid,
|
||||
NtpHourInvalid,
|
||||
NtpMinuteInvalid,
|
||||
NtpSecondInvalid,
|
||||
NtpTimeUpdated,
|
||||
|
||||
SecurityBase = 10000,
|
||||
SecurityPasswordLength,
|
||||
SecurityAuthSuccess,
|
||||
|
||||
PowerBase = 11000,
|
||||
PowerSerialZero,
|
||||
PowerInvalidInverter,
|
||||
};
|
||||
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "Hoymiles.h"
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <Hoymiles.h>
|
||||
|
||||
class WebApiPrometheusClass {
|
||||
public:
|
||||
|
||||
19
include/WebApi_ws_console.h
Normal file
19
include/WebApi_ws_console.h
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
class WebApiWsConsoleClass {
|
||||
public:
|
||||
WebApiWsConsoleClass();
|
||||
void init(AsyncWebServer* server);
|
||||
void loop();
|
||||
|
||||
private:
|
||||
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||
|
||||
AsyncWebServer* _server;
|
||||
AsyncWebSocket _ws;
|
||||
|
||||
uint32_t _lastWsCleanup = 0;
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "ArduinoJson.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <Hoymiles.h>
|
||||
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "Hoymiles.h"
|
||||
#include "inverters/HM_1CH.h"
|
||||
#include "inverters/HM_2CH.h"
|
||||
@ -31,8 +35,8 @@ void HoymilesClass::loop()
|
||||
if (_radio->isIdle()) {
|
||||
std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos);
|
||||
if (iv != nullptr) {
|
||||
Serial.print(F("Fetch inverter: "));
|
||||
Serial.println(iv->serial(), HEX);
|
||||
_messageOutput->print(F("Fetch inverter: "));
|
||||
_messageOutput->println(iv->serial(), HEX);
|
||||
|
||||
iv->sendStatsRequest(_radio.get());
|
||||
|
||||
@ -44,25 +48,25 @@ void HoymilesClass::loop()
|
||||
if ((iv->SystemConfigPara()->getLastLimitRequestSuccess() == CMD_NOK)
|
||||
|| ((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL)
|
||||
&& (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) {
|
||||
Serial.println("Request SystemConfigPara");
|
||||
_messageOutput->println("Request SystemConfigPara");
|
||||
iv->sendSystemConfigParaRequest(_radio.get());
|
||||
}
|
||||
|
||||
// Set limit if required
|
||||
if (iv->SystemConfigPara()->getLastLimitCommandSuccess() == CMD_NOK) {
|
||||
Serial.println(F("Resend ActivePowerControl"));
|
||||
_messageOutput->println(F("Resend ActivePowerControl"));
|
||||
iv->resendActivePowerControlRequest(_radio.get());
|
||||
}
|
||||
|
||||
// Set power status if required
|
||||
if (iv->PowerCommand()->getLastPowerCommandSuccess() == CMD_NOK) {
|
||||
Serial.println(F("Resend PowerCommand"));
|
||||
_messageOutput->println(F("Resend PowerCommand"));
|
||||
iv->resendPowerControlRequest(_radio.get());
|
||||
}
|
||||
|
||||
// Fetch dev info (but first fetch stats)
|
||||
if (iv->Statistics()->getLastUpdate() > 0 && (iv->DevInfo()->getLastUpdateAll() == 0 || iv->DevInfo()->getLastUpdateSimple() == 0)) {
|
||||
Serial.println(F("Request device info"));
|
||||
_messageOutput->println(F("Request device info"));
|
||||
iv->sendDevInfoRequest(_radio.get());
|
||||
}
|
||||
}
|
||||
@ -171,4 +175,14 @@ uint32_t HoymilesClass::PollInterval()
|
||||
void HoymilesClass::setPollInterval(uint32_t interval)
|
||||
{
|
||||
_pollInterval = interval;
|
||||
}
|
||||
|
||||
void HoymilesClass::setMessageOutput(Print* output)
|
||||
{
|
||||
_messageOutput = output;
|
||||
}
|
||||
|
||||
Print* HoymilesClass::getMessageOutput()
|
||||
{
|
||||
return _messageOutput;
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "HoymilesRadio.h"
|
||||
#include "inverters/InverterAbstract.h"
|
||||
#include "types.h"
|
||||
#include <Print.h>
|
||||
#include <SPI.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -15,6 +17,9 @@ public:
|
||||
void init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ);
|
||||
void loop();
|
||||
|
||||
void setMessageOutput(Print* output);
|
||||
Print* getMessageOutput();
|
||||
|
||||
std::shared_ptr<InverterAbstract> addInverter(const char* name, uint64_t serial);
|
||||
std::shared_ptr<InverterAbstract> getInverterByPos(uint8_t pos);
|
||||
std::shared_ptr<InverterAbstract> getInverterBySerial(uint64_t serial);
|
||||
@ -35,6 +40,8 @@ private:
|
||||
|
||||
uint32_t _pollInterval = 0;
|
||||
uint32_t _lastPoll = 0;
|
||||
|
||||
Print* _messageOutput = &Serial;
|
||||
};
|
||||
|
||||
extern HoymilesClass Hoymiles;
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "HoymilesRadio.h"
|
||||
#include "Hoymiles.h"
|
||||
#include "commands/RequestFrameCommand.h"
|
||||
@ -21,9 +25,9 @@ void HoymilesRadio::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pin
|
||||
_radio->setRetries(0, 0);
|
||||
_radio->maskIRQ(true, true, false); // enable only receiving interrupts
|
||||
if (_radio->isChipConnected()) {
|
||||
Serial.println(F("Connection successfull"));
|
||||
Hoymiles.getMessageOutput()->println(F("Connection successfull"));
|
||||
} else {
|
||||
Serial.println(F("Connection error!!"));
|
||||
Hoymiles.getMessageOutput()->println(F("Connection error!!"));
|
||||
}
|
||||
|
||||
attachInterrupt(digitalPinToInterrupt(pinIRQ), std::bind(&HoymilesRadio::handleIntr, this), FALLING);
|
||||
@ -40,7 +44,7 @@ void HoymilesRadio::loop()
|
||||
}
|
||||
|
||||
if (_packetReceived) {
|
||||
Serial.println(F("Interrupt received"));
|
||||
Hoymiles.getMessageOutput()->println(F("Interrupt received"));
|
||||
while (_radio->available()) {
|
||||
if (!(_rxBuffer.size() > FRAGMENT_BUFFER_SIZE)) {
|
||||
fragment_t f;
|
||||
@ -52,7 +56,7 @@ void HoymilesRadio::loop()
|
||||
_radio->read(f.fragment, f.len);
|
||||
_rxBuffer.push(f);
|
||||
} else {
|
||||
Serial.println(F("Buffer full"));
|
||||
Hoymiles.getMessageOutput()->println(F("Buffer full"));
|
||||
_radio->flush_rx();
|
||||
}
|
||||
}
|
||||
@ -72,11 +76,11 @@ void HoymilesRadio::loop()
|
||||
dumpBuf(buf, f.fragment, f.len);
|
||||
inv->addRxFragment(f.fragment, f.len);
|
||||
} else {
|
||||
Serial.println(F("Inverter Not found!"));
|
||||
Hoymiles.getMessageOutput()->println(F("Inverter Not found!"));
|
||||
}
|
||||
|
||||
} else {
|
||||
Serial.println(F("Frame kaputt"));
|
||||
Hoymiles.getMessageOutput()->println(F("Frame kaputt"));
|
||||
}
|
||||
|
||||
// Remove paket from buffer even it was corrupted
|
||||
@ -85,46 +89,46 @@ void HoymilesRadio::loop()
|
||||
}
|
||||
|
||||
if (_busyFlag && _rxTimeout.occured()) {
|
||||
Serial.println(F("RX Period End"));
|
||||
Hoymiles.getMessageOutput()->println(F("RX Period End"));
|
||||
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress());
|
||||
|
||||
if (nullptr != inv) {
|
||||
CommandAbstract* cmd = _commandQueue.front().get();
|
||||
uint8_t verifyResult = inv->verifyAllFragments(cmd);
|
||||
if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) {
|
||||
Serial.println(F("Nothing received, resend whole request"));
|
||||
Hoymiles.getMessageOutput()->println(F("Nothing received, resend whole request"));
|
||||
sendLastPacketAgain();
|
||||
|
||||
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
|
||||
Serial.println(F("Nothing received, resend count exeeded"));
|
||||
Hoymiles.getMessageOutput()->println(F("Nothing received, resend count exeeded"));
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
|
||||
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
|
||||
Serial.println(F("Retransmit timeout"));
|
||||
Hoymiles.getMessageOutput()->println(F("Retransmit timeout"));
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
|
||||
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
|
||||
Serial.println(F("Packet handling error"));
|
||||
Hoymiles.getMessageOutput()->println(F("Packet handling error"));
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
|
||||
} else if (verifyResult > 0) {
|
||||
// Perform Retransmit
|
||||
Serial.print(F("Request retransmit: "));
|
||||
Serial.println(verifyResult);
|
||||
Hoymiles.getMessageOutput()->print(F("Request retransmit: "));
|
||||
Hoymiles.getMessageOutput()->println(verifyResult);
|
||||
sendRetransmitPacket(verifyResult);
|
||||
|
||||
} else {
|
||||
// Successfull received all packages
|
||||
Serial.println(F("Success"));
|
||||
Hoymiles.getMessageOutput()->println(F("Success"));
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
}
|
||||
} else {
|
||||
// If inverter was not found, assume the command is invalid
|
||||
Serial.println(F("RX: Invalid inverter found"));
|
||||
Hoymiles.getMessageOutput()->println(F("RX: Invalid inverter found"));
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
}
|
||||
@ -138,7 +142,7 @@ void HoymilesRadio::loop()
|
||||
inv->clearRxFragmentBuffer();
|
||||
sendEsbPacket(cmd);
|
||||
} else {
|
||||
Serial.println(F("TX: Invalid inverter found"));
|
||||
Hoymiles.getMessageOutput()->println(F("TX: Invalid inverter found"));
|
||||
_commandQueue.pop();
|
||||
}
|
||||
}
|
||||
@ -248,12 +252,12 @@ void HoymilesRadio::sendEsbPacket(CommandAbstract* cmd)
|
||||
openWritingPipe(s);
|
||||
_radio->setRetries(3, 15);
|
||||
|
||||
Serial.print(F("TX "));
|
||||
Serial.print(cmd->getCommandName());
|
||||
Serial.print(F(" Channel: "));
|
||||
Serial.print(_radio->getChannel());
|
||||
Serial.print(F(" --> "));
|
||||
cmd->dumpDataPayload(Serial);
|
||||
Hoymiles.getMessageOutput()->print(F("TX "));
|
||||
Hoymiles.getMessageOutput()->print(cmd->getCommandName());
|
||||
Hoymiles.getMessageOutput()->print(F(" Channel: "));
|
||||
Hoymiles.getMessageOutput()->print(_radio->getChannel());
|
||||
Hoymiles.getMessageOutput()->print(F(" --> "));
|
||||
cmd->dumpDataPayload(Hoymiles.getMessageOutput());
|
||||
_radio->write(cmd->getDataPayload(), cmd->getDataSize());
|
||||
|
||||
_radio->setRetries(0, 0);
|
||||
@ -285,10 +289,10 @@ void HoymilesRadio::dumpBuf(const char* info, uint8_t buf[], uint8_t len)
|
||||
{
|
||||
|
||||
if (NULL != info)
|
||||
Serial.print(String(info));
|
||||
Hoymiles.getMessageOutput()->print(String(info));
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
Serial.printf("%02X ", buf[i]);
|
||||
Hoymiles.getMessageOutput()->printf("%02X ", buf[i]);
|
||||
}
|
||||
Serial.println(F(""));
|
||||
Hoymiles.getMessageOutput()->println(F(""));
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "TimeoutHelper.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "ActivePowerControlCommand.h"
|
||||
#include "inverters/InverterAbstract.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "DevControlCommand.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "AlarmDataCommand.h"
|
||||
#include "inverters/InverterAbstract.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "MultiDataCommand.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "CommandAbstract.h"
|
||||
#include "crc.h"
|
||||
#include <string.h>
|
||||
@ -19,13 +23,13 @@ const uint8_t* CommandAbstract::getDataPayload()
|
||||
return _payload;
|
||||
}
|
||||
|
||||
void CommandAbstract::dumpDataPayload(Stream& stream)
|
||||
void CommandAbstract::dumpDataPayload(Print* stream)
|
||||
{
|
||||
const uint8_t* payload = getDataPayload();
|
||||
for (uint8_t i = 0; i < getDataSize(); i++) {
|
||||
stream.printf("%02X ", payload[i]);
|
||||
stream->printf("%02X ", payload[i]);
|
||||
}
|
||||
stream.println("");
|
||||
stream->println("");
|
||||
}
|
||||
|
||||
uint8_t CommandAbstract::getDataSize()
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
@ -14,7 +15,7 @@ public:
|
||||
virtual ~CommandAbstract() {};
|
||||
|
||||
const uint8_t* getDataPayload();
|
||||
void dumpDataPayload(Stream& stream);
|
||||
void dumpDataPayload(Print* stream);
|
||||
|
||||
uint8_t getDataSize();
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "DevControlCommand.h"
|
||||
#include "crc.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "CommandAbstract.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "DevInfoAllCommand.h"
|
||||
#include "inverters/InverterAbstract.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "MultiDataCommand.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "DevInfoSimpleCommand.h"
|
||||
#include "inverters/InverterAbstract.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "MultiDataCommand.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "MultiDataCommand.h"
|
||||
#include "crc.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "CommandAbstract.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "ParaSetCommand.h"
|
||||
|
||||
ParaSetCommand::ParaSetCommand(uint64_t target_address, uint64_t router_address)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "CommandAbstract.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "PowerControlCommand.h"
|
||||
#include "inverters/InverterAbstract.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "DevControlCommand.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "RealTimeRunDataCommand.h"
|
||||
#include "inverters/InverterAbstract.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "MultiDataCommand.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "RequestFrameCommand.h"
|
||||
|
||||
RequestFrameCommand::RequestFrameCommand(uint64_t target_address, uint64_t router_address, uint8_t frame_no)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "SingleDataCommand.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "SingleDataCommand.h"
|
||||
|
||||
SingleDataCommand::SingleDataCommand(uint64_t target_address, uint64_t router_address)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "CommandAbstract.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "SystemConfigParaCommand.h"
|
||||
#include "inverters/InverterAbstract.h"
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "MultiDataCommand.h"
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "crc.h"
|
||||
|
||||
uint8_t crc8(const uint8_t buf[], uint8_t len)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "HM_1CH.h"
|
||||
|
||||
HM_1CH::HM_1CH(uint64_t serial)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "HM_Abstract.h"
|
||||
@ -12,24 +13,24 @@ public:
|
||||
|
||||
private:
|
||||
const byteAssign_t byteAssignment[18] = {
|
||||
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10, false },
|
||||
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100, false },
|
||||
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10, false },
|
||||
{ FLD_YD, UNIT_WH, CH1, 12, 2, 1, false },
|
||||
{ FLD_YT, UNIT_KWH, CH1, 8, 4, 1000, false },
|
||||
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC, false },
|
||||
{ CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
|
||||
{ CH1, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
|
||||
{ CH1, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
|
||||
{ CH1, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 },
|
||||
{ CH1, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 },
|
||||
{ CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
|
||||
|
||||
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10, false },
|
||||
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100, false },
|
||||
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10, false },
|
||||
{ FLD_PRA, UNIT_VA, CH0, 20, 2, 10, false },
|
||||
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100, false },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 24, 2, 1000, false },
|
||||
{ FLD_T, UNIT_C, CH0, 26, 2, 10, true },
|
||||
{ FLD_EVT_LOG, UNIT_NONE, CH0, 28, 2, 1, false },
|
||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC, false }
|
||||
{ CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 },
|
||||
{ CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 },
|
||||
{ CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 },
|
||||
{ CH0, FLD_PRA, UNIT_VA, 20, 2, 10, false, 1 },
|
||||
{ CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 },
|
||||
{ CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 },
|
||||
{ CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 },
|
||||
{ CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 },
|
||||
{ CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
|
||||
{ CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
|
||||
{ CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
|
||||
{ CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
|
||||
};
|
||||
};
|
||||
@ -1,3 +1,8 @@
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "HM_2CH.h"
|
||||
|
||||
HM_2CH::HM_2CH(uint64_t serial)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "HM_Abstract.h"
|
||||
@ -12,31 +13,31 @@ public:
|
||||
|
||||
private:
|
||||
const byteAssign_t byteAssignment[24] = {
|
||||
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10, false },
|
||||
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100, false },
|
||||
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10, false },
|
||||
{ FLD_YD, UNIT_WH, CH1, 22, 2, 1, false },
|
||||
{ FLD_YT, UNIT_KWH, CH1, 14, 4, 1000, false },
|
||||
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC, false },
|
||||
{ CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
|
||||
{ CH1, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
|
||||
{ CH1, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
|
||||
{ CH1, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
|
||||
{ CH1, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
|
||||
{ CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH2, 8, 2, 10, false },
|
||||
{ FLD_IDC, UNIT_A, CH2, 10, 2, 100, false },
|
||||
{ FLD_PDC, UNIT_W, CH2, 12, 2, 10, false },
|
||||
{ FLD_YD, UNIT_WH, CH2, 24, 2, 1, false },
|
||||
{ FLD_YT, UNIT_KWH, CH2, 18, 4, 1000, false },
|
||||
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC, false },
|
||||
{ CH2, FLD_UDC, UNIT_V, 8, 2, 10, false, 1 },
|
||||
{ CH2, FLD_IDC, UNIT_A, 10, 2, 100, false, 2 },
|
||||
{ CH2, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
|
||||
{ CH2, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
|
||||
{ CH2, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
|
||||
{ CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
|
||||
|
||||
{ FLD_UAC, UNIT_V, CH0, 26, 2, 10, false },
|
||||
{ FLD_IAC, UNIT_A, CH0, 34, 2, 100, false },
|
||||
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10, false },
|
||||
{ FLD_PRA, UNIT_VA, CH0, 32, 2, 10, false },
|
||||
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100, false },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 36, 2, 1000, false },
|
||||
{ FLD_T, UNIT_C, CH0, 38, 2, 10, true },
|
||||
{ FLD_EVT_LOG, UNIT_NONE, CH0, 40, 2, 1, false },
|
||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC, false }
|
||||
{ CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
|
||||
{ CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
|
||||
{ CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
|
||||
{ CH0, FLD_PRA, UNIT_VA, 32, 2, 10, false, 1 },
|
||||
{ CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
|
||||
{ CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
|
||||
{ CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
|
||||
{ CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
|
||||
{ CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
|
||||
{ CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
|
||||
{ CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
|
||||
{ CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
|
||||
};
|
||||
};
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "HM_4CH.h"
|
||||
|
||||
HM_4CH::HM_4CH(uint64_t serial)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "HM_Abstract.h"
|
||||
@ -12,45 +13,45 @@ public:
|
||||
|
||||
private:
|
||||
const byteAssign_t byteAssignment[36] = {
|
||||
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10, false },
|
||||
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100, false },
|
||||
{ FLD_PDC, UNIT_W, CH1, 8, 2, 10, false },
|
||||
{ FLD_YD, UNIT_WH, CH1, 20, 2, 1, false },
|
||||
{ FLD_YT, UNIT_KWH, CH1, 12, 4, 1000, false },
|
||||
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC, false },
|
||||
{ CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
|
||||
{ CH1, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
|
||||
{ CH1, FLD_PDC, UNIT_W, 8, 2, 10, false, 1 },
|
||||
{ CH1, FLD_YD, UNIT_WH, 20, 2, 1, false, 0 },
|
||||
{ CH1, FLD_YT, UNIT_KWH, 12, 4, 1000, false, 3 },
|
||||
{ CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC, false },
|
||||
{ FLD_IDC, UNIT_A, CH2, 6, 2, 100, false },
|
||||
{ FLD_PDC, UNIT_W, CH2, 10, 2, 10, false },
|
||||
{ FLD_YD, UNIT_WH, CH2, 22, 2, 1, false },
|
||||
{ FLD_YT, UNIT_KWH, CH2, 16, 4, 1000, false },
|
||||
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC, false },
|
||||
{ CH2, FLD_UDC, UNIT_V, CALC_UDC_CH, CH1, CMD_CALC, false, 1 },
|
||||
{ CH2, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
|
||||
{ CH2, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
|
||||
{ CH2, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
|
||||
{ CH2, FLD_YT, UNIT_KWH, 16, 4, 1000, false, 3 },
|
||||
{ CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH3, 24, 2, 10, false },
|
||||
{ FLD_IDC, UNIT_A, CH3, 26, 2, 100, false },
|
||||
{ FLD_PDC, UNIT_W, CH3, 30, 2, 10, false },
|
||||
{ FLD_YD, UNIT_WH, CH3, 42, 2, 1, false },
|
||||
{ FLD_YT, UNIT_KWH, CH3, 34, 4, 1000, false },
|
||||
{ FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC, false },
|
||||
{ CH3, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
|
||||
{ CH3, FLD_IDC, UNIT_A, 26, 2, 100, false, 2 },
|
||||
{ CH3, FLD_PDC, UNIT_W, 30, 2, 10, false, 1 },
|
||||
{ CH3, FLD_YD, UNIT_WH, 42, 2, 1, false, 0 },
|
||||
{ CH3, FLD_YT, UNIT_KWH, 34, 4, 1000, false, 3 },
|
||||
{ CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC, false },
|
||||
{ FLD_IDC, UNIT_A, CH4, 28, 2, 100, false },
|
||||
{ FLD_PDC, UNIT_W, CH4, 32, 2, 10, false },
|
||||
{ FLD_YD, UNIT_WH, CH4, 44, 2, 1, false },
|
||||
{ FLD_YT, UNIT_KWH, CH4, 38, 4, 1000, false },
|
||||
{ FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC, false },
|
||||
{ CH4, FLD_UDC, UNIT_V, CALC_UDC_CH, CH3, CMD_CALC, false, 1 },
|
||||
{ CH4, FLD_IDC, UNIT_A, 28, 2, 100, false, 2 },
|
||||
{ CH4, FLD_PDC, UNIT_W, 32, 2, 10, false, 1 },
|
||||
{ CH4, FLD_YD, UNIT_WH, 44, 2, 1, false, 0 },
|
||||
{ CH4, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
|
||||
{ CH4, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH4, CMD_CALC, false, 3 },
|
||||
|
||||
{ FLD_UAC, UNIT_V, CH0, 46, 2, 10, false },
|
||||
{ FLD_IAC, UNIT_A, CH0, 54, 2, 100, false },
|
||||
{ FLD_PAC, UNIT_W, CH0, 50, 2, 10, false },
|
||||
{ FLD_PRA, UNIT_VA, CH0, 52, 2, 10, false },
|
||||
{ FLD_F, UNIT_HZ, CH0, 48, 2, 100, false },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 56, 2, 1000, false },
|
||||
{ FLD_T, UNIT_C, CH0, 58, 2, 10, true },
|
||||
{ FLD_EVT_LOG, UNIT_NONE, CH0, 60, 2, 1, false },
|
||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC, false },
|
||||
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC, false }
|
||||
{ CH0, FLD_UAC, UNIT_V, 46, 2, 10, false, 1 },
|
||||
{ CH0, FLD_IAC, UNIT_A, 54, 2, 100, false, 2 },
|
||||
{ CH0, FLD_PAC, UNIT_W, 50, 2, 10, false, 1 },
|
||||
{ CH0, FLD_PRA, UNIT_VA, 52, 2, 10, false, 1 },
|
||||
{ CH0, FLD_F, UNIT_HZ, 48, 2, 100, false, 2 },
|
||||
{ CH0, FLD_PF, UNIT_NONE, 56, 2, 1000, false, 3 },
|
||||
{ CH0, FLD_T, UNIT_C, 58, 2, 10, true, 1 },
|
||||
{ CH0, FLD_EVT_LOG, UNIT_NONE, 60, 2, 1, false, 0 },
|
||||
{ CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
|
||||
{ CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
|
||||
{ CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
|
||||
{ CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
|
||||
};
|
||||
};
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "HM_Abstract.h"
|
||||
#include "HoymilesRadio.h"
|
||||
#include "commands/ActivePowerControlCommand.h"
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "InverterAbstract.h"
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "InverterAbstract.h"
|
||||
#include "../Hoymiles.h"
|
||||
#include "crc.h"
|
||||
#include <cstring>
|
||||
|
||||
@ -103,18 +108,18 @@ void InverterAbstract::clearRxFragmentBuffer()
|
||||
void InverterAbstract::addRxFragment(uint8_t fragment[], uint8_t len)
|
||||
{
|
||||
if (len < 11) {
|
||||
Serial.printf("FATAL: (%s, %d) fragment too short\n", __FILE__, __LINE__);
|
||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too short\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len - 11 > MAX_RF_PAYLOAD_SIZE) {
|
||||
Serial.printf("FATAL: (%s, %d) fragment too large\n", __FILE__, __LINE__);
|
||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too large\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t fragmentCount = fragment[9];
|
||||
if (fragmentCount == 0) {
|
||||
Serial.println("ERROR: fragment number zero received and ignored");
|
||||
Hoymiles.getMessageOutput()->println("ERROR: fragment number zero received and ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -141,7 +146,7 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
|
||||
{
|
||||
// All missing
|
||||
if (_rxFragmentLastPacketId == 0) {
|
||||
Serial.println(F("All missing"));
|
||||
Hoymiles.getMessageOutput()->println(F("All missing"));
|
||||
if (cmd->getSendCount() <= MAX_RESEND_COUNT) {
|
||||
return FRAGMENT_ALL_MISSING_RESEND;
|
||||
} else {
|
||||
@ -152,7 +157,7 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
|
||||
|
||||
// Last fragment is missing (thte one with 0x80)
|
||||
if (_rxFragmentMaxPacketId == 0) {
|
||||
Serial.println(F("Last missing"));
|
||||
Hoymiles.getMessageOutput()->println(F("Last missing"));
|
||||
if (_rxFragmentRetransmitCnt++ < MAX_RETRANSMIT_COUNT) {
|
||||
return _rxFragmentLastPacketId + 1;
|
||||
} else {
|
||||
@ -164,7 +169,7 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
|
||||
// Middle fragment is missing
|
||||
for (uint8_t i = 0; i < _rxFragmentMaxPacketId - 1; i++) {
|
||||
if (!_rxFragmentBuffer[i].wasReceived) {
|
||||
Serial.println(F("Middle missing"));
|
||||
Hoymiles.getMessageOutput()->println(F("Middle missing"));
|
||||
if (_rxFragmentRetransmitCnt++ < MAX_RETRANSMIT_COUNT) {
|
||||
return i + 1;
|
||||
} else {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "../commands/ActivePowerControlCommand.h"
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "AlarmLogParser.h"
|
||||
#include "../Hoymiles.h"
|
||||
#include <cstring>
|
||||
|
||||
void AlarmLogParser::clearBuffer()
|
||||
@ -10,7 +15,7 @@ void AlarmLogParser::clearBuffer()
|
||||
void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||
{
|
||||
if (offset + len > ALARM_LOG_PAYLOAD_SIZE) {
|
||||
Serial.printf("FATAL: (%s, %d) stats packet too large for buffer (%d > %d)\n", __FILE__, __LINE__, offset + len, ALARM_LOG_PAYLOAD_SIZE);
|
||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer (%d > %d)\n", __FILE__, __LINE__, offset + len, ALARM_LOG_PAYLOAD_SIZE);
|
||||
return;
|
||||
}
|
||||
memcpy(&_payloadAlarmLog[offset], payload, len);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include "Parser.h"
|
||||
#include <cstdint>
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "DevInfoParser.h"
|
||||
#include "../Hoymiles.h"
|
||||
#include <cstring>
|
||||
|
||||
#define ALL 0xff
|
||||
@ -32,7 +37,7 @@ void DevInfoParser::clearBufferAll()
|
||||
void DevInfoParser::appendFragmentAll(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||
{
|
||||
if (offset + len > DEV_INFO_SIZE) {
|
||||
Serial.printf("FATAL: (%s, %d) dev info all packet too large for buffer\n", __FILE__, __LINE__);
|
||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) dev info all packet too large for buffer\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
memcpy(&_payloadDevInfoAll[offset], payload, len);
|
||||
@ -48,7 +53,7 @@ void DevInfoParser::clearBufferSimple()
|
||||
void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||
{
|
||||
if (offset + len > DEV_INFO_SIZE) {
|
||||
Serial.printf("FATAL: (%s, %d) dev info Simple packet too large for buffer\n", __FILE__, __LINE__);
|
||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) dev info Simple packet too large for buffer\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
memcpy(&_payloadDevInfoSimple[offset], payload, len);
|
||||
@ -84,7 +89,7 @@ uint16_t DevInfoParser::getFwBuildVersion()
|
||||
|
||||
time_t DevInfoParser::getFwBuildDateTime()
|
||||
{
|
||||
struct tm timeinfo = { };
|
||||
struct tm timeinfo = {};
|
||||
timeinfo.tm_year = ((((uint16_t)_payloadDevInfoAll[2]) << 8) | _payloadDevInfoAll[3]) - 1900;
|
||||
|
||||
timeinfo.tm_mon = ((((uint16_t)_payloadDevInfoAll[4]) << 8) | _payloadDevInfoAll[5]) / 100 - 1;
|
||||
@ -139,10 +144,10 @@ String DevInfoParser::getHwModelName()
|
||||
|
||||
uint8_t DevInfoParser::getDevIdx()
|
||||
{
|
||||
uint8_t pos;
|
||||
// Check for all 4 bytes first
|
||||
uint8_t pos;
|
||||
// Check for all 4 bytes first
|
||||
for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) {
|
||||
if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2]
|
||||
if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2]
|
||||
&& devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3]
|
||||
&& devInfo[pos].hwPart[2] == _payloadDevInfoSimple[4]
|
||||
&& devInfo[pos].hwPart[3] == _payloadDevInfoSimple[5]) {
|
||||
@ -152,7 +157,7 @@ uint8_t DevInfoParser::getDevIdx()
|
||||
|
||||
// Then only for 3 bytes
|
||||
for (pos = 0; pos < sizeof(devInfo) / sizeof(devInfo_t); pos++) {
|
||||
if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2]
|
||||
if (devInfo[pos].hwPart[0] == _payloadDevInfoSimple[2]
|
||||
&& devInfo[pos].hwPart[1] == _payloadDevInfoSimple[3]
|
||||
&& devInfo[pos].hwPart[2] == _payloadDevInfoSimple[4]) {
|
||||
return pos;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include "Parser.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "Parser.h"
|
||||
|
||||
uint32_t Parser::getLastUpdate()
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "PowerCommandParser.h"
|
||||
|
||||
void PowerCommandParser::setLastPowerCommandSuccess(LastCommandSuccess status)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include "Parser.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "StatisticsParser.h"
|
||||
#include "../Hoymiles.h"
|
||||
|
||||
static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0);
|
||||
static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0);
|
||||
@ -38,7 +43,7 @@ void StatisticsParser::clearBuffer()
|
||||
void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||
{
|
||||
if (offset + len > STATISTIC_PACKET_SIZE) {
|
||||
Serial.printf("FATAL: (%s, %d) stats packet too large for buffer\n", __FILE__, __LINE__);
|
||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
memcpy(&_payloadStatistic[offset], payload, len);
|
||||
@ -123,20 +128,7 @@ const char* StatisticsParser::getChannelFieldName(uint8_t channel, uint8_t field
|
||||
uint8_t StatisticsParser::getChannelFieldDigits(uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
|
||||
const byteAssign_t* b = _byteAssignment;
|
||||
|
||||
switch (b[pos].div) {
|
||||
case 1:
|
||||
return 0;
|
||||
case 10:
|
||||
return 1;
|
||||
case 100:
|
||||
return 2;
|
||||
case 1000:
|
||||
return 3;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
return _byteAssignment[pos].digits;
|
||||
}
|
||||
|
||||
uint8_t StatisticsParser::getChannelCount()
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include "Parser.h"
|
||||
#include <Arduino.h>
|
||||
@ -62,13 +63,14 @@ enum {
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t ch; // channel 0 - 4
|
||||
uint8_t fieldId; // field id
|
||||
uint8_t unitId; // uint id
|
||||
uint8_t ch; // channel 0 - 4
|
||||
uint8_t start; // pos of first byte in buffer
|
||||
uint8_t num; // number of bytes in buffer
|
||||
uint16_t div; // divisor / calc command
|
||||
bool isSigned; // allow negative numbers
|
||||
uint8_t digits; // number of valid digits after the decimal point
|
||||
} byteAssign_t;
|
||||
|
||||
class StatisticsParser : public Parser {
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "SystemConfigParaParser.h"
|
||||
#include "../Hoymiles.h"
|
||||
#include <cstring>
|
||||
|
||||
void SystemConfigParaParser::clearBuffer()
|
||||
@ -10,7 +15,7 @@ void SystemConfigParaParser::clearBuffer()
|
||||
void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||
{
|
||||
if (offset + len > (SYSTEM_CONFIG_PARA_SIZE)) {
|
||||
Serial.printf("FATAL: (%s, %d) stats packet too large for buffer\n", __FILE__, __LINE__);
|
||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
memcpy(&_payload[offset], payload, len);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include "Parser.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
171
lib/MqttSubscribeParser/MqttSubscribeParser.cpp
Normal file
171
lib/MqttSubscribeParser/MqttSubscribeParser.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "MqttSubscribeParser.h"
|
||||
|
||||
void MqttSubscribeParser::register_callback(const std::string& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb)
|
||||
{
|
||||
cb_filter_t cbf;
|
||||
cbf.topic = topic;
|
||||
cbf.qos = qos;
|
||||
cbf.cb = cb;
|
||||
_callbacks.push_back(cbf);
|
||||
}
|
||||
|
||||
void MqttSubscribeParser::unregister_callback(const std::string& topic)
|
||||
{
|
||||
for (auto it = _callbacks.begin(); it != _callbacks.end();) {
|
||||
if ((*it).topic == topic) {
|
||||
it = _callbacks.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MqttSubscribeParser::handle_message(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
bool result = false;
|
||||
for (const auto& cb : _callbacks) {
|
||||
if (mosquitto_topic_matches_sub(cb.topic.c_str(), topic, &result) == MOSQ_ERR_SUCCESS) {
|
||||
if (result) {
|
||||
cb.cb(properties, topic, payload, len, index, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<cb_filter_t> MqttSubscribeParser::get_callbacks()
|
||||
{
|
||||
return _callbacks;
|
||||
}
|
||||
|
||||
/* Does a topic match a subscription? */
|
||||
int MqttSubscribeParser::mosquitto_topic_matches_sub(const char* sub, const char* topic, bool* result)
|
||||
{
|
||||
size_t spos;
|
||||
|
||||
if (!result)
|
||||
return MOSQ_ERR_INVAL;
|
||||
*result = false;
|
||||
|
||||
if (!sub || !topic || sub[0] == 0 || topic[0] == 0) {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
|
||||
if ((sub[0] == '$' && topic[0] != '$')
|
||||
|| (topic[0] == '$' && sub[0] != '$')) {
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
spos = 0;
|
||||
|
||||
while (sub[0] != 0) {
|
||||
if (topic[0] == '+' || topic[0] == '#') {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
if (sub[0] != topic[0] || topic[0] == 0) { /* Check for wildcard matches */
|
||||
if (sub[0] == '+') {
|
||||
/* Check for bad "+foo" or "a/+foo" subscription */
|
||||
if (spos > 0 && sub[-1] != '/') {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
/* Check for bad "foo+" or "foo+/a" subscription */
|
||||
if (sub[1] != 0 && sub[1] != '/') {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
spos++;
|
||||
sub++;
|
||||
while (topic[0] != 0 && topic[0] != '/') {
|
||||
if (topic[0] == '+' || topic[0] == '#') {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
topic++;
|
||||
}
|
||||
if (topic[0] == 0 && sub[0] == 0) {
|
||||
*result = true;
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
} else if (sub[0] == '#') {
|
||||
/* Check for bad "foo#" subscription */
|
||||
if (spos > 0 && sub[-1] != '/') {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
/* Check for # not the final character of the sub, e.g. "#foo" */
|
||||
if (sub[1] != 0) {
|
||||
return MOSQ_ERR_INVAL;
|
||||
} else {
|
||||
while (topic[0] != 0) {
|
||||
if (topic[0] == '+' || topic[0] == '#') {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
topic++;
|
||||
}
|
||||
*result = true;
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
/* Check for e.g. foo/bar matching foo/+/# */
|
||||
if (topic[0] == 0
|
||||
&& spos > 0
|
||||
&& sub[-1] == '+'
|
||||
&& sub[0] == '/'
|
||||
&& sub[1] == '#') {
|
||||
*result = true;
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
/* There is no match at this point, but is the sub invalid? */
|
||||
while (sub[0] != 0) {
|
||||
if (sub[0] == '#' && sub[1] != 0) {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
spos++;
|
||||
sub++;
|
||||
}
|
||||
|
||||
/* Valid input, but no match */
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
/* sub[spos] == topic[tpos] */
|
||||
if (topic[1] == 0) {
|
||||
/* Check for e.g. foo matching foo/# */
|
||||
if (sub[1] == '/'
|
||||
&& sub[2] == '#'
|
||||
&& sub[3] == 0) {
|
||||
*result = true;
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
spos++;
|
||||
sub++;
|
||||
topic++;
|
||||
if (sub[0] == 0 && topic[0] == 0) {
|
||||
*result = true;
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
} else if (topic[0] == 0 && sub[0] == '+' && sub[1] == 0) {
|
||||
if (spos > 0 && sub[-1] != '/') {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
spos++;
|
||||
sub++;
|
||||
*result = true;
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((topic[0] != 0 || sub[0] != 0)) {
|
||||
*result = false;
|
||||
}
|
||||
while (topic[0] != 0) {
|
||||
if (topic[0] == '+' || topic[0] == '#') {
|
||||
return MOSQ_ERR_INVAL;
|
||||
}
|
||||
topic++;
|
||||
}
|
||||
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
31
lib/MqttSubscribeParser/MqttSubscribeParser.h
Normal file
31
lib/MqttSubscribeParser/MqttSubscribeParser.h
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <espMqttClient.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct cb_filter_t {
|
||||
std::string topic;
|
||||
uint8_t qos;
|
||||
espMqttClientTypes::OnMessageCallback cb;
|
||||
};
|
||||
|
||||
class MqttSubscribeParser {
|
||||
public:
|
||||
void register_callback(const std::string& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb);
|
||||
void unregister_callback(const std::string& topic);
|
||||
void handle_message(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
|
||||
std::vector<cb_filter_t> get_callbacks();
|
||||
|
||||
private:
|
||||
int mosquitto_topic_matches_sub(const char* sub, const char* topic, bool* result);
|
||||
|
||||
std::vector<cb_filter_t> _callbacks;
|
||||
|
||||
enum mosq_err_t {
|
||||
MOSQ_ERR_SUCCESS = 0,
|
||||
MOSQ_ERR_INVAL = 3,
|
||||
};
|
||||
};
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "ResetReason.h"
|
||||
|
||||
#if ESP_IDF_VERSION_MAJOR > 3 // IDF 4+
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "TimeoutHelper.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
|
||||
[platformio]
|
||||
default_envs = generic
|
||||
extra_configs =
|
||||
platformio_override.ini
|
||||
|
||||
[env]
|
||||
framework = arduino
|
||||
@ -35,9 +37,9 @@ monitor_filters = esp32_exception_decoder, time, log2file, colorize
|
||||
monitor_speed = 115200
|
||||
upload_protocol = esptool
|
||||
|
||||
; Specify port here. Comment out (add ; in front of line) to use auto detection.
|
||||
monitor_port = COM4
|
||||
upload_port = COM4
|
||||
; Specify port in platformio_override.ini. Comment out (add ; in front of line) to use auto detection.
|
||||
; monitor_port = COM4
|
||||
; upload_port = COM4
|
||||
|
||||
|
||||
[env:generic]
|
||||
|
||||
31
platformio_override.ini
Normal file
31
platformio_override.ini
Normal file
@ -0,0 +1,31 @@
|
||||
; *** PlatformIO Project Configuration Override File ***
|
||||
; *** Changes done here override settings in platformio.ini ***
|
||||
;
|
||||
; Place your personal settings like monitor_port and upload_port here
|
||||
; instead of editing platformio.ini
|
||||
; to avoid annoying merge conflicts when you pull updates into your
|
||||
; personal clone of the repository
|
||||
;
|
||||
; Please visit documentation for the options and examples
|
||||
; http://docs.platformio.org/en/stable/projectconf.html
|
||||
|
||||
[env]
|
||||
; Specify port here. Comment out (add ; in front of line) to use auto detection.
|
||||
monitor_port = COM4
|
||||
upload_port = COM4
|
||||
|
||||
|
||||
; you can define your personal board and/or settings here
|
||||
; non functional example:
|
||||
|
||||
;[env:my_very_special_board]
|
||||
;board = esp32dev
|
||||
;build_flags = ${env.build_flags}
|
||||
; -DHOYMILES_PIN_MISO=1
|
||||
; -DHOYMILES_PIN_MOSI=2
|
||||
; -DHOYMILES_PIN_SCLK=3
|
||||
; -DHOYMILES_PIN_IRQ=4
|
||||
; -DHOYMILES_PIN_CE=5
|
||||
; -DHOYMILES_PIN_CS=6
|
||||
;monitor_port = /dev/ttyACM0
|
||||
;upload_port = /dev/ttyACM0
|
||||
@ -3,6 +3,7 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "Configuration.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "defaults.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
@ -100,7 +101,7 @@ bool ConfigurationClass::write()
|
||||
|
||||
// Serialize JSON to file
|
||||
if (serializeJson(doc, f) == 0) {
|
||||
Serial.println("Failed to write file");
|
||||
MessageOutput.println("Failed to write file");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -116,7 +117,7 @@ bool ConfigurationClass::read()
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, f);
|
||||
if (error) {
|
||||
Serial.println(F("Failed to read file, using default configuration"));
|
||||
MessageOutput.println(F("Failed to read file, using default configuration"));
|
||||
}
|
||||
|
||||
JsonObject cfg = doc["cfg"];
|
||||
@ -232,7 +233,7 @@ void ConfigurationClass::migrate()
|
||||
if (config.Cfg_Version < 0x00011700) {
|
||||
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
|
||||
if (!f) {
|
||||
Serial.println(F("Failed to open file, cancel migration"));
|
||||
MessageOutput.println(F("Failed to open file, cancel migration"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -240,7 +241,7 @@ void ConfigurationClass::migrate()
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, f);
|
||||
if (error) {
|
||||
Serial.println(F("Failed to read file, cancel migration"));
|
||||
MessageOutput.println(F("Failed to read file, cancel migration"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
54
src/MessageOutput.cpp
Normal file
54
src/MessageOutput.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "MessageOutput.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
MessageOutputClass MessageOutput;
|
||||
|
||||
#define MSG_LOCK() xSemaphoreTake(_lock, portMAX_DELAY)
|
||||
#define MSG_UNLOCK() xSemaphoreGive(_lock)
|
||||
|
||||
MessageOutputClass::MessageOutputClass()
|
||||
{
|
||||
_lock = xSemaphoreCreateMutex();
|
||||
MSG_UNLOCK();
|
||||
}
|
||||
|
||||
void MessageOutputClass::register_ws_output(AsyncWebSocket* output)
|
||||
{
|
||||
_ws = output;
|
||||
}
|
||||
|
||||
size_t MessageOutputClass::write(uint8_t c)
|
||||
{
|
||||
if (_buff_pos < BUFFER_SIZE) {
|
||||
MSG_LOCK();
|
||||
_buffer[_buff_pos] = c;
|
||||
_buff_pos++;
|
||||
MSG_UNLOCK();
|
||||
} else {
|
||||
_forceSend = true;
|
||||
}
|
||||
|
||||
return Serial.write(c);
|
||||
}
|
||||
|
||||
void MessageOutputClass::loop()
|
||||
{
|
||||
// Send data via websocket if either time is over or buffer is full
|
||||
if (_forceSend || (millis() - _lastSend > 1000)) {
|
||||
MSG_LOCK();
|
||||
if (_ws && _buff_pos > 0) {
|
||||
_ws->textAll(_buffer, _buff_pos);
|
||||
_buff_pos = 0;
|
||||
}
|
||||
if(_forceSend) {
|
||||
_buff_pos = 0;
|
||||
}
|
||||
MSG_UNLOCK();
|
||||
_forceSend = false;
|
||||
}
|
||||
}
|
||||
35
src/MqttHandleDtu.cpp
Normal file
35
src/MqttHandleDtu.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "MqttHandleDtu.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include <Hoymiles.h>
|
||||
|
||||
MqttHandleDtuClass MqttHandleDtu;
|
||||
|
||||
void MqttHandleDtuClass::init()
|
||||
{
|
||||
}
|
||||
|
||||
void MqttHandleDtuClass::loop()
|
||||
{
|
||||
if (!MqttSettings.getConnected() || !Hoymiles.getRadio()->isIdle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
|
||||
MqttSettings.publish("dtu/uptime", String(millis() / 1000));
|
||||
MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString());
|
||||
MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname());
|
||||
if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
|
||||
MqttSettings.publish("dtu/rssi", String(WiFi.RSSI()));
|
||||
}
|
||||
|
||||
_lastPublish = millis();
|
||||
}
|
||||
}
|
||||
@ -2,18 +2,18 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "MqttHassPublishing.h"
|
||||
#include "MqttPublishing.h"
|
||||
#include "MqttHandleHass.h"
|
||||
#include "MqttHandleInverter.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
|
||||
MqttHassPublishingClass MqttHassPublishing;
|
||||
MqttHandleHassClass MqttHandleHass;
|
||||
|
||||
void MqttHassPublishingClass::init()
|
||||
void MqttHandleHassClass::init()
|
||||
{
|
||||
}
|
||||
|
||||
void MqttHassPublishingClass::loop()
|
||||
void MqttHandleHassClass::loop()
|
||||
{
|
||||
if (_updateForced) {
|
||||
publishConfig();
|
||||
@ -30,12 +30,12 @@ void MqttHassPublishingClass::loop()
|
||||
}
|
||||
}
|
||||
|
||||
void MqttHassPublishingClass::forceUpdate()
|
||||
void MqttHandleHassClass::forceUpdate()
|
||||
{
|
||||
_updateForced = true;
|
||||
}
|
||||
|
||||
void MqttHassPublishingClass::publishConfig()
|
||||
void MqttHandleHassClass::publishConfig()
|
||||
{
|
||||
if (!Configuration.get().Mqtt_Hass_Enabled) {
|
||||
return;
|
||||
@ -79,7 +79,7 @@ void MqttHassPublishingClass::publishConfig()
|
||||
}
|
||||
}
|
||||
|
||||
void MqttHassPublishingClass::publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear)
|
||||
void MqttHandleHassClass::publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear)
|
||||
{
|
||||
if (!inv->Statistics()->hasChannelFieldValue(channel, fieldType.fieldId)) {
|
||||
return;
|
||||
@ -99,7 +99,7 @@ void MqttHassPublishingClass::publishField(std::shared_ptr<InverterAbstract> inv
|
||||
+ "/config";
|
||||
|
||||
if (!clear) {
|
||||
String stateTopic = MqttSettings.getPrefix() + MqttPublishing.getTopic(inv, channel, fieldType.fieldId);
|
||||
String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, channel, fieldType.fieldId);
|
||||
const char* devCls = deviceClasses[fieldType.deviceClsId];
|
||||
const char* stateCls = stateClasses[fieldType.stateClsId];
|
||||
|
||||
@ -131,13 +131,13 @@ void MqttHassPublishingClass::publishField(std::shared_ptr<InverterAbstract> inv
|
||||
|
||||
char buffer[512];
|
||||
serializeJson(root, buffer);
|
||||
MqttSettings.publishHass(configTopic, buffer);
|
||||
publish(configTopic, buffer);
|
||||
} else {
|
||||
MqttSettings.publishHass(configTopic, "");
|
||||
publish(configTopic, "");
|
||||
}
|
||||
}
|
||||
|
||||
void MqttHassPublishingClass::publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload)
|
||||
void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload)
|
||||
{
|
||||
String serial = inv->serialString();
|
||||
|
||||
@ -169,10 +169,10 @@ void MqttHassPublishingClass::publishInverterButton(std::shared_ptr<InverterAbst
|
||||
|
||||
char buffer[512];
|
||||
serializeJson(root, buffer);
|
||||
MqttSettings.publishHass(configTopic, buffer);
|
||||
publish(configTopic, buffer);
|
||||
}
|
||||
|
||||
void MqttHassPublishingClass::publishInverterNumber(
|
||||
void MqttHandleHassClass::publishInverterNumber(
|
||||
std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category,
|
||||
const char* commandTopic, const char* stateTopic, const char* unitOfMeasure,
|
||||
int16_t min, int16_t max)
|
||||
@ -208,10 +208,10 @@ void MqttHassPublishingClass::publishInverterNumber(
|
||||
|
||||
char buffer[512];
|
||||
serializeJson(root, buffer);
|
||||
MqttSettings.publishHass(configTopic, buffer);
|
||||
publish(configTopic, buffer);
|
||||
}
|
||||
|
||||
void MqttHassPublishingClass::publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off)
|
||||
void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off)
|
||||
{
|
||||
String serial = inv->serialString();
|
||||
|
||||
@ -237,15 +237,22 @@ void MqttHassPublishingClass::publishInverterBinarySensor(std::shared_ptr<Invert
|
||||
|
||||
char buffer[512];
|
||||
serializeJson(root, buffer);
|
||||
MqttSettings.publishHass(configTopic, buffer);
|
||||
publish(configTopic, buffer);
|
||||
}
|
||||
|
||||
void MqttHassPublishingClass::createDeviceInfo(JsonObject& object, std::shared_ptr<InverterAbstract> inv)
|
||||
void MqttHandleHassClass::createDeviceInfo(JsonObject& object, std::shared_ptr<InverterAbstract> inv)
|
||||
{
|
||||
object[F("name")] = inv->name();
|
||||
object[F("ids")] = inv->serialString();
|
||||
object[F("cu")] = String(F("http://")) + WiFi.localIP().toString();
|
||||
object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString();
|
||||
object[F("mf")] = F("OpenDTU");
|
||||
object[F("mdl")] = inv->typeName();
|
||||
object[F("sw")] = AUTO_GIT_HASH;
|
||||
}
|
||||
|
||||
void MqttHandleHassClass::publish(const String& subtopic, const String& payload)
|
||||
{
|
||||
String topic = Configuration.get().Mqtt_Hass_Topic;
|
||||
topic += subtopic;
|
||||
MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt_Hass_Retain);
|
||||
}
|
||||
229
src/MqttHandleInverter.cpp
Normal file
229
src/MqttHandleInverter.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "MqttHandleInverter.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttSettings.h"
|
||||
#include <ctime>
|
||||
|
||||
#define TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE "limit_persistent_relative"
|
||||
#define TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE "limit_persistent_absolute"
|
||||
#define TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE "limit_nonpersistent_relative"
|
||||
#define TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE "limit_nonpersistent_absolute"
|
||||
#define TOPIC_SUB_POWER "power"
|
||||
#define TOPIC_SUB_RESTART "restart"
|
||||
|
||||
MqttHandleInverterClass MqttHandleInverter;
|
||||
|
||||
void MqttHandleInverterClass::init()
|
||||
{
|
||||
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 topic = MqttSettings.getPrefix();
|
||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_POWER).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_RESTART).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
void MqttHandleInverterClass::loop()
|
||||
{
|
||||
if (!MqttSettings.getConnected() || !Hoymiles.getRadio()->isIdle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
|
||||
// Loop all inverters
|
||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||
auto inv = Hoymiles.getInverterByPos(i);
|
||||
|
||||
String subtopic = inv->serialString();
|
||||
|
||||
// Name
|
||||
MqttSettings.publish(subtopic + "/name", inv->name());
|
||||
|
||||
if (inv->DevInfo()->getLastUpdate() > 0) {
|
||||
// Bootloader Version
|
||||
MqttSettings.publish(subtopic + "/device/bootloaderversion", String(inv->DevInfo()->getFwBootloaderVersion()));
|
||||
|
||||
// Firmware Version
|
||||
MqttSettings.publish(subtopic + "/device/fwbuildversion", String(inv->DevInfo()->getFwBuildVersion()));
|
||||
|
||||
// Firmware Build DateTime
|
||||
char timebuffer[32];
|
||||
const time_t t = inv->DevInfo()->getFwBuildDateTime();
|
||||
std::strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", gmtime(&t));
|
||||
MqttSettings.publish(subtopic + "/device/fwbuilddatetime", String(timebuffer));
|
||||
|
||||
// Hardware part number
|
||||
MqttSettings.publish(subtopic + "/device/hwpartnumber", String(inv->DevInfo()->getHwPartNumber()));
|
||||
|
||||
// Hardware version
|
||||
MqttSettings.publish(subtopic + "/device/hwversion", inv->DevInfo()->getHwVersion());
|
||||
}
|
||||
|
||||
if (inv->SystemConfigPara()->getLastUpdate() > 0) {
|
||||
// Limit
|
||||
MqttSettings.publish(subtopic + "/status/limit_relative", String(inv->SystemConfigPara()->getLimitPercent()));
|
||||
|
||||
uint16_t maxpower = inv->DevInfo()->getMaxPower();
|
||||
if (maxpower > 0) {
|
||||
MqttSettings.publish(subtopic + "/status/limit_absolute", String(inv->SystemConfigPara()->getLimitPercent() * maxpower / 100));
|
||||
}
|
||||
}
|
||||
|
||||
MqttSettings.publish(subtopic + "/status/reachable", String(inv->isReachable()));
|
||||
MqttSettings.publish(subtopic + "/status/producing", String(inv->isProducing()));
|
||||
|
||||
if (inv->Statistics()->getLastUpdate() > 0) {
|
||||
MqttSettings.publish(subtopic + "/status/last_update", String(std::time(0) - (millis() - inv->Statistics()->getLastUpdate()) / 1000));
|
||||
} else {
|
||||
MqttSettings.publish(subtopic + "/status/last_update", String(0));
|
||||
}
|
||||
|
||||
uint32_t lastUpdate = inv->Statistics()->getLastUpdate();
|
||||
if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) {
|
||||
_lastPublishStats[i] = lastUpdate;
|
||||
|
||||
// Loop all channels
|
||||
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
|
||||
if (c > 0) {
|
||||
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
|
||||
if (inv_cfg != nullptr) {
|
||||
MqttSettings.publish(inv->serialString() + "/" + String(c) + "/name", inv_cfg->channel[c - 1].Name);
|
||||
}
|
||||
}
|
||||
for (uint8_t f = 0; f < sizeof(_publishFields); f++) {
|
||||
publishField(inv, c, _publishFields[f]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
_lastPublish = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void MqttHandleInverterClass::publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
String topic = getTopic(inv, channel, fieldId);
|
||||
if (topic == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
MqttSettings.publish(topic, String(inv->Statistics()->getChannelFieldValue(channel, fieldId)));
|
||||
}
|
||||
|
||||
String MqttHandleInverterClass::getTopic(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
if (!inv->Statistics()->hasChannelFieldValue(channel, fieldId)) {
|
||||
return String("");
|
||||
}
|
||||
|
||||
String chanName;
|
||||
if (channel == 0 && fieldId == FLD_PDC) {
|
||||
chanName = "powerdc";
|
||||
} else {
|
||||
chanName = inv->Statistics()->getChannelFieldName(channel, fieldId);
|
||||
chanName.toLowerCase();
|
||||
}
|
||||
|
||||
return inv->serialString() + "/" + String(channel) + "/" + chanName;
|
||||
}
|
||||
|
||||
void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
char token_topic[MQTT_MAX_TOPIC_STRLEN + 40]; // respect all subtopics
|
||||
strncpy(token_topic, topic, MQTT_MAX_TOPIC_STRLEN + 40); // convert const char* to char*
|
||||
|
||||
char* serial_str;
|
||||
char* subtopic;
|
||||
char* setting;
|
||||
char* rest = &token_topic[strlen(config.Mqtt_Topic)];
|
||||
|
||||
serial_str = strtok_r(rest, "/", &rest);
|
||||
subtopic = strtok_r(rest, "/", &rest);
|
||||
setting = strtok_r(rest, "/", &rest);
|
||||
|
||||
if (serial_str == NULL || subtopic == NULL || setting == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t serial;
|
||||
serial = strtoull(serial_str, 0, 16);
|
||||
|
||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||
|
||||
if (inv == nullptr) {
|
||||
MessageOutput.println(F("Inverter not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
// check if subtopic is unequal cmd
|
||||
if (strcmp(subtopic, "cmd")) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* strlimit = new char[len + 1];
|
||||
memcpy(strlimit, payload, len);
|
||||
strlimit[len] = '\0';
|
||||
uint32_t payload_val = strtol(strlimit, NULL, 10);
|
||||
delete[] strlimit;
|
||||
|
||||
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
||||
// Set inverter limit relative persistent
|
||||
MessageOutput.printf("Limit Persistent: %d %%\n", payload_val);
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativPersistent);
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
||||
// Set inverter limit absolute persistent
|
||||
MessageOutput.printf("Limit Persistent: %d W\n", payload_val);
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutPersistent);
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
||||
// Set inverter limit relative non persistent
|
||||
MessageOutput.printf("Limit Non-Persistent: %d %%\n", payload_val);
|
||||
if (!properties.retain) {
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativNonPersistent);
|
||||
} else {
|
||||
MessageOutput.println("Ignored because retained");
|
||||
}
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
||||
// Set inverter limit absolute non persistent
|
||||
MessageOutput.printf("Limit Non-Persistent: %d W\n", payload_val);
|
||||
if (!properties.retain) {
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutNonPersistent);
|
||||
} else {
|
||||
MessageOutput.println("Ignored because retained");
|
||||
}
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
||||
// Turn inverter on or off
|
||||
MessageOutput.printf("Set inverter power to: %d\n", payload_val);
|
||||
inv->sendPowerControlRequest(Hoymiles.getRadio(), payload_val > 0);
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
||||
// Restart inverter
|
||||
MessageOutput.printf("Restart inverter\n");
|
||||
if (!properties.retain && payload_val == 1) {
|
||||
inv->sendRestartControlRequest(Hoymiles.getRadio());
|
||||
} else {
|
||||
MessageOutput.println("Ignored because retained");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,19 +3,19 @@
|
||||
* Copyright (C) 2022 Helge Erbe and others
|
||||
*/
|
||||
#include "VeDirectFrameHandler.h"
|
||||
#include "MqttVedirectPublishing.h"
|
||||
#include "MqttHandleVedirect.h"
|
||||
#include "MqttSettings.h"
|
||||
|
||||
|
||||
|
||||
|
||||
MqttVedirectPublishingClass MqttVedirectPublishing;
|
||||
MqttHandleVedirectClass MqttHandleVedirect;
|
||||
|
||||
void MqttVedirectPublishingClass::init()
|
||||
void MqttHandleVedirectClass::init()
|
||||
{
|
||||
}
|
||||
|
||||
void MqttVedirectPublishingClass::loop()
|
||||
void MqttHandleVedirectClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
@ -1,130 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "MqttPublishing.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include <ctime>
|
||||
|
||||
MqttPublishingClass MqttPublishing;
|
||||
|
||||
void MqttPublishingClass::init()
|
||||
{
|
||||
}
|
||||
|
||||
void MqttPublishingClass::loop()
|
||||
{
|
||||
if (!MqttSettings.getConnected() || !Hoymiles.getRadio()->isIdle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
|
||||
MqttSettings.publish("dtu/uptime", String(millis() / 1000));
|
||||
MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString());
|
||||
MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname());
|
||||
if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
|
||||
MqttSettings.publish("dtu/rssi", String(WiFi.RSSI()));
|
||||
}
|
||||
|
||||
// Loop all inverters
|
||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||
auto inv = Hoymiles.getInverterByPos(i);
|
||||
|
||||
String subtopic = inv->serialString();
|
||||
|
||||
// Name
|
||||
MqttSettings.publish(subtopic + "/name", inv->name());
|
||||
|
||||
if (inv->DevInfo()->getLastUpdate() > 0) {
|
||||
// Bootloader Version
|
||||
MqttSettings.publish(subtopic + "/device/bootloaderversion", String(inv->DevInfo()->getFwBootloaderVersion()));
|
||||
|
||||
// Firmware Version
|
||||
MqttSettings.publish(subtopic + "/device/fwbuildversion", String(inv->DevInfo()->getFwBuildVersion()));
|
||||
|
||||
// Firmware Build DateTime
|
||||
char timebuffer[32];
|
||||
const time_t t = inv->DevInfo()->getFwBuildDateTime();
|
||||
std::strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", gmtime(&t));
|
||||
MqttSettings.publish(subtopic + "/device/fwbuilddatetime", String(timebuffer));
|
||||
|
||||
// Hardware part number
|
||||
MqttSettings.publish(subtopic + "/device/hwpartnumber", String(inv->DevInfo()->getHwPartNumber()));
|
||||
|
||||
// Hardware version
|
||||
MqttSettings.publish(subtopic + "/device/hwversion", inv->DevInfo()->getHwVersion());
|
||||
}
|
||||
|
||||
if (inv->SystemConfigPara()->getLastUpdate() > 0) {
|
||||
// Limit
|
||||
MqttSettings.publish(subtopic + "/status/limit_relative", String(inv->SystemConfigPara()->getLimitPercent()));
|
||||
|
||||
uint16_t maxpower = inv->DevInfo()->getMaxPower();
|
||||
if (maxpower > 0) {
|
||||
MqttSettings.publish(subtopic + "/status/limit_absolute", String(inv->SystemConfigPara()->getLimitPercent() * maxpower / 100));
|
||||
}
|
||||
}
|
||||
|
||||
MqttSettings.publish(subtopic + "/status/reachable", String(inv->isReachable()));
|
||||
MqttSettings.publish(subtopic + "/status/producing", String(inv->isProducing()));
|
||||
|
||||
if (inv->Statistics()->getLastUpdate() > 0) {
|
||||
MqttSettings.publish(subtopic + "/status/last_update", String(std::time(0) - (millis() - inv->Statistics()->getLastUpdate()) / 1000));
|
||||
} else {
|
||||
MqttSettings.publish(subtopic + "/status/last_update", String(0));
|
||||
}
|
||||
|
||||
uint32_t lastUpdate = inv->Statistics()->getLastUpdate();
|
||||
if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) {
|
||||
_lastPublishStats[i] = lastUpdate;
|
||||
|
||||
// Loop all channels
|
||||
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
|
||||
if (c > 0) {
|
||||
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
|
||||
if (inv_cfg != nullptr) {
|
||||
MqttSettings.publish(inv->serialString() + "/" + String(c) + "/name", inv_cfg->channel[c - 1].Name);
|
||||
}
|
||||
}
|
||||
for (uint8_t f = 0; f < sizeof(_publishFields); f++) {
|
||||
publishField(inv, c, _publishFields[f]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
_lastPublish = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void MqttPublishingClass::publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
String topic = getTopic(inv, channel, fieldId);
|
||||
if (topic == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
MqttSettings.publish(topic, String(inv->Statistics()->getChannelFieldValue(channel, fieldId)));
|
||||
}
|
||||
|
||||
String MqttPublishingClass::getTopic(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId)
|
||||
{
|
||||
if (!inv->Statistics()->hasChannelFieldValue(channel, fieldId)) {
|
||||
return String("");
|
||||
}
|
||||
|
||||
String chanName;
|
||||
if (channel == 0 && fieldId == FLD_PDC) {
|
||||
chanName = "powerdc";
|
||||
} else {
|
||||
chanName = inv->Statistics()->getChannelFieldName(channel, fieldId);
|
||||
chanName.toLowerCase();
|
||||
}
|
||||
|
||||
return inv->serialString() + "/" + String(channel) + "/" + chanName;
|
||||
}
|
||||
@ -3,19 +3,8 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "MqttSettings.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "Configuration.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include <Hoymiles.h>
|
||||
#include <MqttClientSetup.h>
|
||||
#include <Ticker.h>
|
||||
#include <espMqttClient.h>
|
||||
|
||||
#define TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE "limit_persistent_relative"
|
||||
#define TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE "limit_persistent_absolute"
|
||||
#define TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE "limit_nonpersistent_relative"
|
||||
#define TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE "limit_nonpersistent_absolute"
|
||||
#define TOPIC_SUB_POWER "power"
|
||||
#define TOPIC_SUB_RESTART "restart"
|
||||
|
||||
MqttSettingsClass::MqttSettingsClass()
|
||||
{
|
||||
@ -25,11 +14,11 @@ void MqttSettingsClass::NetworkEvent(network_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case network_event::NETWORK_GOT_IP:
|
||||
Serial.println(F("Network connected"));
|
||||
MessageOutput.println(F("Network connected"));
|
||||
performConnect();
|
||||
break;
|
||||
case network_event::NETWORK_DISCONNECTED:
|
||||
Serial.println(F("Network lost connection"));
|
||||
MessageOutput.println(F("Network lost connection"));
|
||||
mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
|
||||
break;
|
||||
default:
|
||||
@ -39,45 +28,53 @@ void MqttSettingsClass::NetworkEvent(network_event event)
|
||||
|
||||
void MqttSettingsClass::onMqttConnect(bool sessionPresent)
|
||||
{
|
||||
Serial.println(F("Connected to MQTT."));
|
||||
MessageOutput.println(F("Connected to MQTT."));
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Online);
|
||||
|
||||
String topic = getPrefix();
|
||||
mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE).c_str(), 0);
|
||||
mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE).c_str(), 0);
|
||||
mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE).c_str(), 0);
|
||||
mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE).c_str(), 0);
|
||||
mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_POWER).c_str(), 0);
|
||||
mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_RESTART).c_str(), 0);
|
||||
for (const auto& cb : _mqttSubscribeParser.get_callbacks()) {
|
||||
mqttClient->subscribe(cb.topic.c_str(), cb.qos);
|
||||
}
|
||||
}
|
||||
|
||||
void MqttSettingsClass::subscribe(const String& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb)
|
||||
{
|
||||
_mqttSubscribeParser.register_callback(topic.c_str(), qos, cb);
|
||||
mqttClient->subscribe(topic.c_str(), qos);
|
||||
}
|
||||
|
||||
void MqttSettingsClass::unsubscribe(const String& topic)
|
||||
{
|
||||
_mqttSubscribeParser.unregister_callback(topic.c_str());
|
||||
mqttClient->unsubscribe(topic.c_str());
|
||||
}
|
||||
|
||||
void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason)
|
||||
{
|
||||
Serial.println(F("Disconnected from MQTT."));
|
||||
MessageOutput.println(F("Disconnected from MQTT."));
|
||||
|
||||
Serial.print(F("Disconnect reason:"));
|
||||
MessageOutput.print(F("Disconnect reason:"));
|
||||
switch (reason) {
|
||||
case espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED:
|
||||
Serial.println(F("TCP_DISCONNECTED"));
|
||||
MessageOutput.println(F("TCP_DISCONNECTED"));
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
|
||||
Serial.println(F("MQTT_UNACCEPTABLE_PROTOCOL_VERSION"));
|
||||
MessageOutput.println(F("MQTT_UNACCEPTABLE_PROTOCOL_VERSION"));
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_IDENTIFIER_REJECTED:
|
||||
Serial.println(F("MQTT_IDENTIFIER_REJECTED"));
|
||||
MessageOutput.println(F("MQTT_IDENTIFIER_REJECTED"));
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_SERVER_UNAVAILABLE:
|
||||
Serial.println(F("MQTT_SERVER_UNAVAILABLE"));
|
||||
MessageOutput.println(F("MQTT_SERVER_UNAVAILABLE"));
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_MALFORMED_CREDENTIALS:
|
||||
Serial.println(F("MQTT_MALFORMED_CREDENTIALS"));
|
||||
MessageOutput.println(F("MQTT_MALFORMED_CREDENTIALS"));
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_NOT_AUTHORIZED:
|
||||
Serial.println(F("MQTT_NOT_AUTHORIZED"));
|
||||
MessageOutput.println(F("MQTT_NOT_AUTHORIZED"));
|
||||
break;
|
||||
default:
|
||||
Serial.println(F("Unknown"));
|
||||
MessageOutput.println(F("Unknown"));
|
||||
}
|
||||
mqttReconnectTimer.once(
|
||||
2, +[](MqttSettingsClass* instance) { instance->performConnect(); }, this);
|
||||
@ -85,90 +82,10 @@ void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason re
|
||||
|
||||
void MqttSettingsClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
MessageOutput.print(F("Received MQTT message on topic: "));
|
||||
MessageOutput.println(topic);
|
||||
|
||||
Serial.print(F("Received MQTT message on topic: "));
|
||||
Serial.println(topic);
|
||||
|
||||
char token_topic[MQTT_MAX_TOPIC_STRLEN + 40]; // respect all subtopics
|
||||
strncpy(token_topic, topic, MQTT_MAX_TOPIC_STRLEN + 40); // convert const char* to char*
|
||||
|
||||
char* serial_str;
|
||||
char* subtopic;
|
||||
char* setting;
|
||||
char* rest = &token_topic[strlen(config.Mqtt_Topic)];
|
||||
|
||||
serial_str = strtok_r(rest, "/", &rest);
|
||||
subtopic = strtok_r(rest, "/", &rest);
|
||||
setting = strtok_r(rest, "/", &rest);
|
||||
|
||||
if (serial_str == NULL || subtopic == NULL || setting == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t serial;
|
||||
serial = strtoull(serial_str, 0, 16);
|
||||
|
||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||
|
||||
if (inv == nullptr) {
|
||||
Serial.println(F("Inverter not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
// check if subtopic is unequal cmd
|
||||
if (strcmp(subtopic, "cmd")) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* strlimit = new char[len + 1];
|
||||
memcpy(strlimit, payload, len);
|
||||
strlimit[len] = '\0';
|
||||
uint32_t payload_val = strtol(strlimit, NULL, 10);
|
||||
delete[] strlimit;
|
||||
|
||||
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
||||
// Set inverter limit relative persistent
|
||||
Serial.printf("Limit Persistent: %d %%\n", payload_val);
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativPersistent);
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
||||
// Set inverter limit absolute persistent
|
||||
Serial.printf("Limit Persistent: %d W\n", payload_val);
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutPersistent);
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
||||
// Set inverter limit relative non persistent
|
||||
Serial.printf("Limit Non-Persistent: %d %%\n", payload_val);
|
||||
if (!properties.retain) {
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativNonPersistent);
|
||||
} else {
|
||||
Serial.println("Ignored because retained");
|
||||
}
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
||||
// Set inverter limit absolute non persistent
|
||||
Serial.printf("Limit Non-Persistent: %d W\n", payload_val);
|
||||
if (!properties.retain) {
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutNonPersistent);
|
||||
} else {
|
||||
Serial.println("Ignored because retained");
|
||||
}
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
||||
// Turn inverter on or off
|
||||
Serial.printf("Set inverter power to: %d\n", payload_val);
|
||||
inv->sendPowerControlRequest(Hoymiles.getRadio(), payload_val > 0);
|
||||
|
||||
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
||||
// Restart inverter
|
||||
Serial.printf("Restart inverter\n");
|
||||
if (!properties.retain && payload_val == 1) {
|
||||
inv->sendRestartControlRequest(Hoymiles.getRadio());
|
||||
} else {
|
||||
Serial.println("Ignored because retained");
|
||||
}
|
||||
}
|
||||
_mqttSubscribeParser.handle_message(properties, topic, payload, len, index, total);
|
||||
}
|
||||
|
||||
void MqttSettingsClass::performConnect()
|
||||
@ -180,7 +97,7 @@ void MqttSettingsClass::performConnect()
|
||||
using std::placeholders::_4;
|
||||
using std::placeholders::_5;
|
||||
using std::placeholders::_6;
|
||||
Serial.println(F("Connecting to MQTT..."));
|
||||
MessageOutput.println(F("Connecting to MQTT..."));
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
willTopic = getPrefix() + config.Mqtt_LwtTopic;
|
||||
clientId = NetworkSettings.getApName();
|
||||
@ -240,11 +157,9 @@ void MqttSettingsClass::publish(const String& subtopic, const String& payload)
|
||||
mqttClient->publish(topic.c_str(), 0, Configuration.get().Mqtt_Retain, payload.c_str());
|
||||
}
|
||||
|
||||
void MqttSettingsClass::publishHass(const String& subtopic, const String& payload)
|
||||
void MqttSettingsClass::publishGeneric(const String& topic, const String& payload, bool retain, uint8_t qos)
|
||||
{
|
||||
String topic = Configuration.get().Mqtt_Hass_Topic;
|
||||
topic += subtopic;
|
||||
mqttClient->publish(topic.c_str(), 0, Configuration.get().Mqtt_Hass_Retain, payload.c_str());
|
||||
mqttClient->publish(topic.c_str(), qos, retain, payload.c_str());
|
||||
}
|
||||
|
||||
void MqttSettingsClass::init()
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
*/
|
||||
#include "NetworkSettings.h"
|
||||
#include "Configuration.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "Utils.h"
|
||||
#include "defaults.h"
|
||||
#include <WiFi.h>
|
||||
#ifdef OPENDTU_ETHERNET
|
||||
#include <ETH.h>
|
||||
#endif
|
||||
@ -31,30 +31,30 @@ void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
||||
switch (event) {
|
||||
#ifdef OPENDTU_ETHERNET
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
Serial.println(F("ETH start"));
|
||||
MessageOutput.println(F("ETH start"));
|
||||
if (_networkMode == network_mode::Ethernet) {
|
||||
raiseEvent(network_event::NETWORK_START);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
Serial.println(F("ETH stop"));
|
||||
MessageOutput.println(F("ETH stop"));
|
||||
if (_networkMode == network_mode::Ethernet) {
|
||||
raiseEvent(network_event::NETWORK_STOP);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
Serial.println(F("ETH connected"));
|
||||
MessageOutput.println(F("ETH connected"));
|
||||
_ethConnected = true;
|
||||
raiseEvent(network_event::NETWORK_CONNECTED);
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
Serial.printf("ETH got IP: %s\n", ETH.localIP().toString().c_str());
|
||||
MessageOutput.printf("ETH got IP: %s\n", ETH.localIP().toString().c_str());
|
||||
if (_networkMode == network_mode::Ethernet) {
|
||||
raiseEvent(network_event::NETWORK_GOT_IP);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
Serial.println(F("ETH disconnected"));
|
||||
MessageOutput.println(F("ETH disconnected"));
|
||||
_ethConnected = false;
|
||||
if (_networkMode == network_mode::Ethernet) {
|
||||
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
||||
@ -62,21 +62,21 @@ void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
||||
break;
|
||||
#endif
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
Serial.println(F("WiFi connected"));
|
||||
MessageOutput.println(F("WiFi connected"));
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
raiseEvent(network_event::NETWORK_CONNECTED);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
Serial.println(F("WiFi disconnected"));
|
||||
MessageOutput.println(F("WiFi disconnected"));
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
Serial.println(F("Try reconnecting"));
|
||||
MessageOutput.println(F("Try reconnecting"));
|
||||
WiFi.reconnect();
|
||||
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
Serial.printf("WiFi got ip: %s\n", WiFi.localIP().toString().c_str());
|
||||
MessageOutput.printf("WiFi got ip: %s\n", WiFi.localIP().toString().c_str());
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
raiseEvent(network_event::NETWORK_GOT_IP);
|
||||
}
|
||||
@ -152,7 +152,7 @@ void NetworkSettingsClass::loop()
|
||||
if (_ethConnected) {
|
||||
if (_networkMode != network_mode::Ethernet) {
|
||||
// Do stuff when switching to Ethernet mode
|
||||
Serial.println(F("Switch to Ethernet mode"));
|
||||
MessageOutput.println(F("Switch to Ethernet mode"));
|
||||
_networkMode = network_mode::Ethernet;
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
setStaticIp();
|
||||
@ -162,7 +162,7 @@ void NetworkSettingsClass::loop()
|
||||
#endif
|
||||
if (_networkMode != network_mode::WiFi) {
|
||||
// Do stuff when switching to Ethernet mode
|
||||
Serial.println(F("Switch to WiFi mode"));
|
||||
MessageOutput.println(F("Switch to WiFi mode"));
|
||||
_networkMode = network_mode::WiFi;
|
||||
enableAdminMode();
|
||||
applyConfig();
|
||||
@ -183,7 +183,7 @@ void NetworkSettingsClass::loop()
|
||||
// seconds, disable the internal Access Point
|
||||
if (adminTimeoutCounter > ADMIN_TIMEOUT) {
|
||||
adminEnabled = false;
|
||||
Serial.println(F("Admin mode disabled"));
|
||||
MessageOutput.println(F("Admin mode disabled"));
|
||||
setupMode();
|
||||
}
|
||||
// It's nearly not possible to use the internal AP if the
|
||||
@ -194,16 +194,16 @@ void NetworkSettingsClass::loop()
|
||||
connectRedoTimer = 0;
|
||||
} else {
|
||||
if (connectTimeoutTimer > WIFI_RECONNECT_TIMEOUT && !forceDisconnection) {
|
||||
Serial.print(F("Disable search for AP... "));
|
||||
MessageOutput.print(F("Disable search for AP... "));
|
||||
WiFi.mode(WIFI_AP);
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
connectRedoTimer = 0;
|
||||
forceDisconnection = true;
|
||||
}
|
||||
if (connectRedoTimer > WIFI_RECONNECT_REDO_TIMEOUT && forceDisconnection) {
|
||||
Serial.print(F("Enable search for AP... "));
|
||||
MessageOutput.print(F("Enable search for AP... "));
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
applyConfig();
|
||||
connectTimeoutTimer = 0;
|
||||
forceDisconnection = false;
|
||||
@ -221,28 +221,28 @@ void NetworkSettingsClass::applyConfig()
|
||||
if (!strcmp(Configuration.get().WiFi_Ssid, "")) {
|
||||
return;
|
||||
}
|
||||
Serial.print(F("Configuring WiFi STA using "));
|
||||
MessageOutput.print(F("Configuring WiFi STA using "));
|
||||
if (strcmp(WiFi.SSID().c_str(), Configuration.get().WiFi_Ssid) || strcmp(WiFi.psk().c_str(), Configuration.get().WiFi_Password)) {
|
||||
Serial.print(F("new credentials... "));
|
||||
MessageOutput.print(F("new credentials... "));
|
||||
WiFi.begin(
|
||||
Configuration.get().WiFi_Ssid,
|
||||
Configuration.get().WiFi_Password);
|
||||
} else {
|
||||
Serial.print(F("existing credentials... "));
|
||||
MessageOutput.print(F("existing credentials... "));
|
||||
WiFi.begin();
|
||||
}
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
setStaticIp();
|
||||
}
|
||||
|
||||
void NetworkSettingsClass::setHostname()
|
||||
{
|
||||
Serial.print(F("Setting Hostname... "));
|
||||
MessageOutput.print(F("Setting Hostname... "));
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
if (WiFi.hostname(getHostname())) {
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
} else {
|
||||
Serial.println(F("failed"));
|
||||
MessageOutput.println(F("failed"));
|
||||
}
|
||||
|
||||
// Evil bad hack to get the hostname set up correctly
|
||||
@ -253,9 +253,9 @@ void NetworkSettingsClass::setHostname()
|
||||
#ifdef OPENDTU_ETHERNET
|
||||
else if (_networkMode == network_mode::Ethernet) {
|
||||
if (ETH.setHostname(getHostname().c_str())) {
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
} else {
|
||||
Serial.println(F("failed"));
|
||||
MessageOutput.println(F("failed"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -265,35 +265,35 @@ void NetworkSettingsClass::setStaticIp()
|
||||
{
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
if (Configuration.get().WiFi_Dhcp) {
|
||||
Serial.print(F("Configuring WiFi STA DHCP IP... "));
|
||||
MessageOutput.print(F("Configuring WiFi STA DHCP IP... "));
|
||||
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
} else {
|
||||
Serial.print(F("Configuring WiFi STA static IP... "));
|
||||
MessageOutput.print(F("Configuring WiFi STA static IP... "));
|
||||
WiFi.config(
|
||||
IPAddress(Configuration.get().WiFi_Ip),
|
||||
IPAddress(Configuration.get().WiFi_Gateway),
|
||||
IPAddress(Configuration.get().WiFi_Netmask),
|
||||
IPAddress(Configuration.get().WiFi_Dns1),
|
||||
IPAddress(Configuration.get().WiFi_Dns2));
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
}
|
||||
}
|
||||
#ifdef OPENDTU_ETHERNET
|
||||
else if (_networkMode == network_mode::Ethernet) {
|
||||
if (Configuration.get().WiFi_Dhcp) {
|
||||
Serial.print(F("Configuring Ethernet DHCP IP... "));
|
||||
MessageOutput.print(F("Configuring Ethernet DHCP IP... "));
|
||||
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
} else {
|
||||
Serial.print(F("Configuring Ethernet static IP... "));
|
||||
MessageOutput.print(F("Configuring Ethernet static IP... "));
|
||||
ETH.config(
|
||||
IPAddress(Configuration.get().WiFi_Ip),
|
||||
IPAddress(Configuration.get().WiFi_Gateway),
|
||||
IPAddress(Configuration.get().WiFi_Netmask),
|
||||
IPAddress(Configuration.get().WiFi_Dns1),
|
||||
IPAddress(Configuration.get().WiFi_Dns2));
|
||||
Serial.println(F("done"));
|
||||
MessageOutput.println(F("done"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -3,10 +3,9 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "defaults.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
WebApiClass::WebApiClass()
|
||||
: _server(HTTP_PORT)
|
||||
@ -34,6 +33,7 @@ void WebApiClass::init()
|
||||
_webApiSecurity.init(&_server);
|
||||
_webApiSysstatus.init(&_server);
|
||||
_webApiWebapp.init(&_server);
|
||||
_webApiWsConsole.init(&_server);
|
||||
_webApiWsLive.init(&_server);
|
||||
_webApiWsVedirectLive.init(&_server);
|
||||
_webApiVedirect.init(&_server);
|
||||
@ -58,6 +58,7 @@ void WebApiClass::loop()
|
||||
_webApiSecurity.loop();
|
||||
_webApiSysstatus.loop();
|
||||
_webApiWebapp.loop();
|
||||
_webApiWsConsole.loop();
|
||||
_webApiWsLive.loop();
|
||||
_webApiWsVedirectLive.loop();
|
||||
_webApiVedirect.loop();
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_config.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include <AsyncJson.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
void WebApiConfigClass::init(AsyncWebServer* server)
|
||||
@ -52,6 +52,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -61,6 +62,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -71,6 +73,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -78,6 +81,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("delete"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -85,6 +89,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("delete")].as<bool>() == false) {
|
||||
retMsg[F("message")] = F("Not deleted anything!");
|
||||
retMsg[F("code")] = WebApiError::ConfigNotDeleted;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -92,6 +97,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Configuration resettet. Rebooting now...");
|
||||
retMsg[F("code")] = WebApiError::ConfigSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -3,10 +3,9 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_devinfo.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Hoymiles.h"
|
||||
#include "WebApi.h"
|
||||
#include <AsyncJson.h>
|
||||
#include <Hoymiles.h>
|
||||
#include <ctime>
|
||||
|
||||
void WebApiDevInfoClass::init(AsyncWebServer* server)
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_dtu.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "Hoymiles.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include <AsyncJson.h>
|
||||
#include <Hoymiles.h>
|
||||
|
||||
void WebApiDtuClass::init(AsyncWebServer* server)
|
||||
{
|
||||
@ -58,6 +58,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -67,6 +68,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -77,6 +79,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -84,6 +87,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("dtu_serial") && root.containsKey("dtu_pollinterval") && root.containsKey("dtu_palevel"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -91,6 +95,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("dtu_serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial cannot be zero!");
|
||||
retMsg[F("code")] = WebApiError::DtuSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -98,6 +103,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("dtu_pollinterval")].as<uint32_t>() == 0) {
|
||||
retMsg[F("message")] = F("Poll interval must be greater zero!");
|
||||
retMsg[F("code")] = WebApiError::DtuPollZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -105,6 +111,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("dtu_palevel")].as<uint8_t>() > 3) {
|
||||
retMsg[F("message")] = F("Invalid power level setting!");
|
||||
retMsg[F("code")] = WebApiError::DtuInvalidPowerLevel;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -120,6 +127,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -3,10 +3,9 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_eventlog.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Hoymiles.h"
|
||||
#include "WebApi.h"
|
||||
#include <AsyncJson.h>
|
||||
#include <Hoymiles.h>
|
||||
|
||||
void WebApiEventlogClass::init(AsyncWebServer* server)
|
||||
{
|
||||
|
||||
@ -3,12 +3,11 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_firmware.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "Update.h"
|
||||
#include "WebApi.h"
|
||||
#include "helper.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
void WebApiFirmwareClass::init(AsyncWebServer* server)
|
||||
{
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_inverter.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "Hoymiles.h"
|
||||
#include "MqttHassPublishing.h"
|
||||
#include "MqttHandleHass.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include "helper.h"
|
||||
#include <AsyncJson.h>
|
||||
#include <Hoymiles.h>
|
||||
|
||||
void WebApiInverterClass::init(AsyncWebServer* server)
|
||||
{
|
||||
@ -87,6 +87,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -96,6 +97,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -106,6 +108,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -113,6 +116,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("serial") && root.containsKey("name"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -120,6 +124,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial must be a number > 0!");
|
||||
retMsg[F("code")] = WebApiError::InverterSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -127,6 +132,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("name")].as<String>().length() == 0 || root[F("name")].as<String>().length() > INV_MAX_NAME_STRLEN) {
|
||||
retMsg[F("message")] = F("Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!");
|
||||
retMsg[F("code")] = WebApiError::InverterNameLength;
|
||||
retMsg[F("param")][F("max")] = INV_MAX_NAME_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -136,6 +143,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
if (!inverter) {
|
||||
retMsg[F("message")] = F("Only " STR(INV_MAX_COUNT) " inverters are supported!");
|
||||
retMsg[F("code")] = WebApiError::InverterCount;
|
||||
retMsg[F("param")][F("max")] = INV_MAX_COUNT;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -149,6 +158,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Inverter created!");
|
||||
retMsg[F("code")] = WebApiError::InverterAdded;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -161,7 +171,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
}
|
||||
}
|
||||
|
||||
MqttHassPublishing.forceUpdate();
|
||||
MqttHandleHass.forceUpdate();
|
||||
}
|
||||
|
||||
void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
@ -176,6 +186,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -185,6 +196,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -195,6 +207,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -202,6 +215,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("channel"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -209,6 +223,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("id")].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
||||
retMsg[F("message")] = F("Invalid ID specified!");
|
||||
retMsg[F("code")] = WebApiError::InverterInvalidId;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -216,6 +231,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial must be a number > 0!");
|
||||
retMsg[F("code")] = WebApiError::InverterSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -223,6 +239,8 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("name")].as<String>().length() == 0 || root[F("name")].as<String>().length() > INV_MAX_NAME_STRLEN) {
|
||||
retMsg[F("message")] = F("Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!");
|
||||
retMsg[F("code")] = WebApiError::InverterNameLength;
|
||||
retMsg[F("param")][F("max")] = INV_MAX_NAME_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -231,6 +249,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
JsonArray channelArray = root[F("channel")].as<JsonArray>();
|
||||
if (channelArray.size() == 0 || channelArray.size() > INV_MAX_CHAN_COUNT) {
|
||||
retMsg[F("message")] = F("Invalid amount of max channel setting given!");
|
||||
retMsg[F("code")] = WebApiError::InverterInvalidMaxChannel;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -255,6 +274,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("code")] = WebApiError::InverterChanged;
|
||||
retMsg[F("message")] = F("Inverter changed!");
|
||||
|
||||
response->setLength();
|
||||
@ -280,7 +300,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
}
|
||||
}
|
||||
|
||||
MqttHassPublishing.forceUpdate();
|
||||
MqttHandleHass.forceUpdate();
|
||||
}
|
||||
|
||||
void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
@ -295,6 +315,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -304,6 +325,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -314,6 +336,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -321,6 +344,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("id"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -328,6 +352,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("id")].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
||||
retMsg[F("message")] = F("Invalid ID specified!");
|
||||
retMsg[F("code")] = WebApiError::InverterInvalidId;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -344,9 +369,10 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Inverter deleted!");
|
||||
retMsg[F("code")] = WebApiError::InverterDeleted;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
MqttHassPublishing.forceUpdate();
|
||||
MqttHandleHass.forceUpdate();
|
||||
}
|
||||
@ -3,10 +3,10 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_limit.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Hoymiles.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include <AsyncJson.h>
|
||||
#include <Hoymiles.h>
|
||||
|
||||
void WebApiLimitClass::init(AsyncWebServer* server)
|
||||
{
|
||||
@ -43,11 +43,9 @@ void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request)
|
||||
String limitStatus = "Unknown";
|
||||
if (status == LastCommandSuccess::CMD_OK) {
|
||||
limitStatus = "Ok";
|
||||
}
|
||||
else if (status == LastCommandSuccess::CMD_NOK) {
|
||||
} else if (status == LastCommandSuccess::CMD_NOK) {
|
||||
limitStatus = "Failure";
|
||||
}
|
||||
else if (status == LastCommandSuccess::CMD_PENDING) {
|
||||
} else if (status == LastCommandSuccess::CMD_PENDING) {
|
||||
limitStatus = "Pending";
|
||||
}
|
||||
root[serial]["limit_set_status"] = limitStatus;
|
||||
@ -69,6 +67,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -78,6 +77,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -88,6 +88,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -97,6 +98,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
&& root.containsKey("limit_value")
|
||||
&& root.containsKey("limit_type"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -104,6 +106,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial must be a number > 0!");
|
||||
retMsg[F("code")] = WebApiError::LimitSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -111,6 +114,8 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("limit_value")].as<uint16_t>() == 0 || root[F("limit_value")].as<uint16_t>() > 1500) {
|
||||
retMsg[F("message")] = F("Limit must between 1 and 1500!");
|
||||
retMsg[F("code")] = WebApiError::LimitInvalidLimit;
|
||||
retMsg[F("param")][F("max")] = 1500;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -122,6 +127,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|| (root[F("limit_type")].as<uint16_t>() == PowerLimitControlType::RelativPersistent))) {
|
||||
|
||||
retMsg[F("message")] = F("Invalid type specified!");
|
||||
retMsg[F("code")] = WebApiError::LimitInvalidType;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -134,6 +140,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||
if (inv == nullptr) {
|
||||
retMsg[F("message")] = F("Invalid inverter specified!");
|
||||
retMsg[F("code")] = WebApiError::LimitInvalidInverter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -143,6 +150,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -4,8 +4,9 @@
|
||||
*/
|
||||
|
||||
#include "WebApi_maintenance.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
void WebApiMaintenanceClass::init(AsyncWebServer* server)
|
||||
{
|
||||
@ -32,6 +33,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -41,6 +43,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -51,6 +54,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -58,6 +62,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("reboot"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -66,6 +71,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
if (root[F("reboot")].as<bool>()) {
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Reboot triggered!");
|
||||
retMsg[F("code")] = WebApiError::MaintenanceRebootTriggered;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -75,6 +81,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
ESP.restart();
|
||||
} else {
|
||||
retMsg[F("message")] = F("Reboot cancled!");
|
||||
retMsg[F("code")] = WebApiError::MaintenanceRebootCancled;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_mqtt.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttHassPublishing.h"
|
||||
#include "MqttHandleHass.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include "helper.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
void WebApiMqttClass::init(AsyncWebServer* server)
|
||||
{
|
||||
@ -102,6 +102,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -111,6 +112,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -121,6 +123,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -144,6 +147,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
&& root.containsKey("mqtt_hass_topic")
|
||||
&& root.containsKey("mqtt_hass_individualpanels"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -152,6 +156,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
if (root[F("mqtt_enabled")].as<bool>()) {
|
||||
if (root[F("mqtt_hostname")].as<String>().length() == 0 || root[F("mqtt_hostname")].as<String>().length() > MQTT_MAX_HOSTNAME_STRLEN) {
|
||||
retMsg[F("message")] = F("MqTT Server must between 1 and " STR(MQTT_MAX_HOSTNAME_STRLEN) " characters long!");
|
||||
retMsg[F("code")] = WebApiError::MqttHostnameLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_HOSTNAME_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -159,18 +165,24 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_username")].as<String>().length() > MQTT_MAX_USERNAME_STRLEN) {
|
||||
retMsg[F("message")] = F("Username must not longer then " STR(MQTT_MAX_USERNAME_STRLEN) " characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttUsernameLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_USERNAME_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
if (root[F("mqtt_password")].as<String>().length() > MQTT_MAX_PASSWORD_STRLEN) {
|
||||
retMsg[F("message")] = F("Password must not longer then " STR(MQTT_MAX_PASSWORD_STRLEN) " characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttPasswordLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_PASSWORD_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
if (root[F("mqtt_topic")].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
|
||||
retMsg[F("message")] = F("Topic must not longer then " STR(MQTT_MAX_TOPIC_STRLEN) " characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttTopicLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_TOPIC_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -178,6 +190,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_topic")].as<String>().indexOf(' ') != -1) {
|
||||
retMsg[F("message")] = F("Topic must not contain space characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttTopicCharacter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -185,6 +198,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!root[F("mqtt_topic")].as<String>().endsWith("/")) {
|
||||
retMsg[F("message")] = F("Topic must end with slash (/)!");
|
||||
retMsg[F("code")] = WebApiError::MqttTopicTrailingSlash;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -192,6 +206,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_port")].as<uint>() == 0 || root[F("mqtt_port")].as<uint>() > 65535) {
|
||||
retMsg[F("message")] = F("Port must be a number between 1 and 65535!");
|
||||
retMsg[F("code")] = WebApiError::MqttPort;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -199,6 +214,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_root_ca_cert")].as<String>().length() > MQTT_MAX_ROOT_CA_CERT_STRLEN) {
|
||||
retMsg[F("message")] = F("Certificate must not longer then " STR(MQTT_MAX_ROOT_CA_CERT_STRLEN) " characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttCertificateLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_ROOT_CA_CERT_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -206,6 +223,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_lwt_topic")].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
|
||||
retMsg[F("message")] = F("LWT topic must not longer then " STR(MQTT_MAX_TOPIC_STRLEN) " characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttLwtTopicLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_TOPIC_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -213,6 +232,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_lwt_topic")].as<String>().indexOf(' ') != -1) {
|
||||
retMsg[F("message")] = F("LWT topic must not contain space characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttLwtTopicCharacter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -220,6 +240,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_lwt_online")].as<String>().length() > MQTT_MAX_LWTVALUE_STRLEN) {
|
||||
retMsg[F("message")] = F("LWT online value must not longer then " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttLwtOnlineLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_LWTVALUE_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -227,6 +249,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_lwt_offline")].as<String>().length() > MQTT_MAX_LWTVALUE_STRLEN) {
|
||||
retMsg[F("message")] = F("LWT offline value must not longer then " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttLwtOfflineLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_LWTVALUE_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -234,6 +258,9 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_publish_interval")].as<uint32_t>() < 5 || root[F("mqtt_publish_interval")].as<uint32_t>() > 65535) {
|
||||
retMsg[F("message")] = F("Publish interval must be a number between 5 and 65535!");
|
||||
retMsg[F("code")] = WebApiError::MqttPublishInterval;
|
||||
retMsg[F("param")][F("min")] = 5;
|
||||
retMsg[F("param")][F("max")] = 65535;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -242,6 +269,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
if (root[F("mqtt_hass_enabled")].as<bool>()) {
|
||||
if (root[F("mqtt_hass_topic")].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
|
||||
retMsg[F("message")] = F("Hass topic must not longer then " STR(MQTT_MAX_TOPIC_STRLEN) " characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttHassTopicLength;
|
||||
retMsg[F("param")][F("max")] = MQTT_MAX_TOPIC_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -249,6 +278,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("mqtt_hass_topic")].as<String>().indexOf(' ') != -1) {
|
||||
retMsg[F("message")] = F("Hass topic must not contain space characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttHassTopicCharacter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -279,12 +309,13 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
MqttSettings.performReconnect();
|
||||
MqttHassPublishing.forceUpdate();
|
||||
MqttHandleHass.forceUpdate();
|
||||
}
|
||||
|
||||
String WebApiMqttClass::getRootCaCertInfo(const char* cert)
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_network.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include "helper.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
void WebApiNetworkClass::init(AsyncWebServer* server)
|
||||
{
|
||||
@ -91,6 +91,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -100,6 +101,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -110,6 +112,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -117,6 +120,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("ssid") && root.containsKey("password") && root.containsKey("hostname") && root.containsKey("dhcp") && root.containsKey("ipaddress") && root.containsKey("netmask") && root.containsKey("gateway") && root.containsKey("dns1") && root.containsKey("dns2"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -125,6 +129,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
IPAddress ipaddress;
|
||||
if (!ipaddress.fromString(root[F("ipaddress")].as<String>())) {
|
||||
retMsg[F("message")] = F("IP address is invalid!");
|
||||
retMsg[F("code")] = WebApiError::NetworkIpInvalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -132,6 +137,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
IPAddress netmask;
|
||||
if (!netmask.fromString(root[F("netmask")].as<String>())) {
|
||||
retMsg[F("message")] = F("Netmask is invalid!");
|
||||
retMsg[F("code")] = WebApiError::NetworkNetmaskInvalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -139,6 +145,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
IPAddress gateway;
|
||||
if (!gateway.fromString(root[F("gateway")].as<String>())) {
|
||||
retMsg[F("message")] = F("Gateway is invalid!");
|
||||
retMsg[F("code")] = WebApiError::NetworkGatewayInvalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -146,6 +153,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
IPAddress dns1;
|
||||
if (!dns1.fromString(root[F("dns1")].as<String>())) {
|
||||
retMsg[F("message")] = F("DNS Server IP 1 is invalid!");
|
||||
retMsg[F("code")] = WebApiError::NetworkDns1Invalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -153,6 +161,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
IPAddress dns2;
|
||||
if (!dns2.fromString(root[F("dns2")].as<String>())) {
|
||||
retMsg[F("message")] = F("DNS Server IP 2 is invalid!");
|
||||
retMsg[F("code")] = WebApiError::NetworkDns2Invalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -212,6 +221,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_ntp.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "NtpSettings.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include "helper.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
void WebApiNtpClass::init(AsyncWebServer* server)
|
||||
{
|
||||
@ -85,6 +85,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -94,6 +95,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -104,6 +106,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -111,6 +114,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("ntp_server") && root.containsKey("ntp_timezone"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -118,6 +122,8 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("ntp_server")].as<String>().length() == 0 || root[F("ntp_server")].as<String>().length() > NTP_MAX_SERVER_STRLEN) {
|
||||
retMsg[F("message")] = F("NTP Server must between 1 and " STR(NTP_MAX_SERVER_STRLEN) " characters long!");
|
||||
retMsg[F("code")] = WebApiError::NtpServerLength;
|
||||
retMsg[F("param")][F("max")] = NTP_MAX_SERVER_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -125,6 +131,8 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("ntp_timezone")].as<String>().length() == 0 || root[F("ntp_timezone")].as<String>().length() > NTP_MAX_TIMEZONE_STRLEN) {
|
||||
retMsg[F("message")] = F("Timezone must between 1 and " STR(NTP_MAX_TIMEZONE_STRLEN) " characters long!");
|
||||
retMsg[F("code")] = WebApiError::NtpTimezoneLength;
|
||||
retMsg[F("param")][F("max")] = NTP_MAX_TIMEZONE_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -132,6 +140,8 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("ntp_timezone_descr")].as<String>().length() == 0 || root[F("ntp_timezone_descr")].as<String>().length() > NTP_MAX_TIMEZONEDESCR_STRLEN) {
|
||||
retMsg[F("message")] = F("Timezone description must between 1 and " STR(NTP_MAX_TIMEZONEDESCR_STRLEN) " characters long!");
|
||||
retMsg[F("code")] = WebApiError::NtpTimezoneDescriptionLength;
|
||||
retMsg[F("param")][F("max")] = NTP_MAX_TIMEZONEDESCR_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -145,6 +155,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -192,6 +203,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -201,6 +213,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -211,6 +224,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -223,13 +237,17 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
&& root.containsKey("minute")
|
||||
&& root.containsKey("second"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("year")].as<uint>() < 2022 || root[F("year")].as<uint>() > 2100) {
|
||||
retMsg[F("message")] = F("Year must be a number between 1 and 2100!");
|
||||
retMsg[F("message")] = F("Year must be a number between 2022 and 2100!");
|
||||
retMsg[F("code")] = WebApiError::NtpYearInvalid;
|
||||
retMsg[F("param")][F("min")] = 2022;
|
||||
retMsg[F("param")][F("max")] = 2100;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -237,6 +255,9 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("month")].as<uint>() < 1 || root[F("month")].as<uint>() > 12) {
|
||||
retMsg[F("message")] = F("Month must be a number between 1 and 12!");
|
||||
retMsg[F("code")] = WebApiError::NtpMonthInvalid;
|
||||
retMsg[F("param")][F("min")] = 1;
|
||||
retMsg[F("param")][F("max")] = 12;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -244,6 +265,9 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("day")].as<uint>() < 1 || root[F("day")].as<uint>() > 31) {
|
||||
retMsg[F("message")] = F("Day must be a number between 1 and 31!");
|
||||
retMsg[F("code")] = WebApiError::NtpDayInvalid;
|
||||
retMsg[F("param")][F("min")] = 1;
|
||||
retMsg[F("param")][F("max")] = 31;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -251,6 +275,9 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("hour")].as<uint>() > 23) {
|
||||
retMsg[F("message")] = F("Hour must be a number between 0 and 23!");
|
||||
retMsg[F("code")] = WebApiError::NtpHourInvalid;
|
||||
retMsg[F("param")][F("min")] = 0;
|
||||
retMsg[F("param")][F("max")] = 23;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -258,6 +285,9 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("minute")].as<uint>() > 59) {
|
||||
retMsg[F("message")] = F("Minute must be a number between 0 and 59!");
|
||||
retMsg[F("code")] = WebApiError::NtpMinuteInvalid;
|
||||
retMsg[F("param")][F("min")] = 0;
|
||||
retMsg[F("param")][F("max")] = 59;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -265,6 +295,9 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
if (root[F("second")].as<uint>() > 59) {
|
||||
retMsg[F("message")] = F("Second must be a number between 0 and 59!");
|
||||
retMsg[F("code")] = WebApiError::NtpSecondInvalid;
|
||||
retMsg[F("param")][F("min")] = 0;
|
||||
retMsg[F("param")][F("max")] = 59;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -285,6 +318,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Time updated!");
|
||||
retMsg[F("code")] = WebApiError::NtpTimeUpdated;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user