merging
This commit is contained in:
commit
cb6b98499a
48
.github/workflows/build.yml
vendored
48
.github/workflows/build.yml
vendored
@ -107,31 +107,43 @@ jobs:
|
||||
.pio/build/${{ matrix.environment }}/bootloader.bin
|
||||
.pio/build/${{ matrix.environment }}/boot_app0.bin
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
name: opendtu-release
|
||||
path: |
|
||||
.pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin
|
||||
.pio/build/${{ matrix.environment }}/partitions.bin
|
||||
.pio/build/${{ matrix.environment }}/bootloader_dio_40m.bin
|
||||
.pio/build/${{ matrix.environment }}/boot_app0.bin
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get_default_envs, build]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: opendtu-release
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@v1
|
||||
- name: Build Changelog
|
||||
id: github_release
|
||||
uses: mikepenz/release-changelog-builder-action@v3.7.0
|
||||
with:
|
||||
draft: True
|
||||
files: |
|
||||
*.bin
|
||||
failOnError: true
|
||||
commitMode: true
|
||||
configuration: ".github/workflows/config/release-notes-config.json"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: artifacts/
|
||||
|
||||
- name: Create ZIPs
|
||||
run: |
|
||||
ls -R
|
||||
sudo apt install zip
|
||||
cd artifacts
|
||||
for i in */; do zip -r "${i%/}.zip" "$i"; done
|
||||
for i in */; do cp ${i}opendtu-*.bin ./; done
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body: ${{steps.github_release.outputs.changelog}}
|
||||
draft: False
|
||||
files: |
|
||||
artifacts/*.zip, artifacts/*.bin
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
44
.github/workflows/config/release-notes-config.json
vendored
Normal file
44
.github/workflows/config/release-notes-config.json
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"title": "## ⚡ Breaking Changes",
|
||||
"labels": [
|
||||
"breaking change"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "## 🚀 Features",
|
||||
"labels": [
|
||||
"feature"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "## 🐛 Fixes",
|
||||
"labels": [
|
||||
"fix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "## 📚 Documentation",
|
||||
"labels": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "## 🛠 Under the hood",
|
||||
"labels": []
|
||||
}
|
||||
],
|
||||
"template": "${{CHANGELOG}}",
|
||||
"pr_template": "- [${{TITLE}}](https://github.com/tbnobody/OpenDTU/commit/${{MERGE_SHA}})",
|
||||
"empty_template": "- no changes",
|
||||
"label_extractor": [
|
||||
{
|
||||
"pattern": "(.): (.+)",
|
||||
"target": "$1"
|
||||
}
|
||||
],
|
||||
"tag_resolver": {
|
||||
"method": "sort"
|
||||
}
|
||||
}
|
||||
2
.github/workflows/cpplint.yml
vendored
2
.github/workflows/cpplint.yml
vendored
@ -18,4 +18,4 @@ jobs:
|
||||
pip install cpplint
|
||||
- name: Linting
|
||||
run: |
|
||||
cpplint --repository=. --recursive --filter=-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles
|
||||
cpplint --repository=. --recursive --filter=-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason
|
||||
|
||||
22
.github/workflows/yarnlint.yml
vendored
Normal file
22
.github/workflows/yarnlint.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: Yarn Linting
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js and yarn
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "webapp/yarn.lock"
|
||||
|
||||
- name: Install WebApp dependencies
|
||||
run: yarn --cwd webapp install --frozen-lockfile
|
||||
|
||||
- name: Linting
|
||||
run: yarn --cwd webapp lint
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
This is a fork from the Hoymiles project OpenDTU.
|
||||
|
||||

|
||||
|
||||
## Extensions to the original OpenDTU
|
||||
|
||||
This project is still under development and adds following features:
|
||||
@ -17,6 +19,7 @@ This project is still under development and adds following features:
|
||||
|
||||
[](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml)
|
||||
[](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml)
|
||||
[](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml)
|
||||
|
||||
## !! IMPORTANT UPGRADE NOTES !!
|
||||
|
||||
@ -195,6 +198,7 @@ Topics for 3 phases of a power meter is configurable. Given is an example for th
|
||||
* Hoymiles HM-1000
|
||||
* Hoymiles HM-1200
|
||||
* Hoymiles HM-1500
|
||||
* Solenso SOL-H350
|
||||
* Solenso SOL-H400
|
||||
* Solenso SOL-H800
|
||||
* TSUN TSOL-M350 (Maybe depending on firmware/serial number on the inverter)
|
||||
|
||||
@ -102,4 +102,6 @@ The json file can contain multiple profiles. Each profile requires a name and di
|
||||
| display.data | number | Data Pin (e.g. SDA for i2c displays) required for all displays. Use 255 for not assigned pins. |
|
||||
| display.clk | number | Clock Pin (e.g. SCL for i2c displays) required for SSD1306 and SH1106. Use 255 for not assigned pins. |
|
||||
| display.cs | number | Chip Select Pin required for PCD8544. Use 255 for not assigned pins. |
|
||||
| display.reset | number | Reset Pin required for PCD8544, optional for all other displays. Use 255 for not assigned pins. |
|
||||
| display.reset | number | Reset Pin required for PCD8544, optional for all other displays. Use 255 for not assigned pins. |
|
||||
| led.led0 | number | LED pin for network indication. Blinking = WLAN connected but NTP & MQTT (if enabled) disconnected. On = WLAN, NTP, MQTT connected. Off = Network not connected |
|
||||
| led.led1 | number | LED pin for inverter indication. On = All inverters reachable & producing. Blinking = All inverters reachable but not producing. Off = At least one inverter is not reachable. Only inverters with polling enabled are considered. |
|
||||
64
docs/DeviceProfiles/blinkyparts_esp32.json
Normal file
64
docs/DeviceProfiles/blinkyparts_esp32.json
Normal file
@ -0,0 +1,64 @@
|
||||
[
|
||||
{
|
||||
"name": "LEDs, Display",
|
||||
"nrf24": {
|
||||
"miso": 19,
|
||||
"mosi": 23,
|
||||
"clk": 18,
|
||||
"irq": 16,
|
||||
"en": 4,
|
||||
"cs": 5
|
||||
},
|
||||
"display": {
|
||||
"type": 3,
|
||||
"data": 21,
|
||||
"clk": 22
|
||||
},
|
||||
"led": {
|
||||
"led0": 25,
|
||||
"led1": 26
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only Display",
|
||||
"nrf24": {
|
||||
"miso": 19,
|
||||
"mosi": 23,
|
||||
"clk": 18,
|
||||
"irq": 16,
|
||||
"en": 4,
|
||||
"cs": 5
|
||||
},
|
||||
"display": {
|
||||
"type": 3,
|
||||
"data": 21,
|
||||
"clk": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only LEDs",
|
||||
"nrf24": {
|
||||
"miso": 19,
|
||||
"mosi": 23,
|
||||
"clk": 18,
|
||||
"irq": 16,
|
||||
"en": 4,
|
||||
"cs": 5
|
||||
},
|
||||
"led": {
|
||||
"led0": 25,
|
||||
"led1": 26
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "No Output",
|
||||
"nrf24": {
|
||||
"miso": 19,
|
||||
"mosi": 23,
|
||||
"clk": 18,
|
||||
"irq": 16,
|
||||
"en": 4,
|
||||
"cs": 5
|
||||
}
|
||||
}
|
||||
]
|
||||
35
include/Led_Single.h
Normal file
35
include/Led_Single.h
Normal file
@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "PinMapping.h"
|
||||
#include <TimeoutHelper.h>
|
||||
|
||||
#define LEDSINGLE_UPDATE_INTERVAL 2000
|
||||
|
||||
enum eLedFunction {
|
||||
CONNECTED_NETWORK,
|
||||
CONNECTED_MQTT,
|
||||
INV_REACHABLE,
|
||||
INV_PRODUCING,
|
||||
};
|
||||
|
||||
class LedSingleClass {
|
||||
public:
|
||||
LedSingleClass();
|
||||
void init();
|
||||
void loop();
|
||||
|
||||
private:
|
||||
enum class LedState_t {
|
||||
On,
|
||||
Off,
|
||||
Blink,
|
||||
};
|
||||
|
||||
LedState_t _ledState[PINMAPPING_LED_COUNT];
|
||||
TimeoutHelper _updateTimeout;
|
||||
TimeoutHelper _blinkTimeout;
|
||||
uint8_t _ledActive = 0;
|
||||
};
|
||||
|
||||
extern LedSingleClass LedSingle;
|
||||
@ -6,6 +6,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#define PINMAPPING_FILENAME "/pin_mapping.json"
|
||||
#define PINMAPPING_LED_COUNT 2
|
||||
|
||||
#define MAPPING_NAME_STRLEN 31
|
||||
|
||||
@ -39,6 +40,7 @@ struct PinMapping_t {
|
||||
uint8_t huawei_irq;
|
||||
uint8_t huawei_cs;
|
||||
uint8_t huawei_power;
|
||||
int8_t led[PINMAPPING_LED_COUNT];
|
||||
};
|
||||
|
||||
class PinMappingClass {
|
||||
|
||||
@ -16,7 +16,7 @@ typedef enum {
|
||||
|
||||
typedef enum {
|
||||
EMPTY_WHEN_FULL= 0,
|
||||
EMPTY_AT_NIGTH
|
||||
EMPTY_AT_NIGHT
|
||||
} batDrainStrategy;
|
||||
|
||||
|
||||
@ -28,9 +28,10 @@ public:
|
||||
int32_t getLastRequestedPowewrLimit();
|
||||
|
||||
private:
|
||||
uint32_t _lastCommandSent;
|
||||
uint32_t _lastLoop;
|
||||
int32_t _lastRequestedPowerLimit;
|
||||
uint32_t _lastCommandSent = 0;
|
||||
uint32_t _lastLoop = 0;
|
||||
int32_t _lastRequestedPowerLimit = 0;
|
||||
uint32_t _lastLimitSetTime = 0;
|
||||
plStates _plState = STATE_DISCOVER;
|
||||
|
||||
float _powerMeter1Power;
|
||||
|
||||
@ -35,7 +35,7 @@ void HoymilesClass::loop()
|
||||
if (_radio->isIdle()) {
|
||||
std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos);
|
||||
if (iv != nullptr) {
|
||||
_messageOutput->print(F("Fetch inverter: "));
|
||||
_messageOutput->print("Fetch inverter: ");
|
||||
_messageOutput->println(iv->serial(), HEX);
|
||||
|
||||
iv->sendStatsRequest(_radio.get());
|
||||
@ -54,19 +54,19 @@ void HoymilesClass::loop()
|
||||
|
||||
// Set limit if required
|
||||
if (iv->SystemConfigPara()->getLastLimitCommandSuccess() == CMD_NOK) {
|
||||
_messageOutput->println(F("Resend ActivePowerControl"));
|
||||
_messageOutput->println("Resend ActivePowerControl");
|
||||
iv->resendActivePowerControlRequest(_radio.get());
|
||||
}
|
||||
|
||||
// Set power status if required
|
||||
if (iv->PowerCommand()->getLastPowerCommandSuccess() == CMD_NOK) {
|
||||
_messageOutput->println(F("Resend PowerCommand"));
|
||||
_messageOutput->println("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)) {
|
||||
_messageOutput->println(F("Request device info"));
|
||||
_messageOutput->println("Request device info");
|
||||
iv->sendDevInfoRequest(_radio.get());
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,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()) {
|
||||
Hoymiles.getMessageOutput()->println(F("Connection successful"));
|
||||
Hoymiles.getMessageOutput()->println("Connection successful");
|
||||
} else {
|
||||
Hoymiles.getMessageOutput()->println(F("Connection error!!"));
|
||||
Hoymiles.getMessageOutput()->println("Connection error!!");
|
||||
}
|
||||
|
||||
attachInterrupt(digitalPinToInterrupt(pinIRQ), std::bind(&HoymilesRadio::handleIntr, this), FALLING);
|
||||
@ -44,7 +44,7 @@ void HoymilesRadio::loop()
|
||||
}
|
||||
|
||||
if (_packetReceived) {
|
||||
Hoymiles.getMessageOutput()->println(F("Interrupt received"));
|
||||
Hoymiles.getMessageOutput()->println("Interrupt received");
|
||||
while (_radio->available()) {
|
||||
if (!(_rxBuffer.size() > FRAGMENT_BUFFER_SIZE)) {
|
||||
fragment_t f;
|
||||
@ -56,7 +56,7 @@ void HoymilesRadio::loop()
|
||||
_radio->read(f.fragment, f.len);
|
||||
_rxBuffer.push(f);
|
||||
} else {
|
||||
Hoymiles.getMessageOutput()->println(F("Buffer full"));
|
||||
Hoymiles.getMessageOutput()->println("Buffer full");
|
||||
_radio->flush_rx();
|
||||
}
|
||||
}
|
||||
@ -76,11 +76,11 @@ void HoymilesRadio::loop()
|
||||
dumpBuf(buf, f.fragment, f.len);
|
||||
inv->addRxFragment(f.fragment, f.len);
|
||||
} else {
|
||||
Hoymiles.getMessageOutput()->println(F("Inverter Not found!"));
|
||||
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
||||
}
|
||||
|
||||
} else {
|
||||
Hoymiles.getMessageOutput()->println(F("Frame kaputt"));
|
||||
Hoymiles.getMessageOutput()->println("Frame kaputt");
|
||||
}
|
||||
|
||||
// Remove paket from buffer even it was corrupted
|
||||
@ -89,46 +89,46 @@ void HoymilesRadio::loop()
|
||||
}
|
||||
|
||||
if (_busyFlag && _rxTimeout.occured()) {
|
||||
Hoymiles.getMessageOutput()->println(F("RX Period End"));
|
||||
Hoymiles.getMessageOutput()->println("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) {
|
||||
Hoymiles.getMessageOutput()->println(F("Nothing received, resend whole request"));
|
||||
Hoymiles.getMessageOutput()->println("Nothing received, resend whole request");
|
||||
sendLastPacketAgain();
|
||||
|
||||
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
|
||||
Hoymiles.getMessageOutput()->println(F("Nothing received, resend count exeeded"));
|
||||
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
|
||||
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
|
||||
Hoymiles.getMessageOutput()->println(F("Retransmit timeout"));
|
||||
Hoymiles.getMessageOutput()->println("Retransmit timeout");
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
|
||||
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
|
||||
Hoymiles.getMessageOutput()->println(F("Packet handling error"));
|
||||
Hoymiles.getMessageOutput()->println("Packet handling error");
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
|
||||
} else if (verifyResult > 0) {
|
||||
// Perform Retransmit
|
||||
Hoymiles.getMessageOutput()->print(F("Request retransmit: "));
|
||||
Hoymiles.getMessageOutput()->print("Request retransmit: ");
|
||||
Hoymiles.getMessageOutput()->println(verifyResult);
|
||||
sendRetransmitPacket(verifyResult);
|
||||
|
||||
} else {
|
||||
// Successful received all packages
|
||||
Hoymiles.getMessageOutput()->println(F("Success"));
|
||||
Hoymiles.getMessageOutput()->println("Success");
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
}
|
||||
} else {
|
||||
// If inverter was not found, assume the command is invalid
|
||||
Hoymiles.getMessageOutput()->println(F("RX: Invalid inverter found"));
|
||||
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
|
||||
_commandQueue.pop();
|
||||
_busyFlag = false;
|
||||
}
|
||||
@ -142,7 +142,7 @@ void HoymilesRadio::loop()
|
||||
inv->clearRxFragmentBuffer();
|
||||
sendEsbPacket(cmd);
|
||||
} else {
|
||||
Hoymiles.getMessageOutput()->println(F("TX: Invalid inverter found"));
|
||||
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
|
||||
_commandQueue.pop();
|
||||
}
|
||||
}
|
||||
@ -252,11 +252,11 @@ void HoymilesRadio::sendEsbPacket(CommandAbstract* cmd)
|
||||
openWritingPipe(s);
|
||||
_radio->setRetries(3, 15);
|
||||
|
||||
Hoymiles.getMessageOutput()->print(F("TX "));
|
||||
Hoymiles.getMessageOutput()->print("TX ");
|
||||
Hoymiles.getMessageOutput()->print(cmd->getCommandName());
|
||||
Hoymiles.getMessageOutput()->print(F(" Channel: "));
|
||||
Hoymiles.getMessageOutput()->print(" Channel: ");
|
||||
Hoymiles.getMessageOutput()->print(_radio->getChannel());
|
||||
Hoymiles.getMessageOutput()->print(F(" --> "));
|
||||
Hoymiles.getMessageOutput()->print(" --> ");
|
||||
cmd->dumpDataPayload(Hoymiles.getMessageOutput());
|
||||
_radio->write(cmd->getDataPayload(), cmd->getDataSize());
|
||||
|
||||
@ -294,5 +294,5 @@ void HoymilesRadio::dumpBuf(const char* info, uint8_t buf[], uint8_t len)
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
Hoymiles.getMessageOutput()->printf("%02X ", buf[i]);
|
||||
}
|
||||
Hoymiles.getMessageOutput()->println(F(""));
|
||||
Hoymiles.getMessageOutput()->println("");
|
||||
}
|
||||
@ -29,7 +29,7 @@ bool HM_1CH::isValidSerial(uint64_t serial)
|
||||
|
||||
String HM_1CH::typeName()
|
||||
{
|
||||
return F("HM-300, HM-350, HM-400");
|
||||
return "HM-300, HM-350, HM-400";
|
||||
}
|
||||
|
||||
const std::list<byteAssign_t>* HM_1CH::getByteAssignment()
|
||||
|
||||
@ -30,7 +30,7 @@ bool HM_2CH::isValidSerial(uint64_t serial)
|
||||
|
||||
String HM_2CH::typeName()
|
||||
{
|
||||
return F("HM-600, HM-700, HM-800");
|
||||
return "HM-600, HM-700, HM-800";
|
||||
}
|
||||
|
||||
const std::list<byteAssign_t>* HM_2CH::getByteAssignment()
|
||||
|
||||
@ -29,7 +29,7 @@ bool HM_4CH::isValidSerial(uint64_t serial)
|
||||
|
||||
String HM_4CH::typeName()
|
||||
{
|
||||
return F("HM-1000, HM-1200, HM-1500");
|
||||
return "HM-1000, HM-1200, HM-1500";
|
||||
}
|
||||
|
||||
const std::list<byteAssign_t>* HM_4CH::getByteAssignment()
|
||||
|
||||
@ -169,7 +169,7 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
|
||||
{
|
||||
// All missing
|
||||
if (_rxFragmentLastPacketId == 0) {
|
||||
Hoymiles.getMessageOutput()->println(F("All missing"));
|
||||
Hoymiles.getMessageOutput()->println("All missing");
|
||||
if (cmd->getSendCount() <= MAX_RESEND_COUNT) {
|
||||
return FRAGMENT_ALL_MISSING_RESEND;
|
||||
} else {
|
||||
@ -178,9 +178,9 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// Last fragment is missing (thte one with 0x80)
|
||||
// Last fragment is missing (the one with 0x80)
|
||||
if (_rxFragmentMaxPacketId == 0) {
|
||||
Hoymiles.getMessageOutput()->println(F("Last missing"));
|
||||
Hoymiles.getMessageOutput()->println("Last missing");
|
||||
if (_rxFragmentRetransmitCnt++ < MAX_RETRANSMIT_COUNT) {
|
||||
return _rxFragmentLastPacketId + 1;
|
||||
} else {
|
||||
@ -192,7 +192,7 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd)
|
||||
// Middle fragment is missing
|
||||
for (uint8_t i = 0; i < _rxFragmentMaxPacketId - 1; i++) {
|
||||
if (!_rxFragmentBuffer[i].wasReceived) {
|
||||
Hoymiles.getMessageOutput()->println(F("Middle missing"));
|
||||
Hoymiles.getMessageOutput()->println("Middle missing");
|
||||
if (_rxFragmentRetransmitCnt++ < MAX_RETRANSMIT_COUNT) {
|
||||
return i + 1;
|
||||
} else {
|
||||
|
||||
@ -64,214 +64,214 @@ void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry)
|
||||
|
||||
switch (entry->MessageId) {
|
||||
case 1:
|
||||
entry->Message = F("Inverter start");
|
||||
entry->Message = "Inverter start";
|
||||
break;
|
||||
case 2:
|
||||
entry->Message = F("DTU command failed");
|
||||
entry->Message = "DTU command failed";
|
||||
break;
|
||||
case 121:
|
||||
entry->Message = F("Over temperature protection");
|
||||
entry->Message = "Over temperature protection";
|
||||
break;
|
||||
case 124:
|
||||
entry->Message = F("Shut down by remote control");
|
||||
entry->Message = "Shut down by remote control";
|
||||
break;
|
||||
case 125:
|
||||
entry->Message = F("Grid configuration parameter error");
|
||||
entry->Message = "Grid configuration parameter error";
|
||||
break;
|
||||
case 126:
|
||||
entry->Message = F("Software error code 126");
|
||||
entry->Message = "Software error code 126";
|
||||
break;
|
||||
case 127:
|
||||
entry->Message = F("Firmware error");
|
||||
entry->Message = "Firmware error";
|
||||
break;
|
||||
case 128:
|
||||
entry->Message = F("Software error code 128");
|
||||
entry->Message = "Software error code 128";
|
||||
break;
|
||||
case 129:
|
||||
entry->Message = F("Abnormal bias");
|
||||
entry->Message = "Abnormal bias";
|
||||
break;
|
||||
case 130:
|
||||
entry->Message = F("Offline");
|
||||
entry->Message = "Offline";
|
||||
break;
|
||||
case 141:
|
||||
entry->Message = F("Grid: Grid overvoltage");
|
||||
entry->Message = "Grid: Grid overvoltage";
|
||||
break;
|
||||
case 142:
|
||||
entry->Message = F("Grid: 10 min value grid overvoltage");
|
||||
entry->Message = "Grid: 10 min value grid overvoltage";
|
||||
break;
|
||||
case 143:
|
||||
entry->Message = F("Grid: Grid undervoltage");
|
||||
entry->Message = "Grid: Grid undervoltage";
|
||||
break;
|
||||
case 144:
|
||||
entry->Message = F("Grid: Grid overfrequency");
|
||||
entry->Message = "Grid: Grid overfrequency";
|
||||
break;
|
||||
case 145:
|
||||
entry->Message = F("Grid: Grid underfrequency");
|
||||
entry->Message = "Grid: Grid underfrequency";
|
||||
break;
|
||||
case 146:
|
||||
entry->Message = F("Grid: Rapid grid frequency change rate");
|
||||
entry->Message = "Grid: Rapid grid frequency change rate";
|
||||
break;
|
||||
case 147:
|
||||
entry->Message = F("Grid: Power grid outage");
|
||||
entry->Message = "Grid: Power grid outage";
|
||||
break;
|
||||
case 148:
|
||||
entry->Message = F("Grid: Grid disconnection");
|
||||
entry->Message = "Grid: Grid disconnection";
|
||||
break;
|
||||
case 149:
|
||||
entry->Message = F("Grid: Island detected");
|
||||
entry->Message = "Grid: Island detected";
|
||||
break;
|
||||
case 205:
|
||||
entry->Message = F("MPPT-A: Input overvoltage");
|
||||
entry->Message = "MPPT-A: Input overvoltage";
|
||||
break;
|
||||
case 206:
|
||||
entry->Message = F("MPPT-B: Input overvoltage");
|
||||
entry->Message = "MPPT-B: Input overvoltage";
|
||||
break;
|
||||
case 207:
|
||||
entry->Message = F("MPPT-A: Input undervoltage");
|
||||
entry->Message = "MPPT-A: Input undervoltage";
|
||||
break;
|
||||
case 208:
|
||||
entry->Message = F("MPPT-B: Input undervoltage");
|
||||
entry->Message = "MPPT-B: Input undervoltage";
|
||||
break;
|
||||
case 209:
|
||||
entry->Message = F("PV-1: No input");
|
||||
entry->Message = "PV-1: No input";
|
||||
break;
|
||||
case 210:
|
||||
entry->Message = F("PV-2: No input");
|
||||
entry->Message = "PV-2: No input";
|
||||
break;
|
||||
case 211:
|
||||
entry->Message = F("PV-3: No input");
|
||||
entry->Message = "PV-3: No input";
|
||||
break;
|
||||
case 212:
|
||||
entry->Message = F("PV-4: No input");
|
||||
entry->Message = "PV-4: No input";
|
||||
break;
|
||||
case 213:
|
||||
entry->Message = F("MPPT-A: PV-1 & PV-2 abnormal wiring");
|
||||
entry->Message = "MPPT-A: PV-1 & PV-2 abnormal wiring";
|
||||
break;
|
||||
case 214:
|
||||
entry->Message = F("MPPT-B: PV-3 & PV-4 abnormal wiring");
|
||||
entry->Message = "MPPT-B: PV-3 & PV-4 abnormal wiring";
|
||||
break;
|
||||
case 215:
|
||||
entry->Message = F("PV-1: Input overvoltage");
|
||||
entry->Message = "PV-1: Input overvoltage";
|
||||
break;
|
||||
case 216:
|
||||
entry->Message = F("PV-1: Input undervoltage");
|
||||
entry->Message = "PV-1: Input undervoltage";
|
||||
break;
|
||||
case 217:
|
||||
entry->Message = F("PV-2: Input overvoltage");
|
||||
entry->Message = "PV-2: Input overvoltage";
|
||||
break;
|
||||
case 218:
|
||||
entry->Message = F("PV-2: Input undervoltage");
|
||||
entry->Message = "PV-2: Input undervoltage";
|
||||
break;
|
||||
case 219:
|
||||
entry->Message = F("PV-3: Input overvoltage");
|
||||
entry->Message = "PV-3: Input overvoltage";
|
||||
break;
|
||||
case 220:
|
||||
entry->Message = F("PV-3: Input undervoltage");
|
||||
entry->Message = "PV-3: Input undervoltage";
|
||||
break;
|
||||
case 221:
|
||||
entry->Message = F("PV-4: Input overvoltage");
|
||||
entry->Message = "PV-4: Input overvoltage";
|
||||
break;
|
||||
case 222:
|
||||
entry->Message = F("PV-4: Input undervoltage");
|
||||
entry->Message = "PV-4: Input undervoltage";
|
||||
break;
|
||||
case 301:
|
||||
entry->Message = F("Hardware error code 301");
|
||||
entry->Message = "Hardware error code 301";
|
||||
break;
|
||||
case 302:
|
||||
entry->Message = F("Hardware error code 302");
|
||||
entry->Message = "Hardware error code 302";
|
||||
break;
|
||||
case 303:
|
||||
entry->Message = F("Hardware error code 303");
|
||||
entry->Message = "Hardware error code 303";
|
||||
break;
|
||||
case 304:
|
||||
entry->Message = F("Hardware error code 304");
|
||||
entry->Message = "Hardware error code 304";
|
||||
break;
|
||||
case 305:
|
||||
entry->Message = F("Hardware error code 305");
|
||||
entry->Message = "Hardware error code 305";
|
||||
break;
|
||||
case 306:
|
||||
entry->Message = F("Hardware error code 306");
|
||||
entry->Message = "Hardware error code 306";
|
||||
break;
|
||||
case 307:
|
||||
entry->Message = F("Hardware error code 307");
|
||||
entry->Message = "Hardware error code 307";
|
||||
break;
|
||||
case 308:
|
||||
entry->Message = F("Hardware error code 308");
|
||||
entry->Message = "Hardware error code 308";
|
||||
break;
|
||||
case 309:
|
||||
entry->Message = F("Hardware error code 309");
|
||||
entry->Message = "Hardware error code 309";
|
||||
break;
|
||||
case 310:
|
||||
entry->Message = F("Hardware error code 310");
|
||||
entry->Message = "Hardware error code 310";
|
||||
break;
|
||||
case 311:
|
||||
entry->Message = F("Hardware error code 311");
|
||||
entry->Message = "Hardware error code 311";
|
||||
break;
|
||||
case 312:
|
||||
entry->Message = F("Hardware error code 312");
|
||||
entry->Message = "Hardware error code 312";
|
||||
break;
|
||||
case 313:
|
||||
entry->Message = F("Hardware error code 313");
|
||||
entry->Message = "Hardware error code 313";
|
||||
break;
|
||||
case 314:
|
||||
entry->Message = F("Hardware error code 314");
|
||||
entry->Message = "Hardware error code 314";
|
||||
break;
|
||||
case 5041:
|
||||
entry->Message = F("Error code-04 Port 1");
|
||||
entry->Message = "Error code-04 Port 1";
|
||||
break;
|
||||
case 5042:
|
||||
entry->Message = F("Error code-04 Port 2");
|
||||
entry->Message = "Error code-04 Port 2";
|
||||
break;
|
||||
case 5043:
|
||||
entry->Message = F("Error code-04 Port 3");
|
||||
entry->Message = "Error code-04 Port 3";
|
||||
break;
|
||||
case 5044:
|
||||
entry->Message = F("Error code-04 Port 4");
|
||||
entry->Message = "Error code-04 Port 4";
|
||||
break;
|
||||
case 5051:
|
||||
entry->Message = F("PV Input 1 Overvoltage/Undervoltage");
|
||||
entry->Message = "PV Input 1 Overvoltage/Undervoltage";
|
||||
break;
|
||||
case 5052:
|
||||
entry->Message = F("PV Input 2 Overvoltage/Undervoltage");
|
||||
entry->Message = "PV Input 2 Overvoltage/Undervoltage";
|
||||
break;
|
||||
case 5053:
|
||||
entry->Message = F("PV Input 3 Overvoltage/Undervoltage");
|
||||
entry->Message = "PV Input 3 Overvoltage/Undervoltage";
|
||||
break;
|
||||
case 5054:
|
||||
entry->Message = F("PV Input 4 Overvoltage/Undervoltage");
|
||||
entry->Message = "PV Input 4 Overvoltage/Undervoltage";
|
||||
break;
|
||||
case 5060:
|
||||
entry->Message = F("Abnormal bias");
|
||||
entry->Message = "Abnormal bias";
|
||||
break;
|
||||
case 5070:
|
||||
entry->Message = F("Over temperature protection");
|
||||
entry->Message = "Over temperature protection";
|
||||
break;
|
||||
case 5080:
|
||||
entry->Message = F("Grid Overvoltage/Undervoltage");
|
||||
entry->Message = "Grid Overvoltage/Undervoltage";
|
||||
break;
|
||||
case 5090:
|
||||
entry->Message = F("Grid Overfrequency/Underfrequency");
|
||||
entry->Message = "Grid Overfrequency/Underfrequency";
|
||||
break;
|
||||
case 5100:
|
||||
entry->Message = F("Island detected");
|
||||
entry->Message = "Island detected";
|
||||
break;
|
||||
case 5120:
|
||||
entry->Message = F("EEPROM reading and writing error");
|
||||
entry->Message = "EEPROM reading and writing error";
|
||||
break;
|
||||
case 5150:
|
||||
entry->Message = F("10 min value grid overvoltage");
|
||||
entry->Message = "10 min value grid overvoltage";
|
||||
break;
|
||||
case 5200:
|
||||
entry->Message = F("Firmware error");
|
||||
entry->Message = "Firmware error";
|
||||
break;
|
||||
case 8310:
|
||||
entry->Message = F("Shut down");
|
||||
entry->Message = "Shut down";
|
||||
break;
|
||||
case 9000:
|
||||
entry->Message = F("Microinverter is suspected of being stolen");
|
||||
entry->Message = "Microinverter is suspected of being stolen";
|
||||
break;
|
||||
default:
|
||||
entry->Message = F("Unknown");
|
||||
entry->Message = "Unknown";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,7 +100,7 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch
|
||||
}
|
||||
|
||||
result /= static_cast<float>(div);
|
||||
if (setting != NULL) {
|
||||
if (setting != NULL && _statisticLength > 0) {
|
||||
result += setting->offset;
|
||||
}
|
||||
return result;
|
||||
|
||||
@ -29,58 +29,58 @@ String ResetReasonClass::get_reset_reason_verbose(uint8_t cpu_id)
|
||||
|
||||
switch (reason) {
|
||||
case 1:
|
||||
reason_str = F("Vbat power on reset");
|
||||
reason_str = "Vbat power on reset";
|
||||
break;
|
||||
case 3:
|
||||
reason_str = F("Software reset digital core");
|
||||
reason_str = "Software reset digital core";
|
||||
break;
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
case 4:
|
||||
reason_str = F("Legacy watch dog reset digital core");
|
||||
reason_str = "Legacy watch dog reset digital core";
|
||||
break;
|
||||
#endif
|
||||
case 5:
|
||||
reason_str = F("Deep Sleep reset digital core");
|
||||
reason_str = "Deep Sleep reset digital core";
|
||||
break;
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
case 6:
|
||||
reason_str = F("Reset by SLC module, reset digital core");
|
||||
reason_str = "Reset by SLC module, reset digital core";
|
||||
break;
|
||||
#endif
|
||||
case 7:
|
||||
reason_str = F("Timer Group0 Watch dog reset digital core");
|
||||
reason_str = "Timer Group0 Watch dog reset digital core";
|
||||
break;
|
||||
case 8:
|
||||
reason_str = F("Timer Group1 Watch dog reset digital core");
|
||||
reason_str = "Timer Group1 Watch dog reset digital core";
|
||||
break;
|
||||
case 9:
|
||||
reason_str = F("RTC Watch dog Reset digital core");
|
||||
reason_str = "RTC Watch dog Reset digital core";
|
||||
break;
|
||||
case 10:
|
||||
reason_str = F("Instrusion tested to reset CPU");
|
||||
reason_str = "Instrusion tested to reset CPU";
|
||||
break;
|
||||
case 11:
|
||||
reason_str = F("Time Group reset CPU");
|
||||
reason_str = "Time Group reset CPU";
|
||||
break;
|
||||
case 12:
|
||||
reason_str = F("Software reset CPU");
|
||||
reason_str = "Software reset CPU";
|
||||
break;
|
||||
case 13:
|
||||
reason_str = F("RTC Watch dog Reset CPU");
|
||||
reason_str = "RTC Watch dog Reset CPU";
|
||||
break;
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
case 14:
|
||||
reason_str = F("for APP CPU, reset by PRO CPU");
|
||||
reason_str = "for APP CPU, reset by PRO CPU";
|
||||
break;
|
||||
#endif
|
||||
case 15:
|
||||
reason_str = F("Reset when the vdd voltage is not stable");
|
||||
reason_str = "Reset when the vdd voltage is not stable";
|
||||
break;
|
||||
case 16:
|
||||
reason_str = F("RTC Watch dog reset digital core and rtc module");
|
||||
reason_str = "RTC Watch dog reset digital core and rtc module";
|
||||
break;
|
||||
default:
|
||||
reason_str = F("NO_MEAN");
|
||||
reason_str = "NO_MEAN";
|
||||
}
|
||||
|
||||
return reason_str;
|
||||
@ -95,58 +95,58 @@ String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id)
|
||||
|
||||
switch (reason) {
|
||||
case 1:
|
||||
reason_str = F("POWERON_RESET");
|
||||
reason_str = "POWERON_RESET";
|
||||
break;
|
||||
case 3:
|
||||
reason_str = F("SW_RESET");
|
||||
reason_str = "SW_RESET";
|
||||
break;
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
case 4:
|
||||
reason_str = F("OWDT_RESET");
|
||||
reason_str = "OWDT_RESET";
|
||||
break;
|
||||
#endif
|
||||
case 5:
|
||||
reason_str = F("DEEPSLEEP_RESET");
|
||||
reason_str = "DEEPSLEEP_RESET";
|
||||
break;
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
case 6:
|
||||
reason_str = F("SDIO_RESET");
|
||||
reason_str = "SDIO_RESET";
|
||||
break;
|
||||
#endif
|
||||
case 7:
|
||||
reason_str = F("TG0WDT_SYS_RESET");
|
||||
reason_str = "TG0WDT_SYS_RESET";
|
||||
break;
|
||||
case 8:
|
||||
reason_str = F("TG1WDT_SYS_RESET");
|
||||
reason_str = "TG1WDT_SYS_RESET";
|
||||
break;
|
||||
case 9:
|
||||
reason_str = F("RTCWDT_SYS_RESET");
|
||||
reason_str = "RTCWDT_SYS_RESET";
|
||||
break;
|
||||
case 10:
|
||||
reason_str = F("INTRUSION_RESET");
|
||||
reason_str = "INTRUSION_RESET";
|
||||
break;
|
||||
case 11:
|
||||
reason_str = F("TGWDT_CPU_RESET");
|
||||
reason_str = "TGWDT_CPU_RESET";
|
||||
break;
|
||||
case 12:
|
||||
reason_str = F("SW_CPU_RESET");
|
||||
reason_str = "SW_CPU_RESET";
|
||||
break;
|
||||
case 13:
|
||||
reason_str = F("RTCWDT_CPU_RESET");
|
||||
reason_str = "RTCWDT_CPU_RESET";
|
||||
break;
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
case 14:
|
||||
reason_str = F("EXT_CPU_RESET");
|
||||
reason_str = "EXT_CPU_RESET";
|
||||
break;
|
||||
#endif
|
||||
case 15:
|
||||
reason_str = F("RTCWDT_BROWN_OUT_RESET");
|
||||
reason_str = "RTCWDT_BROWN_OUT_RESET";
|
||||
break;
|
||||
case 16:
|
||||
reason_str = F("RTCWDT_RTC_RESET");
|
||||
reason_str = "RTCWDT_RTC_RESET";
|
||||
break;
|
||||
default:
|
||||
reason_str = F("NO_MEAN");
|
||||
reason_str = "NO_MEAN";
|
||||
}
|
||||
|
||||
return reason_str;
|
||||
|
||||
@ -22,6 +22,11 @@ void TimeoutHelper::extend(uint32_t ms)
|
||||
timeout += ms;
|
||||
}
|
||||
|
||||
void TimeoutHelper::reset()
|
||||
{
|
||||
startMillis = millis();
|
||||
}
|
||||
|
||||
bool TimeoutHelper::occured()
|
||||
{
|
||||
return millis() > (startMillis + timeout);
|
||||
|
||||
@ -8,6 +8,7 @@ public:
|
||||
TimeoutHelper();
|
||||
void set(uint32_t ms);
|
||||
void extend(uint32_t ms);
|
||||
void reset();
|
||||
bool occured();
|
||||
|
||||
private:
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0
|
||||
|
||||
typedef struct {
|
||||
uint16_t PID; // pruduct id
|
||||
uint16_t PID; // product id
|
||||
char SER[VE_MAX_VALUE_LEN]; // serial number
|
||||
char FW[VE_MAX_VALUE_LEN]; // firmware release number
|
||||
bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit)
|
||||
|
||||
@ -15,7 +15,7 @@ extra_configs =
|
||||
|
||||
[env]
|
||||
framework = arduino
|
||||
platform = espressif32@>=6.0.1
|
||||
platform = espressif32@>=6.1.0
|
||||
|
||||
build_flags =
|
||||
-DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz
|
||||
@ -96,7 +96,7 @@ build_flags = ${env.build_flags}
|
||||
-DOPENDTU_ETHERNET
|
||||
|
||||
|
||||
[env:d1 mini esp32]
|
||||
[env:d1_mini_esp32]
|
||||
board = wemos_d1_mini32
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
|
||||
@ -180,7 +180,7 @@ bool ConfigurationClass::read()
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, f);
|
||||
if (error) {
|
||||
MessageOutput.printf("Failed to read file, using default configuration. Error: %s (capacity: %d)\r\n", error.c_str(), doc.capacity());
|
||||
MessageOutput.println("Failed to read file, using default configuration");
|
||||
}
|
||||
|
||||
JsonObject cfg = doc["cfg"];
|
||||
@ -359,7 +359,7 @@ void ConfigurationClass::migrate()
|
||||
{
|
||||
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
|
||||
if (!f) {
|
||||
MessageOutput.println(F("Failed to open file, cancel migration"));
|
||||
MessageOutput.println("Failed to open file, cancel migration");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,10 @@
|
||||
#include "SunPosition.h"
|
||||
#include <Hoymiles.h>
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||
#define VSPI FSPI
|
||||
#endif
|
||||
|
||||
InverterSettingsClass InverterSettings;
|
||||
|
||||
void InverterSettingsClass::init()
|
||||
@ -17,27 +21,27 @@ void InverterSettingsClass::init()
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
|
||||
// Initialize inverter communication
|
||||
MessageOutput.print(F("Initialize Hoymiles interface... "));
|
||||
MessageOutput.print("Initialize Hoymiles interface... ");
|
||||
if (PinMapping.isValidNrf24Config()) {
|
||||
SPIClass* spiClass = new SPIClass(HSPI);
|
||||
SPIClass* spiClass = new SPIClass(VSPI);
|
||||
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
|
||||
Hoymiles.setMessageOutput(&MessageOutput);
|
||||
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
|
||||
|
||||
MessageOutput.println(F(" Setting radio PA level... "));
|
||||
MessageOutput.println(" Setting radio PA level... ");
|
||||
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
||||
|
||||
MessageOutput.println(F(" Setting DTU serial... "));
|
||||
MessageOutput.println(" Setting DTU serial... ");
|
||||
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
|
||||
|
||||
MessageOutput.println(F(" Setting poll interval... "));
|
||||
MessageOutput.println(" Setting poll interval... ");
|
||||
Hoymiles.setPollInterval(config.Dtu_PollInterval);
|
||||
|
||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||
if (config.Inverter[i].Serial > 0) {
|
||||
MessageOutput.print(F(" Adding inverter: "));
|
||||
MessageOutput.print(" Adding inverter: ");
|
||||
MessageOutput.print(config.Inverter[i].Serial, HEX);
|
||||
MessageOutput.print(F(" - "));
|
||||
MessageOutput.print(" - ");
|
||||
MessageOutput.print(config.Inverter[i].Name);
|
||||
auto inv = Hoymiles.addInverter(
|
||||
config.Inverter[i].Name,
|
||||
@ -49,12 +53,12 @@ void InverterSettingsClass::init()
|
||||
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset);
|
||||
}
|
||||
}
|
||||
MessageOutput.println(F(" done"));
|
||||
MessageOutput.println(" done");
|
||||
}
|
||||
}
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
} else {
|
||||
MessageOutput.println(F("Invalid pin config"));
|
||||
MessageOutput.println("Invalid pin config");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
110
src/Led_Single.cpp
Normal file
110
src/Led_Single.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2023 Thomas Basler and others
|
||||
*/
|
||||
#include "Led_Single.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "PinMapping.h"
|
||||
#include <Hoymiles.h>
|
||||
|
||||
LedSingleClass LedSingle;
|
||||
|
||||
LedSingleClass::LedSingleClass()
|
||||
{
|
||||
}
|
||||
|
||||
void LedSingleClass::init()
|
||||
{
|
||||
_blinkTimeout.set(500);
|
||||
_updateTimeout.set(LEDSINGLE_UPDATE_INTERVAL);
|
||||
|
||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||
auto& pin = PinMapping.get();
|
||||
|
||||
if (pin.led[i] >= 0) {
|
||||
pinMode(pin.led[i], OUTPUT);
|
||||
digitalWrite(pin.led[i], LOW);
|
||||
_ledActive++;
|
||||
}
|
||||
|
||||
_ledState[i] = LedState_t::Off;
|
||||
}
|
||||
}
|
||||
|
||||
void LedSingleClass::loop()
|
||||
{
|
||||
if (_ledActive == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_updateTimeout.occured()) {
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
// Update network status
|
||||
_ledState[0] = LedState_t::Off;
|
||||
|
||||
if (NetworkSettings.isConnected()) {
|
||||
_ledState[0] = LedState_t::Blink;
|
||||
}
|
||||
|
||||
struct tm timeinfo;
|
||||
if (getLocalTime(&timeinfo, 5) && (!config.Mqtt_Enabled || (config.Mqtt_Enabled && MqttSettings.getConnected()))) {
|
||||
_ledState[0] = LedState_t::On;
|
||||
}
|
||||
|
||||
// Update inverter status
|
||||
_ledState[1] = LedState_t::Off;
|
||||
if (Hoymiles.getNumInverters()) {
|
||||
bool allReachable = true;
|
||||
bool allProducing = true;
|
||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||
auto inv = Hoymiles.getInverterByPos(i);
|
||||
if (inv == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (inv->getEnablePolling()) {
|
||||
if (!inv->isReachable()) {
|
||||
allReachable = false;
|
||||
}
|
||||
if (!inv->isProducing()) {
|
||||
allProducing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// set LED status
|
||||
if (allReachable && allProducing) {
|
||||
_ledState[1] = LedState_t::On;
|
||||
}
|
||||
if (allReachable && !allProducing) {
|
||||
_ledState[1] = LedState_t::Blink;
|
||||
}
|
||||
}
|
||||
|
||||
_updateTimeout.reset();
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||
auto& pin = PinMapping.get();
|
||||
|
||||
if (pin.led[i] < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (_ledState[i]) {
|
||||
case LedState_t::Off:
|
||||
digitalWrite(pin.led[i], LOW);
|
||||
break;
|
||||
case LedState_t::On:
|
||||
digitalWrite(pin.led[i], HIGH);
|
||||
break;
|
||||
case LedState_t::Blink:
|
||||
if (_blinkTimeout.occured()) {
|
||||
digitalWrite(pin.led[i], !digitalRead(pin.led[i]));
|
||||
_blinkTimeout.reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,26 +121,26 @@ void MqttHandleHassClass::publishField(std::shared_ptr<InverterAbstract> inv, Ch
|
||||
}
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
root[F("name")] = name;
|
||||
root[F("stat_t")] = stateTopic;
|
||||
root[F("uniq_id")] = serial + "_ch" + chanNum + "_" + fieldName;
|
||||
root["name"] = name;
|
||||
root["stat_t"] = stateTopic;
|
||||
root["uniq_id"] = serial + "_ch" + chanNum + "_" + fieldName;
|
||||
|
||||
String unit_of_measure = inv->Statistics()->getChannelFieldUnit(type, channel, fieldType.fieldId);
|
||||
if (unit_of_measure != "") {
|
||||
root[F("unit_of_meas")] = unit_of_measure;
|
||||
root["unit_of_meas"] = unit_of_measure;
|
||||
}
|
||||
|
||||
JsonObject deviceObj = root.createNestedObject("dev");
|
||||
createDeviceInfo(deviceObj, inv);
|
||||
|
||||
if (Configuration.get().Mqtt_Hass_Expire) {
|
||||
root[F("exp_aft")] = Hoymiles.getNumInverters() * Configuration.get().Mqtt_PublishInterval * 3;
|
||||
root["exp_aft"] = Hoymiles.getNumInverters() * Configuration.get().Mqtt_PublishInterval * 3;
|
||||
}
|
||||
if (devCls != 0) {
|
||||
root[F("dev_cla")] = devCls;
|
||||
root["dev_cla"] = devCls;
|
||||
}
|
||||
if (stateCls != 0) {
|
||||
root[F("stat_cla")] = stateCls;
|
||||
root["stat_cla"] = stateCls;
|
||||
}
|
||||
|
||||
char buffer[512];
|
||||
@ -166,17 +166,17 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
|
||||
String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
root[F("name")] = String(inv->name()) + " " + caption;
|
||||
root[F("uniq_id")] = serial + "_" + buttonId;
|
||||
root["name"] = String(inv->name()) + " " + caption;
|
||||
root["uniq_id"] = serial + "_" + buttonId;
|
||||
if (strcmp(icon, "")) {
|
||||
root[F("ic")] = icon;
|
||||
root["ic"] = icon;
|
||||
}
|
||||
if (strcmp(deviceClass, "")) {
|
||||
root[F("dev_cla")] = deviceClass;
|
||||
root["dev_cla"] = deviceClass;
|
||||
}
|
||||
root[F("ent_cat")] = category;
|
||||
root[F("cmd_t")] = cmdTopic;
|
||||
root[F("payload_press")] = payload;
|
||||
root["ent_cat"] = category;
|
||||
root["cmd_t"] = cmdTopic;
|
||||
root["payload_press"] = payload;
|
||||
|
||||
JsonObject deviceObj = root.createNestedObject("dev");
|
||||
createDeviceInfo(deviceObj, inv);
|
||||
@ -205,17 +205,17 @@ void MqttHandleHassClass::publishInverterNumber(
|
||||
String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic;
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
root[F("name")] = String(inv->name()) + " " + caption;
|
||||
root[F("uniq_id")] = serial + "_" + buttonId;
|
||||
root["name"] = String(inv->name()) + " " + caption;
|
||||
root["uniq_id"] = serial + "_" + buttonId;
|
||||
if (strcmp(icon, "")) {
|
||||
root[F("ic")] = icon;
|
||||
root["ic"] = icon;
|
||||
}
|
||||
root[F("ent_cat")] = category;
|
||||
root[F("cmd_t")] = cmdTopic;
|
||||
root[F("stat_t")] = statTopic;
|
||||
root[F("unit_of_meas")] = unitOfMeasure;
|
||||
root[F("min")] = min;
|
||||
root[F("max")] = max;
|
||||
root["ent_cat"] = category;
|
||||
root["cmd_t"] = cmdTopic;
|
||||
root["stat_t"] = statTopic;
|
||||
root["unit_of_meas"] = unitOfMeasure;
|
||||
root["min"] = min;
|
||||
root["max"] = max;
|
||||
|
||||
JsonObject deviceObj = root.createNestedObject("dev");
|
||||
createDeviceInfo(deviceObj, inv);
|
||||
@ -240,11 +240,11 @@ void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAb
|
||||
String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
root[F("name")] = String(inv->name()) + " " + caption;
|
||||
root[F("uniq_id")] = serial + "_" + sensorId;
|
||||
root[F("stat_t")] = statTopic;
|
||||
root[F("pl_on")] = payload_on;
|
||||
root[F("pl_off")] = payload_off;
|
||||
root["name"] = String(inv->name()) + " " + caption;
|
||||
root["uniq_id"] = serial + "_" + sensorId;
|
||||
root["stat_t"] = statTopic;
|
||||
root["pl_on"] = payload_on;
|
||||
root["pl_off"] = payload_off;
|
||||
|
||||
JsonObject deviceObj = root.createNestedObject("dev");
|
||||
createDeviceInfo(deviceObj, inv);
|
||||
@ -256,12 +256,12 @@ void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAb
|
||||
|
||||
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://")) + NetworkSettings.localIP().toString();
|
||||
object[F("mf")] = F("OpenDTU");
|
||||
object[F("mdl")] = inv->typeName();
|
||||
object[F("sw")] = AUTO_GIT_HASH;
|
||||
object["name"] = inv->name();
|
||||
object["ids"] = inv->serialString();
|
||||
object["cu"] = String("http://") + NetworkSettings.localIP().toString();
|
||||
object["mf"] = "OpenDTU";
|
||||
object["mdl"] = inv->typeName();
|
||||
object["sw"] = AUTO_GIT_HASH;
|
||||
}
|
||||
|
||||
void MqttHandleHassClass::publish(const String& subtopic, const String& payload)
|
||||
|
||||
@ -185,7 +185,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||
|
||||
if (inv == nullptr) {
|
||||
MessageOutput.println(F("Inverter not found"));
|
||||
MessageOutput.println("Inverter not found");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -14,11 +14,11 @@ void MqttSettingsClass::NetworkEvent(network_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case network_event::NETWORK_GOT_IP:
|
||||
MessageOutput.println(F("Network connected"));
|
||||
MessageOutput.println("Network connected");
|
||||
performConnect();
|
||||
break;
|
||||
case network_event::NETWORK_DISCONNECTED:
|
||||
MessageOutput.println(F("Network lost connection"));
|
||||
MessageOutput.println("Network lost connection");
|
||||
mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
|
||||
break;
|
||||
default:
|
||||
@ -28,7 +28,7 @@ void MqttSettingsClass::NetworkEvent(network_event event)
|
||||
|
||||
void MqttSettingsClass::onMqttConnect(bool sessionPresent)
|
||||
{
|
||||
MessageOutput.println(F("Connected to MQTT."));
|
||||
MessageOutput.println("Connected to MQTT.");
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Online);
|
||||
|
||||
@ -51,30 +51,30 @@ void MqttSettingsClass::unsubscribe(const String& topic)
|
||||
|
||||
void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason)
|
||||
{
|
||||
MessageOutput.println(F("Disconnected from MQTT."));
|
||||
MessageOutput.println("Disconnected from MQTT.");
|
||||
|
||||
MessageOutput.print(F("Disconnect reason:"));
|
||||
MessageOutput.print("Disconnect reason:");
|
||||
switch (reason) {
|
||||
case espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED:
|
||||
MessageOutput.println(F("TCP_DISCONNECTED"));
|
||||
MessageOutput.println("TCP_DISCONNECTED");
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
|
||||
MessageOutput.println(F("MQTT_UNACCEPTABLE_PROTOCOL_VERSION"));
|
||||
MessageOutput.println("MQTT_UNACCEPTABLE_PROTOCOL_VERSION");
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_IDENTIFIER_REJECTED:
|
||||
MessageOutput.println(F("MQTT_IDENTIFIER_REJECTED"));
|
||||
MessageOutput.println("MQTT_IDENTIFIER_REJECTED");
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_SERVER_UNAVAILABLE:
|
||||
MessageOutput.println(F("MQTT_SERVER_UNAVAILABLE"));
|
||||
MessageOutput.println("MQTT_SERVER_UNAVAILABLE");
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_MALFORMED_CREDENTIALS:
|
||||
MessageOutput.println(F("MQTT_MALFORMED_CREDENTIALS"));
|
||||
MessageOutput.println("MQTT_MALFORMED_CREDENTIALS");
|
||||
break;
|
||||
case espMqttClientTypes::DisconnectReason::MQTT_NOT_AUTHORIZED:
|
||||
MessageOutput.println(F("MQTT_NOT_AUTHORIZED"));
|
||||
MessageOutput.println("MQTT_NOT_AUTHORIZED");
|
||||
break;
|
||||
default:
|
||||
MessageOutput.println(F("Unknown"));
|
||||
MessageOutput.println("Unknown");
|
||||
}
|
||||
mqttReconnectTimer.once(
|
||||
2, +[](MqttSettingsClass* instance) { instance->performConnect(); }, this);
|
||||
@ -82,7 +82,7 @@ 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)
|
||||
{
|
||||
MessageOutput.print(F("Received MQTT message on topic: "));
|
||||
MessageOutput.print("Received MQTT message on topic: ");
|
||||
MessageOutput.println(topic);
|
||||
|
||||
_mqttSubscribeParser.handle_message(properties, topic, payload, len, index, total);
|
||||
@ -97,7 +97,7 @@ void MqttSettingsClass::performConnect()
|
||||
using std::placeholders::_4;
|
||||
using std::placeholders::_5;
|
||||
using std::placeholders::_6;
|
||||
MessageOutput.println(F("Connecting to MQTT..."));
|
||||
MessageOutput.println("Connecting to MQTT...");
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
willTopic = getPrefix() + config.Mqtt_LwtTopic;
|
||||
clientId = NetworkSettings.getApName();
|
||||
|
||||
@ -29,19 +29,19 @@ void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
||||
{
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
MessageOutput.println(F("ETH start"));
|
||||
MessageOutput.println("ETH start");
|
||||
if (_networkMode == network_mode::Ethernet) {
|
||||
raiseEvent(network_event::NETWORK_START);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
MessageOutput.println(F("ETH stop"));
|
||||
MessageOutput.println("ETH stop");
|
||||
if (_networkMode == network_mode::Ethernet) {
|
||||
raiseEvent(network_event::NETWORK_STOP);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
MessageOutput.println(F("ETH connected"));
|
||||
MessageOutput.println("ETH connected");
|
||||
_ethConnected = true;
|
||||
raiseEvent(network_event::NETWORK_CONNECTED);
|
||||
break;
|
||||
@ -52,22 +52,22 @@ void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
MessageOutput.println(F("ETH disconnected"));
|
||||
MessageOutput.println("ETH disconnected");
|
||||
_ethConnected = false;
|
||||
if (_networkMode == network_mode::Ethernet) {
|
||||
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
MessageOutput.println(F("WiFi connected"));
|
||||
MessageOutput.println("WiFi connected");
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
raiseEvent(network_event::NETWORK_CONNECTED);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
MessageOutput.println(F("WiFi disconnected"));
|
||||
MessageOutput.println("WiFi disconnected");
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
MessageOutput.println(F("Try reconnecting"));
|
||||
MessageOutput.println("Try reconnecting");
|
||||
WiFi.reconnect();
|
||||
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
||||
}
|
||||
@ -154,7 +154,7 @@ void NetworkSettingsClass::loop()
|
||||
if (_ethConnected) {
|
||||
if (_networkMode != network_mode::Ethernet) {
|
||||
// Do stuff when switching to Ethernet mode
|
||||
MessageOutput.println(F("Switch to Ethernet mode"));
|
||||
MessageOutput.println("Switch to Ethernet mode");
|
||||
_networkMode = network_mode::Ethernet;
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
setStaticIp();
|
||||
@ -163,7 +163,7 @@ void NetworkSettingsClass::loop()
|
||||
} else
|
||||
if (_networkMode != network_mode::WiFi) {
|
||||
// Do stuff when switching to Ethernet mode
|
||||
MessageOutput.println(F("Switch to WiFi mode"));
|
||||
MessageOutput.println("Switch to WiFi mode");
|
||||
_networkMode = network_mode::WiFi;
|
||||
enableAdminMode();
|
||||
applyConfig();
|
||||
@ -184,7 +184,7 @@ void NetworkSettingsClass::loop()
|
||||
// seconds, disable the internal Access Point
|
||||
if (adminTimeoutCounter > ADMIN_TIMEOUT) {
|
||||
adminEnabled = false;
|
||||
MessageOutput.println(F("Admin mode disabled"));
|
||||
MessageOutput.println("Admin mode disabled");
|
||||
setupMode();
|
||||
}
|
||||
// It's nearly not possible to use the internal AP if the
|
||||
@ -195,16 +195,16 @@ void NetworkSettingsClass::loop()
|
||||
connectRedoTimer = 0;
|
||||
} else {
|
||||
if (connectTimeoutTimer > WIFI_RECONNECT_TIMEOUT && !forceDisconnection) {
|
||||
MessageOutput.print(F("Disable search for AP... "));
|
||||
MessageOutput.print("Disable search for AP... ");
|
||||
WiFi.mode(WIFI_AP);
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
connectRedoTimer = 0;
|
||||
forceDisconnection = true;
|
||||
}
|
||||
if (connectRedoTimer > WIFI_RECONNECT_REDO_TIMEOUT && forceDisconnection) {
|
||||
MessageOutput.print(F("Enable search for AP... "));
|
||||
MessageOutput.print("Enable search for AP... ");
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
applyConfig();
|
||||
connectTimeoutTimer = 0;
|
||||
forceDisconnection = false;
|
||||
@ -222,28 +222,28 @@ void NetworkSettingsClass::applyConfig()
|
||||
if (!strcmp(Configuration.get().WiFi_Ssid, "")) {
|
||||
return;
|
||||
}
|
||||
MessageOutput.print(F("Configuring WiFi STA using "));
|
||||
MessageOutput.print("Configuring WiFi STA using ");
|
||||
if (strcmp(WiFi.SSID().c_str(), Configuration.get().WiFi_Ssid) || strcmp(WiFi.psk().c_str(), Configuration.get().WiFi_Password)) {
|
||||
MessageOutput.print(F("new credentials... "));
|
||||
MessageOutput.print("new credentials... ");
|
||||
WiFi.begin(
|
||||
Configuration.get().WiFi_Ssid,
|
||||
Configuration.get().WiFi_Password);
|
||||
} else {
|
||||
MessageOutput.print(F("existing credentials... "));
|
||||
MessageOutput.print("existing credentials... ");
|
||||
WiFi.begin();
|
||||
}
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
setStaticIp();
|
||||
}
|
||||
|
||||
void NetworkSettingsClass::setHostname()
|
||||
{
|
||||
MessageOutput.print(F("Setting Hostname... "));
|
||||
MessageOutput.print("Setting Hostname... ");
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
if (WiFi.hostname(getHostname())) {
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
} else {
|
||||
MessageOutput.println(F("failed"));
|
||||
MessageOutput.println("failed");
|
||||
}
|
||||
|
||||
// Evil bad hack to get the hostname set up correctly
|
||||
@ -253,9 +253,9 @@ void NetworkSettingsClass::setHostname()
|
||||
}
|
||||
else if (_networkMode == network_mode::Ethernet) {
|
||||
if (ETH.setHostname(getHostname().c_str())) {
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
} else {
|
||||
MessageOutput.println(F("failed"));
|
||||
MessageOutput.println("failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -264,34 +264,34 @@ void NetworkSettingsClass::setStaticIp()
|
||||
{
|
||||
if (_networkMode == network_mode::WiFi) {
|
||||
if (Configuration.get().WiFi_Dhcp) {
|
||||
MessageOutput.print(F("Configuring WiFi STA DHCP IP... "));
|
||||
MessageOutput.print("Configuring WiFi STA DHCP IP... ");
|
||||
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
} else {
|
||||
MessageOutput.print(F("Configuring WiFi STA static IP... "));
|
||||
MessageOutput.print("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));
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
}
|
||||
}
|
||||
else if (_networkMode == network_mode::Ethernet) {
|
||||
if (Configuration.get().WiFi_Dhcp) {
|
||||
MessageOutput.print(F("Configuring Ethernet DHCP IP... "));
|
||||
MessageOutput.print("Configuring Ethernet DHCP IP... ");
|
||||
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
} else {
|
||||
MessageOutput.print(F("Configuring Ethernet static IP... "));
|
||||
MessageOutput.print("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));
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,14 @@
|
||||
#define DISPLAY_RESET 255
|
||||
#endif
|
||||
|
||||
#ifndef LED0
|
||||
#define LED0 -1
|
||||
#endif
|
||||
|
||||
#ifndef LED1
|
||||
#define LED1 -1
|
||||
#endif
|
||||
|
||||
PinMappingClass PinMapping;
|
||||
|
||||
PinMappingClass::PinMappingClass()
|
||||
@ -73,6 +81,8 @@ PinMappingClass::PinMappingClass()
|
||||
_pinMapping.huawei_cs = HUAWEI_PIN_CS;
|
||||
_pinMapping.huawei_irq = HUAWEI_PIN_IRQ;
|
||||
_pinMapping.huawei_power = HUAWEI_PIN_POWER;
|
||||
_pinMapping.led[0] = LED0;
|
||||
_pinMapping.led[1] = LED1;
|
||||
}
|
||||
|
||||
PinMapping_t& PinMappingClass::get()
|
||||
@ -92,7 +102,7 @@ bool PinMappingClass::init(const String& deviceMapping)
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, f);
|
||||
if (error) {
|
||||
MessageOutput.println(F("Failed to read file, using default configuration"));
|
||||
MessageOutput.println("Failed to read file, using default configuration");
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < doc.size(); i++) {
|
||||
@ -138,6 +148,9 @@ bool PinMappingClass::init(const String& deviceMapping)
|
||||
_pinMapping.huawei_cs = doc[i]["huawei"]["cs"] | HUAWEI_PIN_CS;
|
||||
_pinMapping.huawei_power = doc[i]["huawei"]["power"] | HUAWEI_PIN_POWER;
|
||||
|
||||
_pinMapping.led[0] = doc[i]["led"]["led0"] | LED0;
|
||||
_pinMapping.led[1] = doc[i]["led"]["led1"] | LED1;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -147,12 +160,12 @@ bool PinMappingClass::init(const String& deviceMapping)
|
||||
|
||||
bool PinMappingClass::isValidNrf24Config()
|
||||
{
|
||||
return _pinMapping.nrf24_clk > 0
|
||||
&& _pinMapping.nrf24_cs > 0
|
||||
&& _pinMapping.nrf24_en > 0
|
||||
&& _pinMapping.nrf24_irq > 0
|
||||
&& _pinMapping.nrf24_miso > 0
|
||||
&& _pinMapping.nrf24_mosi > 0;
|
||||
return _pinMapping.nrf24_clk >= 0
|
||||
&& _pinMapping.nrf24_cs >= 0
|
||||
&& _pinMapping.nrf24_en >= 0
|
||||
&& _pinMapping.nrf24_irq >= 0
|
||||
&& _pinMapping.nrf24_miso >= 0
|
||||
&& _pinMapping.nrf24_mosi >= 0;
|
||||
}
|
||||
|
||||
bool PinMappingClass::isValidEthConfig()
|
||||
|
||||
@ -17,9 +17,6 @@ PowerLimiterClass PowerLimiter;
|
||||
|
||||
void PowerLimiterClass::init()
|
||||
{
|
||||
_lastCommandSent = 0;
|
||||
_lastLoop = 0;
|
||||
_lastRequestedPowerLimit = 0;
|
||||
}
|
||||
|
||||
void PowerLimiterClass::loop()
|
||||
@ -44,10 +41,16 @@ void PowerLimiterClass::loop()
|
||||
}
|
||||
|
||||
float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_UDC);
|
||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||
float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor);
|
||||
|
||||
if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000) {
|
||||
// If the last inverter update is too old, don't do anything.
|
||||
// If the last inverter update was before the last limit updated, don't do anything.
|
||||
// Also give the Power meter 3 seconds time to recognize power changes because of the last set limit
|
||||
// and also because the Hoymiles MPPT might not react immediately.
|
||||
if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000
|
||||
|| inverter->Statistics()->getLastUpdate() <= _lastLimitSetTime
|
||||
|| PowerMeter.getLastPowerMeterUpdate() <= (_lastLimitSetTime + 3000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -56,7 +59,6 @@ void PowerLimiterClass::loop()
|
||||
dcVoltage, config.PowerLimiter_VoltageStartThreshold, config.PowerLimiter_VoltageStopThreshold, inverter->isProducing());
|
||||
}
|
||||
|
||||
|
||||
while(true) {
|
||||
switch(_plState) {
|
||||
case STATE_DISCOVER:
|
||||
@ -103,7 +105,7 @@ void PowerLimiterClass::loop()
|
||||
}
|
||||
|
||||
if (!canUseDirectSolarPower()) {
|
||||
if (config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGTH)
|
||||
if (config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGHT)
|
||||
_plState = STATE_NORMAL_OPERATION;
|
||||
else
|
||||
_plState = STATE_OFF;
|
||||
@ -120,14 +122,16 @@ void PowerLimiterClass::loop()
|
||||
_plState = STATE_OFF;
|
||||
break;
|
||||
}
|
||||
if (canUseDirectSolarPower() && (config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGTH)) {
|
||||
if (!isStartThresholdReached(inverter) && canUseDirectSolarPower() && (config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGHT)) {
|
||||
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
|
||||
break;
|
||||
}
|
||||
|
||||
// check if grid power consumption is not within the upper and lower threshold of the target consumption
|
||||
if (newPowerLimit >= (config.PowerLimiter_TargetPowerConsumption - config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
||||
newPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis)) {
|
||||
newPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
||||
_lastRequestedPowerLimit >= (config.PowerLimiter_TargetPowerConsumption - config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
||||
_lastRequestedPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis) ) {
|
||||
return;
|
||||
}
|
||||
setNewPowerLimit(inverter, newPowerLimit);;
|
||||
@ -190,7 +194,8 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
|
||||
// the produced power of this inverter has also to be taken into account.
|
||||
// We don't use FLD_PAC from the statistics, because that
|
||||
// data might be too old and unrelieable.
|
||||
newPowerLimit += _lastRequestedPowerLimit;
|
||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||
newPowerLimit += static_cast<int>(acPower);
|
||||
}
|
||||
|
||||
float efficency = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_EFF);
|
||||
@ -247,6 +252,8 @@ void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inver
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit);
|
||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = newPowerLimit;
|
||||
// wait for the next inverter update (+ 3 seconds to make sure the limit got applied)
|
||||
_lastLimitSetTime = millis();
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,7 +270,7 @@ float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr<InverterAbstrac
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||
float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_UDC);
|
||||
|
||||
if (dcVoltage <= 0.0) {
|
||||
|
||||
@ -91,7 +91,7 @@ bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)
|
||||
|
||||
// WebAPI should set the X-Requested-With to prevent browser internal auth dialogs
|
||||
if (!request->hasHeader("X-Requested-With")) {
|
||||
r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\""));
|
||||
r->addHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
|
||||
}
|
||||
request->send(r);
|
||||
|
||||
|
||||
@ -59,11 +59,11 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -72,8 +72,8 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -83,32 +83,32 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("delete"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("delete")].as<bool>() == false) {
|
||||
retMsg[F("message")] = F("Not deleted anything!");
|
||||
retMsg[F("code")] = WebApiError::ConfigNotDeleted;
|
||||
if (root["delete"].as<bool>() == false) {
|
||||
retMsg["message"] = "Not deleted anything!";
|
||||
retMsg["code"] = WebApiError::ConfigNotDeleted;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Configuration resettet. Rebooting now...");
|
||||
retMsg[F("code")] = WebApiError::ConfigSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Configuration resettet. Rebooting now...";
|
||||
retMsg["code"] = WebApiError::ConfigSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -125,7 +125,7 @@ void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
JsonArray data = root.createNestedArray(F("configs"));
|
||||
JsonArray data = root.createNestedArray("configs");
|
||||
|
||||
File rootfs = LittleFS.open("/");
|
||||
File file = rootfs.openNextFile();
|
||||
|
||||
@ -37,37 +37,41 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
|
||||
JsonObject curPin = root.createNestedObject("curPin");
|
||||
curPin[F("name")] = config.Dev_PinMapping;
|
||||
curPin["name"] = config.Dev_PinMapping;
|
||||
|
||||
JsonObject nrfPinObj = curPin.createNestedObject("nrf24");
|
||||
nrfPinObj[F("clk")] = pin.nrf24_clk;
|
||||
nrfPinObj[F("cs")] = pin.nrf24_cs;
|
||||
nrfPinObj[F("en")] = pin.nrf24_en;
|
||||
nrfPinObj[F("irq")] = pin.nrf24_irq;
|
||||
nrfPinObj[F("miso")] = pin.nrf24_miso;
|
||||
nrfPinObj[F("mosi")] = pin.nrf24_mosi;
|
||||
nrfPinObj["clk"] = pin.nrf24_clk;
|
||||
nrfPinObj["cs"] = pin.nrf24_cs;
|
||||
nrfPinObj["en"] = pin.nrf24_en;
|
||||
nrfPinObj["irq"] = pin.nrf24_irq;
|
||||
nrfPinObj["miso"] = pin.nrf24_miso;
|
||||
nrfPinObj["mosi"] = pin.nrf24_mosi;
|
||||
|
||||
JsonObject ethPinObj = curPin.createNestedObject("eth");
|
||||
ethPinObj[F("enabled")] = pin.eth_enabled;
|
||||
ethPinObj[F("phy_addr")] = pin.eth_phy_addr;
|
||||
ethPinObj[F("power")] = pin.eth_power;
|
||||
ethPinObj[F("mdc")] = pin.eth_mdc;
|
||||
ethPinObj[F("mdio")] = pin.eth_mdio;
|
||||
ethPinObj[F("type")] = pin.eth_type;
|
||||
ethPinObj[F("clk_mode")] = pin.eth_clk_mode;
|
||||
ethPinObj["enabled"] = pin.eth_enabled;
|
||||
ethPinObj["phy_addr"] = pin.eth_phy_addr;
|
||||
ethPinObj["power"] = pin.eth_power;
|
||||
ethPinObj["mdc"] = pin.eth_mdc;
|
||||
ethPinObj["mdio"] = pin.eth_mdio;
|
||||
ethPinObj["type"] = pin.eth_type;
|
||||
ethPinObj["clk_mode"] = pin.eth_clk_mode;
|
||||
|
||||
JsonObject displayPinObj = curPin.createNestedObject("display");
|
||||
displayPinObj[F("type")] = pin.display_type;
|
||||
displayPinObj[F("data")] = pin.display_data;
|
||||
displayPinObj[F("clk")] = pin.display_clk;
|
||||
displayPinObj[F("cs")] = pin.display_cs;
|
||||
displayPinObj[F("reset")] = pin.display_reset;
|
||||
displayPinObj["type"] = pin.display_type;
|
||||
displayPinObj["data"] = pin.display_data;
|
||||
displayPinObj["clk"] = pin.display_clk;
|
||||
displayPinObj["cs"] = pin.display_cs;
|
||||
displayPinObj["reset"] = pin.display_reset;
|
||||
|
||||
JsonObject ledPinObj = curPin.createNestedObject("led");
|
||||
ledPinObj["led0"] = pin.led[0];
|
||||
ledPinObj["led1"] = pin.led[1];
|
||||
|
||||
JsonObject display = root.createNestedObject("display");
|
||||
display[F("rotation")] = config.Display_Rotation;
|
||||
display[F("power_safe")] = config.Display_PowerSafe;
|
||||
display[F("screensaver")] = config.Display_ScreenSaver;
|
||||
display[F("contrast")] = config.Display_Contrast;
|
||||
display["rotation"] = config.Display_Rotation;
|
||||
display["power_safe"] = config.Display_PowerSafe;
|
||||
display["screensaver"] = config.Display_ScreenSaver;
|
||||
display["contrast"] = config.Display_Contrast;
|
||||
|
||||
JsonObject victronPinObj = curPin.createNestedObject("victron");
|
||||
victronPinObj[F("rx")] = pin.victron_rx;
|
||||
@ -97,11 +101,11 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -110,8 +114,8 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -121,38 +125,38 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("curPin") || root.containsKey("display"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("curPin")][F("name")].as<String>().length() == 0 || root[F("curPin")][F("name")].as<String>().length() > DEV_MAX_MAPPING_NAME_STRLEN) {
|
||||
retMsg[F("message")] = F("Pin mapping must between 1 and " STR(DEV_MAX_MAPPING_NAME_STRLEN) " characters long!");
|
||||
retMsg[F("code")] = WebApiError::HardwarePinMappingLength;
|
||||
retMsg[F("param")][F("max")] = DEV_MAX_MAPPING_NAME_STRLEN;
|
||||
if (root["curPin"]["name"].as<String>().length() == 0 || root["curPin"]["name"].as<String>().length() > DEV_MAX_MAPPING_NAME_STRLEN) {
|
||||
retMsg["message"] = "Pin mapping must between 1 and " STR(DEV_MAX_MAPPING_NAME_STRLEN) " characters long!";
|
||||
retMsg["code"] = WebApiError::HardwarePinMappingLength;
|
||||
retMsg["param"]["max"] = DEV_MAX_MAPPING_NAME_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
bool performRestart = root[F("curPin")][F("name")].as<String>() != config.Dev_PinMapping;
|
||||
bool performRestart = root["curPin"]["name"].as<String>() != config.Dev_PinMapping;
|
||||
|
||||
strlcpy(config.Dev_PinMapping, root[F("curPin")][F("name")].as<String>().c_str(), sizeof(config.Dev_PinMapping));
|
||||
config.Display_Rotation = root[F("display")][F("rotation")].as<uint8_t>();
|
||||
config.Display_PowerSafe = root[F("display")][F("power_safe")].as<bool>();
|
||||
config.Display_ScreenSaver = root[F("display")][F("screensaver")].as<bool>();
|
||||
config.Display_Contrast = root[F("display")][F("contrast")].as<uint8_t>();
|
||||
strlcpy(config.Dev_PinMapping, root["curPin"]["name"].as<String>().c_str(), sizeof(config.Dev_PinMapping));
|
||||
config.Display_Rotation = root["display"]["rotation"].as<uint8_t>();
|
||||
config.Display_PowerSafe = root["display"]["power_safe"].as<bool>();
|
||||
config.Display_ScreenSaver = root["display"]["screensaver"].as<bool>();
|
||||
config.Display_Contrast = root["display"]["contrast"].as<uint8_t>();
|
||||
|
||||
Display.setOrientation(config.Display_Rotation);
|
||||
Display.enablePowerSafe = config.Display_PowerSafe;
|
||||
@ -161,9 +165,9 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Settings saved!";
|
||||
retMsg["code"] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -34,18 +34,18 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
|
||||
auto inv = Hoymiles.getInverterByPos(i);
|
||||
|
||||
JsonObject devInfoObj = root[inv->serialString()].createNestedObject();
|
||||
devInfoObj[F("valid_data")] = inv->DevInfo()->getLastUpdate() > 0;
|
||||
devInfoObj[F("fw_bootloader_version")] = inv->DevInfo()->getFwBootloaderVersion();
|
||||
devInfoObj[F("fw_build_version")] = inv->DevInfo()->getFwBuildVersion();
|
||||
devInfoObj[F("hw_part_number")] = inv->DevInfo()->getHwPartNumber();
|
||||
devInfoObj[F("hw_version")] = inv->DevInfo()->getHwVersion();
|
||||
devInfoObj[F("hw_model_name")] = inv->DevInfo()->getHwModelName();
|
||||
devInfoObj[F("max_power")] = inv->DevInfo()->getMaxPower();
|
||||
devInfoObj["valid_data"] = inv->DevInfo()->getLastUpdate() > 0;
|
||||
devInfoObj["fw_bootloader_version"] = inv->DevInfo()->getFwBootloaderVersion();
|
||||
devInfoObj["fw_build_version"] = inv->DevInfo()->getFwBuildVersion();
|
||||
devInfoObj["hw_part_number"] = inv->DevInfo()->getHwPartNumber();
|
||||
devInfoObj["hw_version"] = inv->DevInfo()->getHwVersion();
|
||||
devInfoObj["hw_model_name"] = inv->DevInfo()->getHwModelName();
|
||||
devInfoObj["max_power"] = inv->DevInfo()->getMaxPower();
|
||||
|
||||
char timebuffer[32];
|
||||
const time_t t = inv->DevInfo()->getFwBuildDateTime();
|
||||
std::strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", gmtime(&t));
|
||||
devInfoObj[F("fw_build_datetime")] = String(timebuffer);
|
||||
devInfoObj["fw_build_datetime"] = String(timebuffer);
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
|
||||
@ -38,9 +38,9 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
|
||||
snprintf(buffer, sizeof(buffer), "%0x%08x",
|
||||
((uint32_t)((config.Dtu_Serial >> 32) & 0xFFFFFFFF)),
|
||||
((uint32_t)(config.Dtu_Serial & 0xFFFFFFFF)));
|
||||
root[F("dtu_serial")] = buffer;
|
||||
root[F("dtu_pollinterval")] = config.Dtu_PollInterval;
|
||||
root[F("dtu_palevel")] = config.Dtu_PaLevel;
|
||||
root["dtu_serial"] = buffer;
|
||||
root["dtu_pollinterval"] = config.Dtu_PollInterval;
|
||||
root["dtu_palevel"] = config.Dtu_PaLevel;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -54,11 +54,11 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -67,8 +67,8 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -78,40 +78,40 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("dtu_serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial cannot be zero!");
|
||||
retMsg[F("code")] = WebApiError::DtuSerialZero;
|
||||
if (root["dtu_serial"].as<uint64_t>() == 0) {
|
||||
retMsg["message"] = "Serial cannot be zero!";
|
||||
retMsg["code"] = WebApiError::DtuSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("dtu_pollinterval")].as<uint32_t>() == 0) {
|
||||
retMsg[F("message")] = F("Poll interval must be greater zero!");
|
||||
retMsg[F("code")] = WebApiError::DtuPollZero;
|
||||
if (root["dtu_pollinterval"].as<uint32_t>() == 0) {
|
||||
retMsg["message"] = "Poll interval must be greater zero!";
|
||||
retMsg["code"] = WebApiError::DtuPollZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("dtu_palevel")].as<uint8_t>() > 3) {
|
||||
retMsg[F("message")] = F("Invalid power level setting!");
|
||||
retMsg[F("code")] = WebApiError::DtuInvalidPowerLevel;
|
||||
if (root["dtu_palevel"].as<uint8_t>() > 3) {
|
||||
retMsg["message"] = "Invalid power level setting!";
|
||||
retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -120,14 +120,14 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
// Interpret the string as a hex value and convert it to uint64_t
|
||||
config.Dtu_Serial = strtoll(root[F("dtu_serial")].as<String>().c_str(), NULL, 16);
|
||||
config.Dtu_PollInterval = root[F("dtu_pollinterval")].as<uint32_t>();
|
||||
config.Dtu_PaLevel = root[F("dtu_palevel")].as<uint8_t>();
|
||||
config.Dtu_Serial = strtoll(root["dtu_serial"].as<String>().c_str(), NULL, 16);
|
||||
config.Dtu_PollInterval = root["dtu_pollinterval"].as<uint32_t>();
|
||||
config.Dtu_PaLevel = root["dtu_palevel"].as<uint8_t>();
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Settings saved!";
|
||||
retMsg["code"] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -43,7 +43,7 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
|
||||
uint8_t logEntryCount = inv->EventLog()->getEntryCount();
|
||||
|
||||
root[serial]["count"] = logEntryCount;
|
||||
JsonArray eventsArray = root[serial].createNestedArray(F("events"));
|
||||
JsonArray eventsArray = root[serial].createNestedArray("events");
|
||||
|
||||
for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) {
|
||||
JsonObject eventsObject = eventsArray.createNestedObject();
|
||||
@ -51,10 +51,10 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
|
||||
AlarmLogEntry_t entry;
|
||||
inv->EventLog()->getLogEntry(logEntry, &entry);
|
||||
|
||||
eventsObject[F("message_id")] = entry.MessageId;
|
||||
eventsObject[F("message")] = entry.Message;
|
||||
eventsObject[F("start_time")] = entry.StartTime;
|
||||
eventsObject[F("end_time")] = entry.EndTime;
|
||||
eventsObject["message_id"] = entry.MessageId;
|
||||
eventsObject["message"] = entry.Message;
|
||||
eventsObject["start_time"] = entry.StartTime;
|
||||
eventsObject["end_time"] = entry.EndTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -35,34 +35,34 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096U);
|
||||
JsonObject root = response->getRoot();
|
||||
JsonArray data = root.createNestedArray(F("inverter"));
|
||||
JsonArray data = root.createNestedArray("inverter");
|
||||
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||
if (config.Inverter[i].Serial > 0) {
|
||||
JsonObject obj = data.createNestedObject();
|
||||
obj[F("id")] = i;
|
||||
obj[F("name")] = String(config.Inverter[i].Name);
|
||||
obj["id"] = i;
|
||||
obj["name"] = String(config.Inverter[i].Name);
|
||||
|
||||
// Inverter Serial is read as HEX
|
||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||
snprintf(buffer, sizeof(buffer), "%0x%08x",
|
||||
((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)),
|
||||
((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF)));
|
||||
obj[F("serial")] = buffer;
|
||||
obj[F("poll_enable")] = config.Inverter[i].Poll_Enable;
|
||||
obj[F("poll_enable_night")] = config.Inverter[i].Poll_Enable_Night;
|
||||
obj[F("command_enable")] = config.Inverter[i].Command_Enable;
|
||||
obj[F("command_enable_night")] = config.Inverter[i].Command_Enable_Night;
|
||||
obj["serial"] = buffer;
|
||||
obj["poll_enable"] = config.Inverter[i].Poll_Enable;
|
||||
obj["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night;
|
||||
obj["command_enable"] = config.Inverter[i].Command_Enable;
|
||||
obj["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
|
||||
|
||||
auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial);
|
||||
uint8_t max_channels;
|
||||
if (inv == nullptr) {
|
||||
obj[F("type")] = F("Unknown");
|
||||
obj["type"] = "Unknown";
|
||||
max_channels = INV_MAX_CHAN_COUNT;
|
||||
} else {
|
||||
obj[F("type")] = inv->typeName();
|
||||
obj["type"] = inv->typeName();
|
||||
max_channels = inv->Statistics()->getChannelsByType(TYPE_DC).size();
|
||||
}
|
||||
|
||||
@ -88,11 +88,11 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -101,8 +101,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -112,33 +112,33 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("serial") && root.containsKey("name"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial must be a number > 0!");
|
||||
retMsg[F("code")] = WebApiError::InverterSerialZero;
|
||||
if (root["serial"].as<uint64_t>() == 0) {
|
||||
retMsg["message"] = "Serial must be a number > 0!";
|
||||
retMsg["code"] = WebApiError::InverterSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["name"].as<String>().length() == 0 || root["name"].as<String>().length() > INV_MAX_NAME_STRLEN) {
|
||||
retMsg["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!";
|
||||
retMsg["code"] = WebApiError::InverterNameLength;
|
||||
retMsg["param"]["max"] = INV_MAX_NAME_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -147,23 +147,23 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||
INVERTER_CONFIG_T* inverter = Configuration.getFreeInverterSlot();
|
||||
|
||||
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;
|
||||
retMsg["message"] = "Only " STR(INV_MAX_COUNT) " inverters are supported!";
|
||||
retMsg["code"] = WebApiError::InverterCount;
|
||||
retMsg["param"]["max"] = INV_MAX_COUNT;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpret the string as a hex value and convert it to uint64_t
|
||||
inverter->Serial = strtoll(root[F("serial")].as<String>().c_str(), NULL, 16);
|
||||
inverter->Serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||
|
||||
strncpy(inverter->Name, root[F("name")].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
||||
strncpy(inverter->Name, root["name"].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Inverter created!");
|
||||
retMsg[F("code")] = WebApiError::InverterAdded;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Inverter created!";
|
||||
retMsg["code"] = WebApiError::InverterAdded;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -187,11 +187,11 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -200,8 +200,8 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -211,82 +211,82 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("id")].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
||||
retMsg[F("message")] = F("Invalid ID specified!");
|
||||
retMsg[F("code")] = WebApiError::InverterInvalidId;
|
||||
if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
||||
retMsg["message"] = "Invalid ID specified!";
|
||||
retMsg["code"] = WebApiError::InverterInvalidId;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial must be a number > 0!");
|
||||
retMsg[F("code")] = WebApiError::InverterSerialZero;
|
||||
if (root["serial"].as<uint64_t>() == 0) {
|
||||
retMsg["message"] = "Serial must be a number > 0!";
|
||||
retMsg["code"] = WebApiError::InverterSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["name"].as<String>().length() == 0 || root["name"].as<String>().length() > INV_MAX_NAME_STRLEN) {
|
||||
retMsg["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!";
|
||||
retMsg["code"] = WebApiError::InverterNameLength;
|
||||
retMsg["param"]["max"] = INV_MAX_NAME_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonArray channelArray = root[F("channel")].as<JsonArray>();
|
||||
JsonArray channelArray = root["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;
|
||||
retMsg["message"] = "Invalid amount of max channel setting given!";
|
||||
retMsg["code"] = WebApiError::InverterInvalidMaxChannel;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root[F("id")].as<uint8_t>()];
|
||||
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root["id"].as<uint8_t>()];
|
||||
|
||||
uint64_t new_serial = strtoll(root[F("serial")].as<String>().c_str(), NULL, 16);
|
||||
uint64_t new_serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||
uint64_t old_serial = inverter.Serial;
|
||||
|
||||
// Interpret the string as a hex value and convert it to uint64_t
|
||||
inverter.Serial = new_serial;
|
||||
strncpy(inverter.Name, root[F("name")].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
||||
strncpy(inverter.Name, root["name"].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
||||
|
||||
uint8_t arrayCount = 0;
|
||||
for (JsonVariant channel : channelArray) {
|
||||
inverter.channel[arrayCount].MaxChannelPower = channel[F("max_power")].as<uint16_t>();
|
||||
inverter.channel[arrayCount].YieldTotalOffset = channel[F("yield_total_offset")].as<float>();
|
||||
strncpy(inverter.channel[arrayCount].Name, channel[F("name")] | "", sizeof(inverter.channel[arrayCount].Name));
|
||||
inverter.Poll_Enable = root[F("poll_enable")] | true;
|
||||
inverter.Poll_Enable_Night = root[F("poll_enable_night")] | true;
|
||||
inverter.Command_Enable = root[F("command_enable")] | true;
|
||||
inverter.Command_Enable_Night = root[F("command_enable_night")] | true;
|
||||
inverter.channel[arrayCount].MaxChannelPower = channel["max_power"].as<uint16_t>();
|
||||
inverter.channel[arrayCount].YieldTotalOffset = channel["yield_total_offset"].as<float>();
|
||||
strncpy(inverter.channel[arrayCount].Name, channel["name"] | "", sizeof(inverter.channel[arrayCount].Name));
|
||||
inverter.Poll_Enable = root["poll_enable"] | true;
|
||||
inverter.Poll_Enable_Night = root["poll_enable_night"] | true;
|
||||
inverter.Command_Enable = root["command_enable"] | true;
|
||||
inverter.Command_Enable_Night = root["command_enable_night"] | true;
|
||||
|
||||
arrayCount++;
|
||||
}
|
||||
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("code")] = WebApiError::InverterChanged;
|
||||
retMsg[F("message")] = F("Inverter changed!");
|
||||
retMsg["type"] = "success";
|
||||
retMsg["code"] = WebApiError::InverterChanged;
|
||||
retMsg["message"] = "Inverter changed!";
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -325,11 +325,11 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -338,8 +338,8 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -349,30 +349,30 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("id"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("id")].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
||||
retMsg[F("message")] = F("Invalid ID specified!");
|
||||
retMsg[F("code")] = WebApiError::InverterInvalidId;
|
||||
if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
||||
retMsg["message"] = "Invalid ID specified!";
|
||||
retMsg["code"] = WebApiError::InverterInvalidId;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t inverter_id = root[F("id")].as<uint8_t>();
|
||||
uint8_t inverter_id = root["id"].as<uint8_t>();
|
||||
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[inverter_id];
|
||||
|
||||
Hoymiles.removeInverterBySerial(inverter.Serial);
|
||||
@ -381,9 +381,9 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
||||
strncpy(inverter.Name, "", sizeof(inverter.Name));
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Inverter deleted!");
|
||||
retMsg[F("code")] = WebApiError::InverterDeleted;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Inverter deleted!";
|
||||
retMsg["code"] = WebApiError::InverterDeleted;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -63,11 +63,11 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -76,8 +76,8 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -87,8 +87,8 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -97,50 +97,50 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
if (!(root.containsKey("serial")
|
||||
&& root.containsKey("limit_value")
|
||||
&& root.containsKey("limit_type"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial must be a number > 0!");
|
||||
retMsg[F("code")] = WebApiError::LimitSerialZero;
|
||||
if (root["serial"].as<uint64_t>() == 0) {
|
||||
retMsg["message"] = "Serial must be a number > 0!";
|
||||
retMsg["code"] = WebApiError::LimitSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["limit_value"].as<uint16_t>() == 0 || root["limit_value"].as<uint16_t>() > 1500) {
|
||||
retMsg["message"] = "Limit must between 1 and 1500!";
|
||||
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
||||
retMsg["param"]["max"] = 1500;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!((root[F("limit_type")].as<uint16_t>() == PowerLimitControlType::AbsolutNonPersistent)
|
||||
|| (root[F("limit_type")].as<uint16_t>() == PowerLimitControlType::AbsolutPersistent)
|
||||
|| (root[F("limit_type")].as<uint16_t>() == PowerLimitControlType::RelativNonPersistent)
|
||||
|| (root[F("limit_type")].as<uint16_t>() == PowerLimitControlType::RelativPersistent))) {
|
||||
if (!((root["limit_type"].as<uint16_t>() == PowerLimitControlType::AbsolutNonPersistent)
|
||||
|| (root["limit_type"].as<uint16_t>() == PowerLimitControlType::AbsolutPersistent)
|
||||
|| (root["limit_type"].as<uint16_t>() == PowerLimitControlType::RelativNonPersistent)
|
||||
|| (root["limit_type"].as<uint16_t>() == PowerLimitControlType::RelativPersistent))) {
|
||||
|
||||
retMsg[F("message")] = F("Invalid type specified!");
|
||||
retMsg[F("code")] = WebApiError::LimitInvalidType;
|
||||
retMsg["message"] = "Invalid type specified!";
|
||||
retMsg["code"] = WebApiError::LimitInvalidType;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t serial = strtoll(root[F("serial")].as<String>().c_str(), NULL, 16);
|
||||
uint16_t limit = root[F("limit_value")].as<uint16_t>();
|
||||
PowerLimitControlType type = root[F("limit_type")].as<PowerLimitControlType>();
|
||||
uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||
uint16_t limit = root["limit_value"].as<uint16_t>();
|
||||
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
||||
|
||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||
if (inv == nullptr) {
|
||||
retMsg[F("message")] = F("Invalid inverter specified!");
|
||||
retMsg[F("code")] = WebApiError::LimitInvalidInverter;
|
||||
retMsg["message"] = "Invalid inverter specified!";
|
||||
retMsg["code"] = WebApiError::LimitInvalidInverter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -148,9 +148,9 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||
|
||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), limit, type);
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Settings saved!";
|
||||
retMsg["code"] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -29,11 +29,11 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -42,8 +42,8 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -53,25 +53,25 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("reboot"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("reboot")].as<bool>()) {
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Reboot triggered!");
|
||||
retMsg[F("code")] = WebApiError::MaintenanceRebootTriggered;
|
||||
if (root["reboot"].as<bool>()) {
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Reboot triggered!";
|
||||
retMsg["code"] = WebApiError::MaintenanceRebootTriggered;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -80,8 +80,8 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
yield();
|
||||
ESP.restart();
|
||||
} else {
|
||||
retMsg[F("message")] = F("Reboot cancled!");
|
||||
retMsg[F("code")] = WebApiError::MaintenanceRebootCancled;
|
||||
retMsg["message"] = "Reboot cancled!";
|
||||
retMsg["code"] = WebApiError::MaintenanceRebootCancled;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -39,22 +39,22 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request)
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("mqtt_enabled")] = config.Mqtt_Enabled;
|
||||
root[F("mqtt_hostname")] = config.Mqtt_Hostname;
|
||||
root[F("mqtt_port")] = config.Mqtt_Port;
|
||||
root[F("mqtt_username")] = config.Mqtt_Username;
|
||||
root[F("mqtt_topic")] = config.Mqtt_Topic;
|
||||
root[F("mqtt_connected")] = MqttSettings.getConnected();
|
||||
root[F("mqtt_retain")] = config.Mqtt_Retain;
|
||||
root[F("mqtt_tls")] = config.Mqtt_Tls;
|
||||
root[F("mqtt_root_ca_cert_info")] = getRootCaCertInfo(config.Mqtt_RootCaCert);
|
||||
root[F("mqtt_lwt_topic")] = String(config.Mqtt_Topic) + config.Mqtt_LwtTopic;
|
||||
root[F("mqtt_publish_interval")] = config.Mqtt_PublishInterval;
|
||||
root[F("mqtt_hass_enabled")] = config.Mqtt_Hass_Enabled;
|
||||
root[F("mqtt_hass_expire")] = config.Mqtt_Hass_Expire;
|
||||
root[F("mqtt_hass_retain")] = config.Mqtt_Hass_Retain;
|
||||
root[F("mqtt_hass_topic")] = config.Mqtt_Hass_Topic;
|
||||
root[F("mqtt_hass_individualpanels")] = config.Mqtt_Hass_IndividualPanels;
|
||||
root["mqtt_enabled"] = config.Mqtt_Enabled;
|
||||
root["mqtt_hostname"] = config.Mqtt_Hostname;
|
||||
root["mqtt_port"] = config.Mqtt_Port;
|
||||
root["mqtt_username"] = config.Mqtt_Username;
|
||||
root["mqtt_topic"] = config.Mqtt_Topic;
|
||||
root["mqtt_connected"] = MqttSettings.getConnected();
|
||||
root["mqtt_retain"] = config.Mqtt_Retain;
|
||||
root["mqtt_tls"] = config.Mqtt_Tls;
|
||||
root["mqtt_root_ca_cert_info"] = getRootCaCertInfo(config.Mqtt_RootCaCert);
|
||||
root["mqtt_lwt_topic"] = String(config.Mqtt_Topic) + config.Mqtt_LwtTopic;
|
||||
root["mqtt_publish_interval"] = config.Mqtt_PublishInterval;
|
||||
root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled;
|
||||
root["mqtt_hass_expire"] = config.Mqtt_Hass_Expire;
|
||||
root["mqtt_hass_retain"] = config.Mqtt_Hass_Retain;
|
||||
root["mqtt_hass_topic"] = config.Mqtt_Hass_Topic;
|
||||
root["mqtt_hass_individualpanels"] = config.Mqtt_Hass_IndividualPanels;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -70,24 +70,24 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("mqtt_enabled")] = config.Mqtt_Enabled;
|
||||
root[F("mqtt_hostname")] = config.Mqtt_Hostname;
|
||||
root[F("mqtt_port")] = config.Mqtt_Port;
|
||||
root[F("mqtt_username")] = config.Mqtt_Username;
|
||||
root[F("mqtt_password")] = config.Mqtt_Password;
|
||||
root[F("mqtt_topic")] = config.Mqtt_Topic;
|
||||
root[F("mqtt_retain")] = config.Mqtt_Retain;
|
||||
root[F("mqtt_tls")] = config.Mqtt_Tls;
|
||||
root[F("mqtt_root_ca_cert")] = config.Mqtt_RootCaCert;
|
||||
root[F("mqtt_lwt_topic")] = config.Mqtt_LwtTopic;
|
||||
root[F("mqtt_lwt_online")] = config.Mqtt_LwtValue_Online;
|
||||
root[F("mqtt_lwt_offline")] = config.Mqtt_LwtValue_Offline;
|
||||
root[F("mqtt_publish_interval")] = config.Mqtt_PublishInterval;
|
||||
root[F("mqtt_hass_enabled")] = config.Mqtt_Hass_Enabled;
|
||||
root[F("mqtt_hass_expire")] = config.Mqtt_Hass_Expire;
|
||||
root[F("mqtt_hass_retain")] = config.Mqtt_Hass_Retain;
|
||||
root[F("mqtt_hass_topic")] = config.Mqtt_Hass_Topic;
|
||||
root[F("mqtt_hass_individualpanels")] = config.Mqtt_Hass_IndividualPanels;
|
||||
root["mqtt_enabled"] = config.Mqtt_Enabled;
|
||||
root["mqtt_hostname"] = config.Mqtt_Hostname;
|
||||
root["mqtt_port"] = config.Mqtt_Port;
|
||||
root["mqtt_username"] = config.Mqtt_Username;
|
||||
root["mqtt_password"] = config.Mqtt_Password;
|
||||
root["mqtt_topic"] = config.Mqtt_Topic;
|
||||
root["mqtt_retain"] = config.Mqtt_Retain;
|
||||
root["mqtt_tls"] = config.Mqtt_Tls;
|
||||
root["mqtt_root_ca_cert"] = config.Mqtt_RootCaCert;
|
||||
root["mqtt_lwt_topic"] = config.Mqtt_LwtTopic;
|
||||
root["mqtt_lwt_online"] = config.Mqtt_LwtValue_Online;
|
||||
root["mqtt_lwt_offline"] = config.Mqtt_LwtValue_Offline;
|
||||
root["mqtt_publish_interval"] = config.Mqtt_PublishInterval;
|
||||
root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled;
|
||||
root["mqtt_hass_expire"] = config.Mqtt_Hass_Expire;
|
||||
root["mqtt_hass_retain"] = config.Mqtt_Hass_Retain;
|
||||
root["mqtt_hass_topic"] = config.Mqtt_Hass_Topic;
|
||||
root["mqtt_hass_individualpanels"] = config.Mqtt_Hass_IndividualPanels;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -101,11 +101,11 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -114,8 +114,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -125,8 +125,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -149,139 +149,139 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
&& root.containsKey("mqtt_hass_retain")
|
||||
&& root.containsKey("mqtt_hass_topic")
|
||||
&& root.containsKey("mqtt_hass_individualpanels"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_enabled"].as<bool>()) {
|
||||
if (root["mqtt_hostname"].as<String>().length() == 0 || root["mqtt_hostname"].as<String>().length() > MQTT_MAX_HOSTNAME_STRLEN) {
|
||||
retMsg["message"] = "MqTT Server must between 1 and " STR(MQTT_MAX_HOSTNAME_STRLEN) " characters long!";
|
||||
retMsg["code"] = WebApiError::MqttHostnameLength;
|
||||
retMsg["param"]["max"] = MQTT_MAX_HOSTNAME_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_username"].as<String>().length() > MQTT_MAX_USERNAME_STRLEN) {
|
||||
retMsg["message"] = "Username must not longer then " STR(MQTT_MAX_USERNAME_STRLEN) " characters!";
|
||||
retMsg["code"] = WebApiError::MqttUsernameLength;
|
||||
retMsg["param"]["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;
|
||||
if (root["mqtt_password"].as<String>().length() > MQTT_MAX_PASSWORD_STRLEN) {
|
||||
retMsg["message"] = "Password must not longer then " STR(MQTT_MAX_PASSWORD_STRLEN) " characters!";
|
||||
retMsg["code"] = WebApiError::MqttPasswordLength;
|
||||
retMsg["param"]["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;
|
||||
if (root["mqtt_topic"].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
|
||||
retMsg["message"] = "Topic must not longer then " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
||||
retMsg["code"] = WebApiError::MqttTopicLength;
|
||||
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("mqtt_topic")].as<String>().indexOf(' ') != -1) {
|
||||
retMsg[F("message")] = F("Topic must not contain space characters!");
|
||||
retMsg[F("code")] = WebApiError::MqttTopicCharacter;
|
||||
if (root["mqtt_topic"].as<String>().indexOf(' ') != -1) {
|
||||
retMsg["message"] = "Topic must not contain space characters!";
|
||||
retMsg["code"] = WebApiError::MqttTopicCharacter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!root[F("mqtt_topic")].as<String>().endsWith("/")) {
|
||||
retMsg[F("message")] = F("Topic must end with slash (/)!");
|
||||
retMsg[F("code")] = WebApiError::MqttTopicTrailingSlash;
|
||||
if (!root["mqtt_topic"].as<String>().endsWith("/")) {
|
||||
retMsg["message"] = "Topic must end with slash (/)!";
|
||||
retMsg["code"] = WebApiError::MqttTopicTrailingSlash;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_port"].as<uint>() == 0 || root["mqtt_port"].as<uint>() > 65535) {
|
||||
retMsg["message"] = "Port must be a number between 1 and 65535!";
|
||||
retMsg["code"] = WebApiError::MqttPort;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_root_ca_cert"].as<String>().length() > MQTT_MAX_ROOT_CA_CERT_STRLEN) {
|
||||
retMsg["message"] = "Certificate must not longer then " STR(MQTT_MAX_ROOT_CA_CERT_STRLEN) " characters!";
|
||||
retMsg["code"] = WebApiError::MqttCertificateLength;
|
||||
retMsg["param"]["max"] = MQTT_MAX_ROOT_CA_CERT_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_lwt_topic"].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
|
||||
retMsg["message"] = "LWT topic must not longer then " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
||||
retMsg["code"] = WebApiError::MqttLwtTopicLength;
|
||||
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_lwt_topic"].as<String>().indexOf(' ') != -1) {
|
||||
retMsg["message"] = "LWT topic must not contain space characters!";
|
||||
retMsg["code"] = WebApiError::MqttLwtTopicCharacter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_lwt_online"].as<String>().length() > MQTT_MAX_LWTVALUE_STRLEN) {
|
||||
retMsg["message"] = "LWT online value must not longer then " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!";
|
||||
retMsg["code"] = WebApiError::MqttLwtOnlineLength;
|
||||
retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_lwt_offline"].as<String>().length() > MQTT_MAX_LWTVALUE_STRLEN) {
|
||||
retMsg["message"] = "LWT offline value must not longer then " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!";
|
||||
retMsg["code"] = WebApiError::MqttLwtOfflineLength;
|
||||
retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_publish_interval"].as<uint32_t>() < 5 || root["mqtt_publish_interval"].as<uint32_t>() > 65535) {
|
||||
retMsg["message"] = "Publish interval must be a number between 5 and 65535!";
|
||||
retMsg["code"] = WebApiError::MqttPublishInterval;
|
||||
retMsg["param"]["min"] = 5;
|
||||
retMsg["param"]["max"] = 65535;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_hass_enabled"].as<bool>()) {
|
||||
if (root["mqtt_hass_topic"].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
|
||||
retMsg["message"] = "Hass topic must not longer then " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
||||
retMsg["code"] = WebApiError::MqttHassTopicLength;
|
||||
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["mqtt_hass_topic"].as<String>().indexOf(' ') != -1) {
|
||||
retMsg["message"] = "Hass topic must not contain space characters!";
|
||||
retMsg["code"] = WebApiError::MqttHassTopicCharacter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -290,29 +290,29 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
}
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
config.Mqtt_Enabled = root[F("mqtt_enabled")].as<bool>();
|
||||
config.Mqtt_Retain = root[F("mqtt_retain")].as<bool>();
|
||||
config.Mqtt_Tls = root[F("mqtt_tls")].as<bool>();
|
||||
strlcpy(config.Mqtt_RootCaCert, root[F("mqtt_root_ca_cert")].as<String>().c_str(), sizeof(config.Mqtt_RootCaCert));
|
||||
config.Mqtt_Port = root[F("mqtt_port")].as<uint>();
|
||||
strlcpy(config.Mqtt_Hostname, root[F("mqtt_hostname")].as<String>().c_str(), sizeof(config.Mqtt_Hostname));
|
||||
strlcpy(config.Mqtt_Username, root[F("mqtt_username")].as<String>().c_str(), sizeof(config.Mqtt_Username));
|
||||
strlcpy(config.Mqtt_Password, root[F("mqtt_password")].as<String>().c_str(), sizeof(config.Mqtt_Password));
|
||||
strlcpy(config.Mqtt_Topic, root[F("mqtt_topic")].as<String>().c_str(), sizeof(config.Mqtt_Topic));
|
||||
strlcpy(config.Mqtt_LwtTopic, root[F("mqtt_lwt_topic")].as<String>().c_str(), sizeof(config.Mqtt_LwtTopic));
|
||||
strlcpy(config.Mqtt_LwtValue_Online, root[F("mqtt_lwt_online")].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Online));
|
||||
strlcpy(config.Mqtt_LwtValue_Offline, root[F("mqtt_lwt_offline")].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Offline));
|
||||
config.Mqtt_PublishInterval = root[F("mqtt_publish_interval")].as<uint32_t>();
|
||||
config.Mqtt_Hass_Enabled = root[F("mqtt_hass_enabled")].as<bool>();
|
||||
config.Mqtt_Hass_Expire = root[F("mqtt_hass_expire")].as<bool>();
|
||||
config.Mqtt_Hass_Retain = root[F("mqtt_hass_retain")].as<bool>();
|
||||
config.Mqtt_Hass_IndividualPanels = root[F("mqtt_hass_individualpanels")].as<bool>();
|
||||
strlcpy(config.Mqtt_Hass_Topic, root[F("mqtt_hass_topic")].as<String>().c_str(), sizeof(config.Mqtt_Hass_Topic));
|
||||
config.Mqtt_Enabled = root["mqtt_enabled"].as<bool>();
|
||||
config.Mqtt_Retain = root["mqtt_retain"].as<bool>();
|
||||
config.Mqtt_Tls = root["mqtt_tls"].as<bool>();
|
||||
strlcpy(config.Mqtt_RootCaCert, root["mqtt_root_ca_cert"].as<String>().c_str(), sizeof(config.Mqtt_RootCaCert));
|
||||
config.Mqtt_Port = root["mqtt_port"].as<uint>();
|
||||
strlcpy(config.Mqtt_Hostname, root["mqtt_hostname"].as<String>().c_str(), sizeof(config.Mqtt_Hostname));
|
||||
strlcpy(config.Mqtt_Username, root["mqtt_username"].as<String>().c_str(), sizeof(config.Mqtt_Username));
|
||||
strlcpy(config.Mqtt_Password, root["mqtt_password"].as<String>().c_str(), sizeof(config.Mqtt_Password));
|
||||
strlcpy(config.Mqtt_Topic, root["mqtt_topic"].as<String>().c_str(), sizeof(config.Mqtt_Topic));
|
||||
strlcpy(config.Mqtt_LwtTopic, root["mqtt_lwt_topic"].as<String>().c_str(), sizeof(config.Mqtt_LwtTopic));
|
||||
strlcpy(config.Mqtt_LwtValue_Online, root["mqtt_lwt_online"].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Online));
|
||||
strlcpy(config.Mqtt_LwtValue_Offline, root["mqtt_lwt_offline"].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Offline));
|
||||
config.Mqtt_PublishInterval = root["mqtt_publish_interval"].as<uint32_t>();
|
||||
config.Mqtt_Hass_Enabled = root["mqtt_hass_enabled"].as<bool>();
|
||||
config.Mqtt_Hass_Expire = root["mqtt_hass_expire"].as<bool>();
|
||||
config.Mqtt_Hass_Retain = root["mqtt_hass_retain"].as<bool>();
|
||||
config.Mqtt_Hass_IndividualPanels = root["mqtt_hass_individualpanels"].as<bool>();
|
||||
strlcpy(config.Mqtt_Hass_Topic, root["mqtt_hass_topic"].as<String>().c_str(), sizeof(config.Mqtt_Hass_Topic));
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Settings saved!";
|
||||
retMsg["code"] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -34,22 +34,22 @@ void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request)
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root[F("sta_status")] = ((WiFi.getMode() & WIFI_STA) != 0);
|
||||
root[F("sta_ssid")] = WiFi.SSID();
|
||||
root[F("sta_rssi")] = WiFi.RSSI();
|
||||
root[F("network_hostname")] = NetworkSettings.getHostname();
|
||||
root[F("network_ip")] = NetworkSettings.localIP().toString();
|
||||
root[F("network_netmask")] = NetworkSettings.subnetMask().toString();
|
||||
root[F("network_gateway")] = NetworkSettings.gatewayIP().toString();
|
||||
root[F("network_dns1")] = NetworkSettings.dnsIP(0).toString();
|
||||
root[F("network_dns2")] = NetworkSettings.dnsIP(1).toString();
|
||||
root[F("network_mac")] = NetworkSettings.macAddress();
|
||||
root[F("network_mode")] = NetworkSettings.NetworkMode() == network_mode::WiFi ? F("Station") : F("Ethernet");
|
||||
root[F("ap_status")] = ((WiFi.getMode() & WIFI_AP) != 0);
|
||||
root[F("ap_ssid")] = NetworkSettings.getApName();
|
||||
root[F("ap_ip")] = WiFi.softAPIP().toString();
|
||||
root[F("ap_mac")] = WiFi.softAPmacAddress();
|
||||
root[F("ap_stationnum")] = WiFi.softAPgetStationNum();
|
||||
root["sta_status"] = ((WiFi.getMode() & WIFI_STA) != 0);
|
||||
root["sta_ssid"] = WiFi.SSID();
|
||||
root["sta_rssi"] = WiFi.RSSI();
|
||||
root["network_hostname"] = NetworkSettings.getHostname();
|
||||
root["network_ip"] = NetworkSettings.localIP().toString();
|
||||
root["network_netmask"] = NetworkSettings.subnetMask().toString();
|
||||
root["network_gateway"] = NetworkSettings.gatewayIP().toString();
|
||||
root["network_dns1"] = NetworkSettings.dnsIP(0).toString();
|
||||
root["network_dns2"] = NetworkSettings.dnsIP(1).toString();
|
||||
root["network_mac"] = NetworkSettings.macAddress();
|
||||
root["network_mode"] = NetworkSettings.NetworkMode() == network_mode::WiFi ? "Station" : "Ethernet";
|
||||
root["ap_status"] = ((WiFi.getMode() & WIFI_AP) != 0);
|
||||
root["ap_ssid"] = NetworkSettings.getApName();
|
||||
root["ap_ip"] = WiFi.softAPIP().toString();
|
||||
root["ap_mac"] = WiFi.softAPmacAddress();
|
||||
root["ap_stationnum"] = WiFi.softAPgetStationNum();
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -65,15 +65,15 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("hostname")] = config.WiFi_Hostname;
|
||||
root[F("dhcp")] = config.WiFi_Dhcp;
|
||||
root[F("ipaddress")] = IPAddress(config.WiFi_Ip).toString();
|
||||
root[F("netmask")] = IPAddress(config.WiFi_Netmask).toString();
|
||||
root[F("gateway")] = IPAddress(config.WiFi_Gateway).toString();
|
||||
root[F("dns1")] = IPAddress(config.WiFi_Dns1).toString();
|
||||
root[F("dns2")] = IPAddress(config.WiFi_Dns2).toString();
|
||||
root[F("ssid")] = config.WiFi_Ssid;
|
||||
root[F("password")] = config.WiFi_Password;
|
||||
root["hostname"] = config.WiFi_Hostname;
|
||||
root["dhcp"] = config.WiFi_Dhcp;
|
||||
root["ipaddress"] = IPAddress(config.WiFi_Ip).toString();
|
||||
root["netmask"] = IPAddress(config.WiFi_Netmask).toString();
|
||||
root["gateway"] = IPAddress(config.WiFi_Gateway).toString();
|
||||
root["dns1"] = IPAddress(config.WiFi_Dns1).toString();
|
||||
root["dns2"] = IPAddress(config.WiFi_Dns2).toString();
|
||||
root["ssid"] = config.WiFi_Ssid;
|
||||
root["password"] = config.WiFi_Password;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -87,11 +87,11 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -100,8 +100,8 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -111,78 +111,78 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
IPAddress ipaddress;
|
||||
if (!ipaddress.fromString(root[F("ipaddress")].as<String>())) {
|
||||
retMsg[F("message")] = F("IP address is invalid!");
|
||||
retMsg[F("code")] = WebApiError::NetworkIpInvalid;
|
||||
if (!ipaddress.fromString(root["ipaddress"].as<String>())) {
|
||||
retMsg["message"] = "IP address is invalid!";
|
||||
retMsg["code"] = WebApiError::NetworkIpInvalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
IPAddress netmask;
|
||||
if (!netmask.fromString(root[F("netmask")].as<String>())) {
|
||||
retMsg[F("message")] = F("Netmask is invalid!");
|
||||
retMsg[F("code")] = WebApiError::NetworkNetmaskInvalid;
|
||||
if (!netmask.fromString(root["netmask"].as<String>())) {
|
||||
retMsg["message"] = "Netmask is invalid!";
|
||||
retMsg["code"] = WebApiError::NetworkNetmaskInvalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
IPAddress gateway;
|
||||
if (!gateway.fromString(root[F("gateway")].as<String>())) {
|
||||
retMsg[F("message")] = F("Gateway is invalid!");
|
||||
retMsg[F("code")] = WebApiError::NetworkGatewayInvalid;
|
||||
if (!gateway.fromString(root["gateway"].as<String>())) {
|
||||
retMsg["message"] = "Gateway is invalid!";
|
||||
retMsg["code"] = WebApiError::NetworkGatewayInvalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
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;
|
||||
if (!dns1.fromString(root["dns1"].as<String>())) {
|
||||
retMsg["message"] = "DNS Server IP 1 is invalid!";
|
||||
retMsg["code"] = WebApiError::NetworkDns1Invalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
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;
|
||||
if (!dns2.fromString(root["dns2"].as<String>())) {
|
||||
retMsg["message"] = "DNS Server IP 2 is invalid!";
|
||||
retMsg["code"] = WebApiError::NetworkDns2Invalid;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("hostname")].as<String>().length() == 0 || root[F("hostname")].as<String>().length() > WIFI_MAX_HOSTNAME_STRLEN) {
|
||||
retMsg[F("message")] = F("Hostname must between 1 and " STR(WIFI_MAX_HOSTNAME_STRLEN) " characters long!");
|
||||
if (root["hostname"].as<String>().length() == 0 || root["hostname"].as<String>().length() > WIFI_MAX_HOSTNAME_STRLEN) {
|
||||
retMsg["message"] = "Hostname must between 1 and " STR(WIFI_MAX_HOSTNAME_STRLEN) " characters long!";
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
|
||||
if (root[F("ssid")].as<String>().length() == 0 || root[F("ssid")].as<String>().length() > WIFI_MAX_SSID_STRLEN) {
|
||||
retMsg[F("message")] = F("SSID must between 1 and " STR(WIFI_MAX_SSID_STRLEN) " characters long!");
|
||||
if (root["ssid"].as<String>().length() == 0 || root["ssid"].as<String>().length() > WIFI_MAX_SSID_STRLEN) {
|
||||
retMsg["message"] = "SSID must between 1 and " STR(WIFI_MAX_SSID_STRLEN) " characters long!";
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (root[F("password")].as<String>().length() > WIFI_MAX_PASSWORD_STRLEN - 1) {
|
||||
retMsg[F("message")] = F("Password must not be longer than " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!");
|
||||
if (root["password"].as<String>().length() > WIFI_MAX_PASSWORD_STRLEN - 1) {
|
||||
retMsg["message"] = "Password must not be longer than " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!";
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -209,19 +209,19 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||
config.WiFi_Dns2[1] = dns2[1];
|
||||
config.WiFi_Dns2[2] = dns2[2];
|
||||
config.WiFi_Dns2[3] = dns2[3];
|
||||
strlcpy(config.WiFi_Ssid, root[F("ssid")].as<String>().c_str(), sizeof(config.WiFi_Ssid));
|
||||
strlcpy(config.WiFi_Password, root[F("password")].as<String>().c_str(), sizeof(config.WiFi_Password));
|
||||
strlcpy(config.WiFi_Hostname, root[F("hostname")].as<String>().c_str(), sizeof(config.WiFi_Hostname));
|
||||
if (root[F("dhcp")].as<bool>()) {
|
||||
strlcpy(config.WiFi_Ssid, root["ssid"].as<String>().c_str(), sizeof(config.WiFi_Ssid));
|
||||
strlcpy(config.WiFi_Password, root["password"].as<String>().c_str(), sizeof(config.WiFi_Password));
|
||||
strlcpy(config.WiFi_Hostname, root["hostname"].as<String>().c_str(), sizeof(config.WiFi_Hostname));
|
||||
if (root["dhcp"].as<bool>()) {
|
||||
config.WiFi_Dhcp = true;
|
||||
} else {
|
||||
config.WiFi_Dhcp = false;
|
||||
}
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Settings saved!";
|
||||
retMsg["code"] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -38,29 +38,29 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request)
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("ntp_server")] = config.Ntp_Server;
|
||||
root[F("ntp_timezone")] = config.Ntp_Timezone;
|
||||
root[F("ntp_timezone_descr")] = config.Ntp_TimezoneDescr;
|
||||
root["ntp_server"] = config.Ntp_Server;
|
||||
root["ntp_timezone"] = config.Ntp_Timezone;
|
||||
root["ntp_timezone_descr"] = config.Ntp_TimezoneDescr;
|
||||
|
||||
struct tm timeinfo;
|
||||
if (!getLocalTime(&timeinfo, 5)) {
|
||||
root[F("ntp_status")] = false;
|
||||
root["ntp_status"] = false;
|
||||
} else {
|
||||
root[F("ntp_status")] = true;
|
||||
root["ntp_status"] = true;
|
||||
}
|
||||
char timeStringBuff[50];
|
||||
strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
||||
root[F("ntp_localtime")] = timeStringBuff;
|
||||
root["ntp_localtime"] = timeStringBuff;
|
||||
|
||||
SunPosition.sunriseTime(&timeinfo);
|
||||
strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
||||
root[F("sun_risetime")] = timeStringBuff;
|
||||
root["sun_risetime"] = timeStringBuff;
|
||||
|
||||
SunPosition.sunsetTime(&timeinfo);
|
||||
strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
||||
root[F("sun_settime")] = timeStringBuff;
|
||||
root["sun_settime"] = timeStringBuff;
|
||||
|
||||
root[F("sun_isDayPeriod")] = SunPosition.isDayPeriod();
|
||||
root["sun_isDayPeriod"] = SunPosition.isDayPeriod();
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -76,11 +76,11 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request)
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("ntp_server")] = config.Ntp_Server;
|
||||
root[F("ntp_timezone")] = config.Ntp_Timezone;
|
||||
root[F("ntp_timezone_descr")] = config.Ntp_TimezoneDescr;
|
||||
root[F("longitude")] = config.Ntp_Longitude;
|
||||
root[F("latitude")] = config.Ntp_Latitude;
|
||||
root["ntp_server"] = config.Ntp_Server;
|
||||
root["ntp_timezone"] = config.Ntp_Timezone;
|
||||
root["ntp_timezone_descr"] = config.Ntp_TimezoneDescr;
|
||||
root["longitude"] = config.Ntp_Longitude;
|
||||
root["latitude"] = config.Ntp_Latitude;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -94,11 +94,11 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -107,8 +107,8 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -118,59 +118,59 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("ntp_server") && root.containsKey("ntp_timezone") && root.containsKey("longitude") && root.containsKey("latitude"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["ntp_server"].as<String>().length() == 0 || root["ntp_server"].as<String>().length() > NTP_MAX_SERVER_STRLEN) {
|
||||
retMsg["message"] = "NTP Server must between 1 and " STR(NTP_MAX_SERVER_STRLEN) " characters long!";
|
||||
retMsg["code"] = WebApiError::NtpServerLength;
|
||||
retMsg["param"]["max"] = NTP_MAX_SERVER_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["ntp_timezone"].as<String>().length() == 0 || root["ntp_timezone"].as<String>().length() > NTP_MAX_TIMEZONE_STRLEN) {
|
||||
retMsg["message"] = "Timezone must between 1 and " STR(NTP_MAX_TIMEZONE_STRLEN) " characters long!";
|
||||
retMsg["code"] = WebApiError::NtpTimezoneLength;
|
||||
retMsg["param"]["max"] = NTP_MAX_TIMEZONE_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["ntp_timezone_descr"].as<String>().length() == 0 || root["ntp_timezone_descr"].as<String>().length() > NTP_MAX_TIMEZONEDESCR_STRLEN) {
|
||||
retMsg["message"] = "Timezone description must between 1 and " STR(NTP_MAX_TIMEZONEDESCR_STRLEN) " characters long!";
|
||||
retMsg["code"] = WebApiError::NtpTimezoneDescriptionLength;
|
||||
retMsg["param"]["max"] = NTP_MAX_TIMEZONEDESCR_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
strlcpy(config.Ntp_Server, root[F("ntp_server")].as<String>().c_str(), sizeof(config.Ntp_Server));
|
||||
strlcpy(config.Ntp_Timezone, root[F("ntp_timezone")].as<String>().c_str(), sizeof(config.Ntp_Timezone));
|
||||
strlcpy(config.Ntp_TimezoneDescr, root[F("ntp_timezone_descr")].as<String>().c_str(), sizeof(config.Ntp_TimezoneDescr));
|
||||
config.Ntp_Latitude = root[F("latitude")].as<double>();
|
||||
config.Ntp_Longitude = root[F("longitude")].as<double>();
|
||||
strlcpy(config.Ntp_Server, root["ntp_server"].as<String>().c_str(), sizeof(config.Ntp_Server));
|
||||
strlcpy(config.Ntp_Timezone, root["ntp_timezone"].as<String>().c_str(), sizeof(config.Ntp_Timezone));
|
||||
strlcpy(config.Ntp_TimezoneDescr, root["ntp_timezone_descr"].as<String>().c_str(), sizeof(config.Ntp_TimezoneDescr));
|
||||
config.Ntp_Latitude = root["latitude"].as<double>();
|
||||
config.Ntp_Longitude = root["longitude"].as<double>();
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Settings saved!";
|
||||
retMsg["code"] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -190,17 +190,17 @@ void WebApiNtpClass::onNtpTimeGet(AsyncWebServerRequest* request)
|
||||
|
||||
struct tm timeinfo;
|
||||
if (!getLocalTime(&timeinfo, 5)) {
|
||||
root[F("ntp_status")] = false;
|
||||
root["ntp_status"] = false;
|
||||
} else {
|
||||
root[F("ntp_status")] = true;
|
||||
root["ntp_status"] = true;
|
||||
}
|
||||
|
||||
root[F("year")] = timeinfo.tm_year + 1900;
|
||||
root[F("month")] = timeinfo.tm_mon + 1;
|
||||
root[F("day")] = timeinfo.tm_mday;
|
||||
root[F("hour")] = timeinfo.tm_hour;
|
||||
root[F("minute")] = timeinfo.tm_min;
|
||||
root[F("second")] = timeinfo.tm_sec;
|
||||
root["year"] = timeinfo.tm_year + 1900;
|
||||
root["month"] = timeinfo.tm_mon + 1;
|
||||
root["day"] = timeinfo.tm_mday;
|
||||
root["hour"] = timeinfo.tm_hour;
|
||||
root["minute"] = timeinfo.tm_min;
|
||||
root["second"] = timeinfo.tm_sec;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -214,11 +214,11 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -227,8 +227,8 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -238,8 +238,8 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -251,89 +251,89 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||
&& root.containsKey("hour")
|
||||
&& root.containsKey("minute")
|
||||
&& root.containsKey("second"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["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 2022 and 2100!");
|
||||
retMsg[F("code")] = WebApiError::NtpYearInvalid;
|
||||
retMsg[F("param")][F("min")] = 2022;
|
||||
retMsg[F("param")][F("max")] = 2100;
|
||||
if (root["year"].as<uint>() < 2022 || root["year"].as<uint>() > 2100) {
|
||||
retMsg["message"] = "Year must be a number between 2022 and 2100!";
|
||||
retMsg["code"] = WebApiError::NtpYearInvalid;
|
||||
retMsg["param"]["min"] = 2022;
|
||||
retMsg["param"]["max"] = 2100;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["month"].as<uint>() < 1 || root["month"].as<uint>() > 12) {
|
||||
retMsg["message"] = "Month must be a number between 1 and 12!";
|
||||
retMsg["code"] = WebApiError::NtpMonthInvalid;
|
||||
retMsg["param"]["min"] = 1;
|
||||
retMsg["param"]["max"] = 12;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["day"].as<uint>() < 1 || root["day"].as<uint>() > 31) {
|
||||
retMsg["message"] = "Day must be a number between 1 and 31!";
|
||||
retMsg["code"] = WebApiError::NtpDayInvalid;
|
||||
retMsg["param"]["min"] = 1;
|
||||
retMsg["param"]["max"] = 31;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["hour"].as<uint>() > 23) {
|
||||
retMsg["message"] = "Hour must be a number between 0 and 23!";
|
||||
retMsg["code"] = WebApiError::NtpHourInvalid;
|
||||
retMsg["param"]["min"] = 0;
|
||||
retMsg["param"]["max"] = 23;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["minute"].as<uint>() > 59) {
|
||||
retMsg["message"] = "Minute must be a number between 0 and 59!";
|
||||
retMsg["code"] = WebApiError::NtpMinuteInvalid;
|
||||
retMsg["param"]["min"] = 0;
|
||||
retMsg["param"]["max"] = 59;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (root["second"].as<uint>() > 59) {
|
||||
retMsg["message"] = "Second must be a number between 0 and 59!";
|
||||
retMsg["code"] = WebApiError::NtpSecondInvalid;
|
||||
retMsg["param"]["min"] = 0;
|
||||
retMsg["param"]["max"] = 59;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
tm local;
|
||||
local.tm_sec = root[F("second")].as<uint>(); // seconds after the minute - [ 0 to 59 ]
|
||||
local.tm_min = root[F("minute")].as<uint>(); // minutes after the hour - [ 0 to 59 ]
|
||||
local.tm_hour = root[F("hour")].as<uint>(); // hours since midnight - [ 0 to 23 ]
|
||||
local.tm_mday = root[F("day")].as<uint>(); // day of the month - [ 1 to 31 ]
|
||||
local.tm_mon = root[F("month")].as<uint>() - 1; // months since January - [ 0 to 11 ]
|
||||
local.tm_year = root[F("year")].as<uint>() - 1900; // years since 1900
|
||||
local.tm_sec = root["second"].as<uint>(); // seconds after the minute - [ 0 to 59 ]
|
||||
local.tm_min = root["minute"].as<uint>(); // minutes after the hour - [ 0 to 59 ]
|
||||
local.tm_hour = root["hour"].as<uint>(); // hours since midnight - [ 0 to 23 ]
|
||||
local.tm_mday = root["day"].as<uint>(); // day of the month - [ 1 to 31 ]
|
||||
local.tm_mon = root["month"].as<uint>() - 1; // months since January - [ 0 to 11 ]
|
||||
local.tm_year = root["year"].as<uint>() - 1900; // years since 1900
|
||||
local.tm_isdst = -1;
|
||||
|
||||
time_t t = mktime(&local);
|
||||
struct timeval now = { .tv_sec = t, .tv_usec = 0 };
|
||||
settimeofday(&now, NULL);
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Time updated!");
|
||||
retMsg[F("code")] = WebApiError::NtpTimeUpdated;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Time updated!";
|
||||
retMsg["code"] = WebApiError::NtpTimeUpdated;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -58,11 +58,11 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -71,8 +71,8 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -82,8 +82,8 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -91,43 +91,43 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!(root.containsKey("serial")
|
||||
&& (root.containsKey("power") || root.containsKey("restart")))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("serial")].as<uint64_t>() == 0) {
|
||||
retMsg[F("message")] = F("Serial must be a number > 0!");
|
||||
retMsg[F("code")] = WebApiError::PowerSerialZero;
|
||||
if (root["serial"].as<uint64_t>() == 0) {
|
||||
retMsg["message"] = "Serial must be a number > 0!";
|
||||
retMsg["code"] = WebApiError::PowerSerialZero;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t serial = strtoll(root[F("serial")].as<String>().c_str(), NULL, 16);
|
||||
uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||
if (inv == nullptr) {
|
||||
retMsg[F("message")] = F("Invalid inverter specified!");
|
||||
retMsg[F("code")] = WebApiError::PowerInvalidInverter;
|
||||
retMsg["message"] = "Invalid inverter specified!";
|
||||
retMsg["code"] = WebApiError::PowerInvalidInverter;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.containsKey("power")) {
|
||||
uint16_t power = root[F("power")].as<bool>();
|
||||
uint16_t power = root["power"].as<bool>();
|
||||
inv->sendPowerControlRequest(Hoymiles.getRadio(), power);
|
||||
} else {
|
||||
if (root[F("restart")].as<bool>()) {
|
||||
if (root["restart"].as<bool>()) {
|
||||
inv->sendRestartControlRequest(Hoymiles.getRadio());
|
||||
}
|
||||
}
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Settings saved!";
|
||||
retMsg["code"] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -29,29 +29,29 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
|
||||
try {
|
||||
auto stream = request->beginResponseStream("text/plain; charset=utf-8", 40960);
|
||||
|
||||
stream->print(F("# HELP opendtu_build Build info\n"));
|
||||
stream->print(F("# TYPE opendtu_build gauge\n"));
|
||||
stream->print("# HELP opendtu_build Build info\n");
|
||||
stream->print("# TYPE opendtu_build gauge\n");
|
||||
stream->printf("opendtu_build{name=\"%s\",id=\"%s\",version=\"%d.%d.%d\"} 1\n",
|
||||
NetworkSettings.getHostname().c_str(), AUTO_GIT_HASH, CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff);
|
||||
|
||||
stream->print(F("# HELP opendtu_platform Platform info\n"));
|
||||
stream->print(F("# TYPE opendtu_platform gauge\n"));
|
||||
stream->print("# HELP opendtu_platform Platform info\n");
|
||||
stream->print("# TYPE opendtu_platform gauge\n");
|
||||
stream->printf("opendtu_platform{arch=\"%s\",mac=\"%s\"} 1\n", ESP.getChipModel(), NetworkSettings.macAddress().c_str());
|
||||
|
||||
stream->print(F("# HELP opendtu_uptime Uptime in seconds\n"));
|
||||
stream->print(F("# TYPE opendtu_uptime counter\n"));
|
||||
stream->print("# HELP opendtu_uptime Uptime in seconds\n");
|
||||
stream->print("# TYPE opendtu_uptime counter\n");
|
||||
stream->printf("opendtu_uptime %lld\n", esp_timer_get_time() / 1000000);
|
||||
|
||||
stream->print(F("# HELP opendtu_heap_size System memory size\n"));
|
||||
stream->print(F("# TYPE opendtu_heap_size gauge\n"));
|
||||
stream->print("# HELP opendtu_heap_size System memory size\n");
|
||||
stream->print("# TYPE opendtu_heap_size gauge\n");
|
||||
stream->printf("opendtu_heap_size %zu\n", ESP.getHeapSize());
|
||||
|
||||
stream->print(F("# HELP opendtu_free_heap_size System free memory\n"));
|
||||
stream->print(F("# TYPE opendtu_free_heap_size gauge\n"));
|
||||
stream->print("# HELP opendtu_free_heap_size System free memory\n");
|
||||
stream->print("# TYPE opendtu_free_heap_size gauge\n");
|
||||
stream->printf("opendtu_free_heap_size %zu\n", ESP.getFreeHeap());
|
||||
|
||||
stream->print(F("# HELP wifi_rssi WiFi RSSI\n"));
|
||||
stream->print(F("# TYPE wifi_rssi gauge\n"));
|
||||
stream->print("# HELP wifi_rssi WiFi RSSI\n");
|
||||
stream->print("# TYPE wifi_rssi gauge\n");
|
||||
stream->printf("wifi_rssi %d\n", WiFi.RSSI());
|
||||
|
||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||
@ -60,8 +60,8 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
|
||||
String serial = inv->serialString();
|
||||
const char* name = inv->name();
|
||||
if (i == 0) {
|
||||
stream->print(F("# HELP opendtu_last_update last update from inverter in s\n"));
|
||||
stream->print(F("# TYPE opendtu_last_update gauge\n"));
|
||||
stream->print("# HELP opendtu_last_update last update from inverter in s\n");
|
||||
stream->print("# TYPE opendtu_last_update gauge\n");
|
||||
}
|
||||
stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%d\",name=\"%s\"} %d\n",
|
||||
serial.c_str(), i, name, inv->Statistics()->getLastUpdate() / 1000);
|
||||
@ -93,7 +93,7 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
|
||||
}
|
||||
}
|
||||
}
|
||||
stream->addHeader(F("Cache-Control"), F("no-cache"));
|
||||
stream->addHeader("Cache-Control", "no-cache");
|
||||
request->send(stream);
|
||||
|
||||
} catch (std::bad_alloc& bad_alloc) {
|
||||
@ -132,8 +132,8 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se
|
||||
|
||||
const bool printHelp = (idx == 0 && channel == 0);
|
||||
if (printHelp) {
|
||||
stream->print(F("# HELP opendtu_PanelInfo panel information\n"));
|
||||
stream->print(F("# TYPE opendtu_PanelInfo gauge\n"));
|
||||
stream->print("# HELP opendtu_PanelInfo panel information\n");
|
||||
stream->print("# TYPE opendtu_PanelInfo gauge\n");
|
||||
}
|
||||
stream->printf("opendtu_PanelInfo{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\",panelname=\"%s\"} 1\n",
|
||||
serial.c_str(),
|
||||
@ -144,8 +144,8 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se
|
||||
);
|
||||
|
||||
if (printHelp) {
|
||||
stream->print(F("# HELP opendtu_MaxPower panel maximum output power\n"));
|
||||
stream->print(F("# TYPE opendtu_MaxPower gauge\n"));
|
||||
stream->print("# HELP opendtu_MaxPower panel maximum output power\n");
|
||||
stream->print("# TYPE opendtu_MaxPower gauge\n");
|
||||
}
|
||||
stream->printf("opendtu_MaxPower{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %d\n",
|
||||
serial.c_str(),
|
||||
@ -156,8 +156,8 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& se
|
||||
);
|
||||
|
||||
if (printHelp) {
|
||||
stream->print(F("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n"));
|
||||
stream->print(F("# TYPE opendtu_YieldTotalOffset gauge\n"));
|
||||
stream->print("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n");
|
||||
stream->print("# TYPE opendtu_YieldTotalOffset gauge\n");
|
||||
}
|
||||
stream->printf("opendtu_YieldTotalOffset{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %f\n",
|
||||
serial.c_str(),
|
||||
|
||||
@ -34,8 +34,8 @@ void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request)
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("password")] = config.Security_Password;
|
||||
root[F("allow_readonly")] = config.Security_AllowReadonly;
|
||||
root["password"] = config.Security_Password;
|
||||
root["allow_readonly"] = config.Security_AllowReadonly;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -49,11 +49,11 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
retMsg["type"] = "warning";
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||
retMsg["message"] = "No values found!";
|
||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -62,8 +62,8 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||
retMsg["message"] = "Data too large!";
|
||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -73,8 +73,8 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||
retMsg["message"] = "Failed to parse data!";
|
||||
retMsg["code"] = WebApiError::GenericParseError;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@ -82,30 +82,30 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
||||
|
||||
if (!root.containsKey("password")
|
||||
&& root.containsKey("allow_readonly")) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||
retMsg["message"] = "Values are missing!";
|
||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("password")].as<String>().length() < 8 || root[F("password")].as<String>().length() > WIFI_MAX_PASSWORD_STRLEN) {
|
||||
retMsg[F("message")] = F("Password must between 8 and " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!");
|
||||
retMsg[F("code")] = WebApiError::SecurityPasswordLength;
|
||||
retMsg[F("param")][F("max")] = WIFI_MAX_PASSWORD_STRLEN;
|
||||
if (root["password"].as<String>().length() < 8 || root["password"].as<String>().length() > WIFI_MAX_PASSWORD_STRLEN) {
|
||||
retMsg["message"] = "Password must between 8 and " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!";
|
||||
retMsg["code"] = WebApiError::SecurityPasswordLength;
|
||||
retMsg["param"]["max"] = WIFI_MAX_PASSWORD_STRLEN;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
strlcpy(config.Security_Password, root[F("password")].as<String>().c_str(), sizeof(config.Security_Password));
|
||||
config.Security_AllowReadonly = root[F("allow_readonly")].as<bool>();
|
||||
strlcpy(config.Security_Password, root["password"].as<String>().c_str(), sizeof(config.Security_Password));
|
||||
config.Security_AllowReadonly = root["allow_readonly"].as<bool>();
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Settings saved!";
|
||||
retMsg["code"] = WebApiError::GenericSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -119,9 +119,9 @@ void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Authentication successful!");
|
||||
retMsg[F("code")] = WebApiError::SecurityAuthSuccess;
|
||||
retMsg["type"] = "success";
|
||||
retMsg["message"] = "Authentication successful!";
|
||||
retMsg["code"] = WebApiError::SecurityAuthSuccess;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -37,40 +37,40 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root[F("hostname")] = NetworkSettings.getHostname();
|
||||
root["hostname"] = NetworkSettings.getHostname();
|
||||
|
||||
root[F("sdkversion")] = ESP.getSdkVersion();
|
||||
root[F("cpufreq")] = ESP.getCpuFreqMHz();
|
||||
root["sdkversion"] = ESP.getSdkVersion();
|
||||
root["cpufreq"] = ESP.getCpuFreqMHz();
|
||||
|
||||
root[F("heap_total")] = ESP.getHeapSize();
|
||||
root[F("heap_used")] = ESP.getHeapSize() - ESP.getFreeHeap();
|
||||
root[F("sketch_total")] = ESP.getFreeSketchSpace();
|
||||
root[F("sketch_used")] = ESP.getSketchSize();
|
||||
root[F("littlefs_total")] = LittleFS.totalBytes();
|
||||
root[F("littlefs_used")] = LittleFS.usedBytes();
|
||||
root["heap_total"] = ESP.getHeapSize();
|
||||
root["heap_used"] = ESP.getHeapSize() - ESP.getFreeHeap();
|
||||
root["sketch_total"] = ESP.getFreeSketchSpace();
|
||||
root["sketch_used"] = ESP.getSketchSize();
|
||||
root["littlefs_total"] = LittleFS.totalBytes();
|
||||
root["littlefs_used"] = LittleFS.usedBytes();
|
||||
|
||||
root[F("chiprevision")] = ESP.getChipRevision();
|
||||
root[F("chipmodel")] = ESP.getChipModel();
|
||||
root[F("chipcores")] = ESP.getChipCores();
|
||||
root["chiprevision"] = ESP.getChipRevision();
|
||||
root["chipmodel"] = ESP.getChipModel();
|
||||
root["chipcores"] = ESP.getChipCores();
|
||||
|
||||
String reason;
|
||||
reason = ResetReason.get_reset_reason_verbose(0);
|
||||
root[F("resetreason_0")] = reason;
|
||||
root["resetreason_0"] = reason;
|
||||
|
||||
reason = ResetReason.get_reset_reason_verbose(1);
|
||||
root[F("resetreason_1")] = reason;
|
||||
root["resetreason_1"] = reason;
|
||||
|
||||
root[F("cfgsavecount")] = Configuration.get().Cfg_SaveCount;
|
||||
root["cfgsavecount"] = Configuration.get().Cfg_SaveCount;
|
||||
|
||||
char version[16];
|
||||
snprintf(version, sizeof(version), "%d.%d.%d", CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff);
|
||||
root[F("config_version")] = version;
|
||||
root[F("git_hash")] = AUTO_GIT_HASH;
|
||||
root["config_version"] = version;
|
||||
root["git_hash"] = AUTO_GIT_HASH;
|
||||
|
||||
root[F("uptime")] = esp_timer_get_time() / 1000000;
|
||||
root["uptime"] = esp_timer_get_time() / 1000000;
|
||||
|
||||
root[F("radio_connected")] = Hoymiles.getRadio()->isConnected();
|
||||
root[F("radio_pvariant")] = Hoymiles.getRadio()->isPVariant();
|
||||
root["radio_connected"] = Hoymiles.getRadio()->isConnected();
|
||||
root["radio_pvariant"] = Hoymiles.getRadio()->isPVariant();
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@ -33,8 +33,6 @@ void WebApiWsHuaweiLiveClass::init(AsyncWebServer* server)
|
||||
|
||||
void WebApiWsHuaweiLiveClass::loop()
|
||||
{
|
||||
|
||||
|
||||
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
||||
if (millis() - _lastWsCleanup > 1000) {
|
||||
_ws.cleanupClients();
|
||||
@ -51,24 +49,28 @@ void WebApiWsHuaweiLiveClass::loop()
|
||||
}
|
||||
_lastUpdateCheck = millis();
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
JsonVariant var = root;
|
||||
generateJsonResponse(var);
|
||||
|
||||
String buffer;
|
||||
if (buffer) {
|
||||
serializeJson(root, buffer);
|
||||
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password);
|
||||
try {
|
||||
String buffer;
|
||||
// free JsonDocument as soon as possible
|
||||
{
|
||||
DynamicJsonDocument root(1024);
|
||||
JsonVariant var = root;
|
||||
generateJsonResponse(var);
|
||||
serializeJson(root, buffer);
|
||||
}
|
||||
|
||||
_ws.textAll(buffer);
|
||||
}
|
||||
if (buffer) {
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password);
|
||||
}
|
||||
|
||||
|
||||
_ws.textAll(buffer);
|
||||
}
|
||||
} catch (std::bad_alloc& bad_alloc) {
|
||||
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||
}
|
||||
}
|
||||
|
||||
void WebApiWsHuaweiLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
@ -119,10 +121,16 @@ void WebApiWsHuaweiLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U);
|
||||
JsonVariant root = response->getRoot().as<JsonVariant>();
|
||||
generateJsonResponse(root);
|
||||
try {
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U);
|
||||
JsonVariant root = response->getRoot().as<JsonVariant>();
|
||||
generateJsonResponse(root);
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
} catch (std::bad_alloc& bad_alloc) {
|
||||
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||
|
||||
WebApi.sendTooManyRequests(request);
|
||||
}
|
||||
}
|
||||
@ -49,24 +49,28 @@ void WebApiWsPylontechLiveClass::loop()
|
||||
}
|
||||
_lastUpdateCheck = millis();
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
JsonVariant var = root;
|
||||
generateJsonResponse(var);
|
||||
|
||||
String buffer;
|
||||
if (buffer) {
|
||||
serializeJson(root, buffer);
|
||||
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password);
|
||||
try {
|
||||
String buffer;
|
||||
// free JsonDocument as soon as possible
|
||||
{
|
||||
DynamicJsonDocument root(1024);
|
||||
JsonVariant var = root;
|
||||
generateJsonResponse(var);
|
||||
serializeJson(root, buffer);
|
||||
}
|
||||
|
||||
_ws.textAll(buffer);
|
||||
}
|
||||
if (buffer) {
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password);
|
||||
}
|
||||
|
||||
|
||||
_ws.textAll(buffer);
|
||||
}
|
||||
} catch (std::bad_alloc& bad_alloc) {
|
||||
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||
}
|
||||
}
|
||||
|
||||
void WebApiWsPylontechLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
@ -137,10 +141,16 @@ void WebApiWsPylontechLiveClass::onLivedataStatus(AsyncWebServerRequest* request
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U);
|
||||
JsonVariant root = response->getRoot().as<JsonVariant>();
|
||||
generateJsonResponse(root);
|
||||
try {
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U);
|
||||
JsonVariant root = response->getRoot().as<JsonVariant>();
|
||||
generateJsonResponse(root);
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
} catch (std::bad_alloc& bad_alloc) {
|
||||
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||
|
||||
WebApi.sendTooManyRequests(request);
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,8 @@
|
||||
#include "Configuration.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "WebApi.h"
|
||||
#include "Battery.h"
|
||||
#include "VeDirectFrameHandler.h"
|
||||
#include "defaults.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
@ -105,16 +107,17 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
|
||||
JsonObject invObject = invArray.createNestedObject();
|
||||
|
||||
invObject[F("serial")] = inv->serialString();
|
||||
invObject[F("name")] = inv->name();
|
||||
invObject[F("data_age")] = (millis() - inv->Statistics()->getLastUpdate()) / 1000;
|
||||
invObject[F("reachable")] = inv->isReachable();
|
||||
invObject[F("producing")] = inv->isProducing();
|
||||
invObject[F("limit_relative")] = inv->SystemConfigPara()->getLimitPercent();
|
||||
invObject["serial"] = inv->serialString();
|
||||
invObject["name"] = inv->name();
|
||||
invObject["data_age"] = (millis() - inv->Statistics()->getLastUpdate()) / 1000;
|
||||
invObject["poll_enabled"] = inv->getEnablePolling();
|
||||
invObject["reachable"] = inv->isReachable();
|
||||
invObject["producing"] = inv->isProducing();
|
||||
invObject["limit_relative"] = inv->SystemConfigPara()->getLimitPercent();
|
||||
if (inv->DevInfo()->getMaxPower() > 0) {
|
||||
invObject[F("limit_absolute")] = inv->SystemConfigPara()->getLimitPercent() * inv->DevInfo()->getMaxPower() / 100.0;
|
||||
invObject["limit_absolute"] = inv->SystemConfigPara()->getLimitPercent() * inv->DevInfo()->getMaxPower() / 100.0;
|
||||
} else {
|
||||
invObject[F("limit_absolute")] = -1;
|
||||
invObject["limit_absolute"] = -1;
|
||||
}
|
||||
|
||||
// Loop all channels
|
||||
@ -124,14 +127,14 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
if (t == TYPE_DC) {
|
||||
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
|
||||
if (inv_cfg != nullptr) {
|
||||
chanTypeObj[String(static_cast<uint8_t>(c))][F("name")]["u"] = inv_cfg->channel[c].Name;
|
||||
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
|
||||
}
|
||||
}
|
||||
addField(chanTypeObj, i, inv, t, c, FLD_PAC);
|
||||
addField(chanTypeObj, i, inv, t, c, FLD_UAC);
|
||||
addField(chanTypeObj, i, inv, t, c, FLD_IAC);
|
||||
if (t == TYPE_AC) {
|
||||
addField(chanTypeObj, i, inv, t, c, FLD_PDC, F("Power DC"));
|
||||
addField(chanTypeObj, i, inv, t, c, FLD_PDC, "Power DC");
|
||||
} else {
|
||||
addField(chanTypeObj, i, inv, t, c, FLD_PDC);
|
||||
}
|
||||
@ -151,9 +154,9 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
}
|
||||
|
||||
if (inv->Statistics()->hasChannelFieldValue(TYPE_INV, CH0, FLD_EVT_LOG)) {
|
||||
invObject[F("events")] = inv->EventLog()->getEntryCount();
|
||||
invObject["events"] = inv->EventLog()->getEntryCount();
|
||||
} else {
|
||||
invObject[F("events")] = -1;
|
||||
invObject["events"] = -1;
|
||||
}
|
||||
|
||||
if (inv->Statistics()->getLastUpdate() > _newestInverterTimestamp) {
|
||||
@ -175,23 +178,27 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
|
||||
JsonObject hintObj = root.createNestedObject("hints");
|
||||
struct tm timeinfo;
|
||||
hintObj[F("time_sync")] = !getLocalTime(&timeinfo, 5);
|
||||
hintObj[F("radio_problem")] = (!Hoymiles.getRadio()->isConnected() || !Hoymiles.getRadio()->isPVariant());
|
||||
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
|
||||
hintObj["radio_problem"] = (!Hoymiles.getRadio()->isConnected() || !Hoymiles.getRadio()->isPVariant());
|
||||
if (!strcmp(Configuration.get().Security_Password, ACCESS_POINT_PASSWORD)) {
|
||||
hintObj[F("default_password")] = true;
|
||||
hintObj["default_password"] = true;
|
||||
} else {
|
||||
hintObj[F("default_password")] = false;
|
||||
hintObj["default_password"] = false;
|
||||
}
|
||||
|
||||
JsonObject vedirectObj = root.createNestedObject("vedirect");
|
||||
vedirectObj[F("enabled")] = Configuration.get().Vedirect_Enabled;
|
||||
|
||||
JsonObject totalVeObj = vedirectObj.createNestedObject("total");
|
||||
addTotalField(totalVeObj, "Power", VeDirect.veFrame.PPV, "W", 1);
|
||||
addTotalField(totalVeObj, "YieldDay", VeDirect.veFrame.H20 * 1000, "Wh", 0);
|
||||
addTotalField(totalVeObj, "YieldTotal", VeDirect.veFrame.H19, "kWh", 2);
|
||||
|
||||
JsonObject huaweiObj = root.createNestedObject("huawei");
|
||||
huaweiObj[F("enabled")] = Configuration.get().Huawei_Enabled;
|
||||
|
||||
JsonObject batteryObj = root.createNestedObject("battery");
|
||||
batteryObj[F("enabled")] = Configuration.get().Battery_Enabled;
|
||||
|
||||
addTotalField(batteryObj, "soc", Battery.stateOfCharge, "%", 0);
|
||||
}
|
||||
|
||||
void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, String topic)
|
||||
|
||||
@ -58,23 +58,25 @@ void WebApiWsVedirectLiveClass::loop()
|
||||
if (millis() - _lastWsPublish > (10 * 1000) || (maxTimeStamp != _newestVedirectTimestamp)) {
|
||||
|
||||
try {
|
||||
String buffer;
|
||||
// free JsonDocument as soon as possible
|
||||
{
|
||||
DynamicJsonDocument root(1024);
|
||||
JsonVariant var = root;
|
||||
generateJsonResponse(var);
|
||||
|
||||
String buffer;
|
||||
if (buffer) {
|
||||
serializeJson(root, buffer);
|
||||
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password);
|
||||
}
|
||||
|
||||
_ws.textAll(buffer);
|
||||
serializeJson(root, buffer);
|
||||
}
|
||||
|
||||
if (buffer) {
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password);
|
||||
}
|
||||
|
||||
_ws.textAll(buffer);
|
||||
}
|
||||
|
||||
} catch (std::bad_alloc& bad_alloc) {
|
||||
MessageOutput.printf("Call to /api/vedirectlivedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||
}
|
||||
@ -153,10 +155,18 @@ void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U);
|
||||
JsonVariant root = response->getRoot().as<JsonVariant>();
|
||||
generateJsonResponse(root);
|
||||
try {
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U);
|
||||
JsonVariant root = response->getRoot();
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
generateJsonResponse(root);
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
} catch (std::bad_alloc& bad_alloc) {
|
||||
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||
|
||||
WebApi.sendTooManyRequests(request);
|
||||
}
|
||||
}
|
||||
67
src/main.cpp
67
src/main.cpp
@ -5,6 +5,7 @@
|
||||
#include "Configuration.h"
|
||||
#include "Display_Graphic.h"
|
||||
#include "InverterSettings.h"
|
||||
#include "Led_Single.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "VeDirectFrameHandler.h"
|
||||
#include "PylontechCanReceiver.h"
|
||||
@ -35,67 +36,67 @@ void setup()
|
||||
while (!Serial)
|
||||
yield();
|
||||
MessageOutput.println();
|
||||
MessageOutput.println(F("Starting OpenDTU"));
|
||||
MessageOutput.println("Starting OpenDTU");
|
||||
|
||||
// Initialize file system
|
||||
MessageOutput.print(F("Initialize FS... "));
|
||||
MessageOutput.print("Initialize FS... ");
|
||||
if (!LittleFS.begin(false)) { // Do not format if mount failed
|
||||
MessageOutput.print(F("failed... trying to format..."));
|
||||
MessageOutput.print("failed... trying to format...");
|
||||
if (!LittleFS.begin(true)) {
|
||||
MessageOutput.print("success");
|
||||
} else {
|
||||
MessageOutput.print("failed");
|
||||
}
|
||||
} else {
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
}
|
||||
|
||||
// Read configuration values
|
||||
MessageOutput.print(F("Reading configuration... "));
|
||||
MessageOutput.print("Reading configuration... ");
|
||||
if (!Configuration.read()) {
|
||||
MessageOutput.print(F("initializing... "));
|
||||
MessageOutput.print("initializing... ");
|
||||
Configuration.init();
|
||||
if (Configuration.write()) {
|
||||
MessageOutput.print(F("written... "));
|
||||
MessageOutput.print("written... ");
|
||||
} else {
|
||||
MessageOutput.print(F("failed... "));
|
||||
MessageOutput.print("failed... ");
|
||||
}
|
||||
}
|
||||
if (Configuration.get().Cfg_Version != CONFIG_VERSION) {
|
||||
MessageOutput.print(F("migrated... "));
|
||||
MessageOutput.print("migrated... ");
|
||||
Configuration.migrate();
|
||||
}
|
||||
CONFIG_T& config = Configuration.get();
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Load PinMapping
|
||||
MessageOutput.print(F("Reading PinMapping... "));
|
||||
MessageOutput.print("Reading PinMapping... ");
|
||||
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
|
||||
MessageOutput.print(F("found valid mapping "));
|
||||
MessageOutput.print("found valid mapping ");
|
||||
} else {
|
||||
MessageOutput.print(F("using default config "));
|
||||
MessageOutput.print("using default config ");
|
||||
}
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Initialize WiFi
|
||||
MessageOutput.print(F("Initialize Network... "));
|
||||
MessageOutput.print("Initialize Network... ");
|
||||
NetworkSettings.init();
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
NetworkSettings.applyConfig();
|
||||
|
||||
// Initialize NTP
|
||||
MessageOutput.print(F("Initialize NTP... "));
|
||||
MessageOutput.print("Initialize NTP... ");
|
||||
NtpSettings.init();
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Initialize SunPosition
|
||||
MessageOutput.print(F("Initialize SunPosition... "));
|
||||
MessageOutput.print("Initialize SunPosition... ");
|
||||
SunPosition.init();
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Initialize MqTT
|
||||
MessageOutput.print(F("Initialize MqTT... "));
|
||||
MessageOutput.print("Initialize MqTT... ");
|
||||
MqttSettings.init();
|
||||
MqttHandleDtu.init();
|
||||
MqttHandleInverter.init();
|
||||
@ -103,15 +104,15 @@ void setup()
|
||||
MqttHandleHass.init();
|
||||
MqttHandleVedirectHass.init();
|
||||
MqttHandleHuawei.init();
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Initialize WebApi
|
||||
MessageOutput.print(F("Initialize WebApi... "));
|
||||
MessageOutput.print("Initialize WebApi... ");
|
||||
WebApi.init();
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Initialize Display
|
||||
MessageOutput.print(F("Initialize Display... "));
|
||||
MessageOutput.print("Initialize Display... ");
|
||||
Display.init(
|
||||
static_cast<DisplayType_t>(pin.display_type),
|
||||
pin.display_data,
|
||||
@ -123,12 +124,17 @@ void setup()
|
||||
Display.enableScreensaver = config.Display_ScreenSaver;
|
||||
Display.setContrast(config.Display_Contrast);
|
||||
Display.setStartupDisplay();
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Initialize Single LEDs
|
||||
MessageOutput.print("Initialize LEDs... ");
|
||||
LedSingle.init();
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Check for default DTU serial
|
||||
MessageOutput.print(F("Check for default DTU serial... "));
|
||||
MessageOutput.print("Check for default DTU serial... ");
|
||||
if (config.Dtu_Serial == DTU_SERIAL) {
|
||||
MessageOutput.print(F("generate serial based on ESP chip id: "));
|
||||
MessageOutput.print("generate serial based on ESP chip id: ");
|
||||
uint64_t dtuId = Utils::generateDtuSerial();
|
||||
MessageOutput.printf("%0x%08x... ",
|
||||
((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
|
||||
@ -136,7 +142,8 @@ void setup()
|
||||
config.Dtu_Serial = dtuId;
|
||||
Configuration.write();
|
||||
}
|
||||
MessageOutput.println(F("done"));
|
||||
MessageOutput.println("done");
|
||||
MessageOutput.println("done");
|
||||
|
||||
InverterSettings.init();
|
||||
|
||||
@ -217,4 +224,6 @@ void loop()
|
||||
yield();
|
||||
HuaweiCan.loop();
|
||||
yield();
|
||||
LedSingle.loop();
|
||||
yield();
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.7",
|
||||
"bootstrap": "^5.3.0-alpha2",
|
||||
"bootstrap": "^5.3.0-alpha3",
|
||||
"bootstrap-icons-vue": "^1.10.3",
|
||||
"mitt": "^3.0.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
@ -24,17 +24,17 @@
|
||||
"@intlify/unplugin-vue-i18n": "^0.10.0",
|
||||
"@rushstack/eslint-patch": "^1.2.0",
|
||||
"@types/bootstrap": "^5.2.6",
|
||||
"@types/node": "^18.15.10",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/spark-md5": "^3.0.2",
|
||||
"@vitejs/plugin-vue": "^4.1.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-plugin-vue": "^9.10.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sass": "^1.60.0",
|
||||
"terser": "^5.16.8",
|
||||
"typescript": "^5.0.2",
|
||||
"typescript": "^5.0.3",
|
||||
"vite": "^4.2.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-css-injected-by-js": "^3.1.0",
|
||||
|
||||
@ -2,7 +2,15 @@
|
||||
<div :class="{'container-xxl': !isWideScreen,
|
||||
'container-fluid': isWideScreen}" role="main">
|
||||
<div class="page-header">
|
||||
<h1>{{ title }}</h1>
|
||||
<div class="row">
|
||||
<div class="col-sm-11">
|
||||
<h1>{{ title }}</h1>
|
||||
</div>
|
||||
<div class="col-sm-1" v-if="showReload">
|
||||
<button type="button" class="float-end btn btn-outline-primary"
|
||||
@click="$emit('reload')" v-tooltip :title="$t('base.Reload')" ><BIconArrowClockwise /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center" v-if="isLoading">
|
||||
@ -19,12 +27,17 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { BIconArrowClockwise } from 'bootstrap-icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BIconArrowClockwise,
|
||||
},
|
||||
props: {
|
||||
title: { type: String, required: true },
|
||||
isLoading: { type: Boolean, required: false, default: false },
|
||||
isWideScreen: { type: Boolean, required: false, default: false },
|
||||
showReload: { type: Boolean, required: false, default: false },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -106,7 +106,7 @@
|
||||
'text-bg-success': !batteryData.alarms.dischargeCurrent
|
||||
}">
|
||||
<template v-if="!batteryData.alarms.dischargeCurrent">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.alarms.dischargeCurrent">{{ $t('battery.alarm') }}</template>
|
||||
<template v-else>{{ $t('battery.alarm') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -115,7 +115,7 @@
|
||||
'text-bg-success': !batteryData.warnings.dischargeCurrent
|
||||
}">
|
||||
<template v-if="!batteryData.warnings.dischargeCurrent">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.warnings.dischargeCurrent">{{ $t('battery.warning') }}</template>
|
||||
<template v-else>{{ $t('battery.warning') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -127,7 +127,7 @@
|
||||
'text-bg-success': !batteryData.alarms.chargeCurrent
|
||||
}">
|
||||
<template v-if="!batteryData.alarms.chargeCurrent">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.alarms.chargeCurrent">{{ $t('battery.alarm') }}</template>
|
||||
<template v-else>{{ $t('battery.alarm') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -136,7 +136,7 @@
|
||||
'text-bg-success': !batteryData.warnings.chargeCurrent
|
||||
}">
|
||||
<template v-if="!batteryData.warnings.chargeCurrent">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.warnings.chargeCurrent">{{ $t('battery.warning') }}</template>
|
||||
<template v-else>{{ $t('battery.warning') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -148,7 +148,7 @@
|
||||
'text-bg-success': !batteryData.alarms.lowTemperature
|
||||
}">
|
||||
<template v-if="!batteryData.alarms.lowTemperature">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.alarms.lowTemperature">{{ $t('battery.alarm') }}</template>
|
||||
<template v-else>{{ $t('battery.alarm') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -157,7 +157,7 @@
|
||||
'text-bg-success': !batteryData.warnings.lowTemperature
|
||||
}">
|
||||
<template v-if="!batteryData.warnings.lowTemperature">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.warnings.lowTemperature">{{ $t('battery.warning') }}</template>
|
||||
<template v-else>{{ $t('battery.warning') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -169,7 +169,7 @@
|
||||
'text-bg-success': !batteryData.alarms.highTemperature
|
||||
}">
|
||||
<template v-if="!batteryData.alarms.highTemperature">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.alarms.highTemperature">{{ $t('battery.alarm') }}</template>
|
||||
<template v-else>{{ $t('battery.alarm') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -178,7 +178,7 @@
|
||||
'text-bg-success': !batteryData.warnings.highTemperature
|
||||
}">
|
||||
<template v-if="!batteryData.warnings.highTemperature">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.warnings.highTemperature">{{ $t('battery.warning') }}</template>
|
||||
<template v-else>{{ $t('battery.warning') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -190,7 +190,7 @@
|
||||
'text-bg-success': !batteryData.alarms.lowVoltage
|
||||
}">
|
||||
<template v-if="!batteryData.alarms.lowVoltage">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.alarms.lowVoltage">{{ $t('battery.alarm') }}</template>
|
||||
<template v-else>{{ $t('battery.alarm') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -199,7 +199,7 @@
|
||||
'text-bg-success': !batteryData.warnings.lowVoltage
|
||||
}">
|
||||
<template v-if="!batteryData.warnings.lowVoltage">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.warnings.lowVoltage">{{ $t('battery.warning') }}</template>
|
||||
<template v-else>{{ $t('battery.warning') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -211,7 +211,7 @@
|
||||
'text-bg-success': !batteryData.alarms.highVoltage
|
||||
}">
|
||||
<template v-if="!batteryData.alarms.highVoltage">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.alarms.highVoltage">{{ $t('battery.alarm') }}</template>
|
||||
<template v-else>{{ $t('battery.alarm') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -220,7 +220,7 @@
|
||||
'text-bg-success': !batteryData.warnings.highVoltage
|
||||
}">
|
||||
<template v-if="!batteryData.warnings.highVoltage">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.warnings.highVoltage">{{ $t('battery.warning') }}</template>
|
||||
<template v-else>{{ $t('battery.warning') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -232,7 +232,7 @@
|
||||
'text-bg-success': !batteryData.alarms.bmsInternal
|
||||
}">
|
||||
<template v-if="!batteryData.alarms.bmsInternal">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.alarms.bmsInternal">{{ $t('battery.alarm') }}</template>
|
||||
<template v-else>{{ $t('battery.alarm') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -241,7 +241,7 @@
|
||||
'text-bg-success': !batteryData.warnings.bmsInternal
|
||||
}">
|
||||
<template v-if="!batteryData.warnings.bmsInternal">{{ $t('battery.ok') }}</template>
|
||||
<template v-else="batteryData.warnings.bmsInternal">{{ $t('battery.warning') }}</template>
|
||||
<template v-else>{{ $t('battery.warning') }}</template>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ $t('firmwareinfo.FirmwareVersion') }}</th>
|
||||
<td><a :href="'https://github.com/tbnobody/OpenDTU/commits/' + systemStatus.git_hash?.substring(1)"
|
||||
<td><a :href="'https://github.com/tbnobody/OpenDTU/commits/' + systemStatus.git_hash"
|
||||
target="_blank" v-tooltip :title="$t('firmwareinfo.FirmwareVersionHint')">
|
||||
{{ systemStatus.git_hash?.substring(1) }}
|
||||
{{ systemStatus.git_hash }}
|
||||
</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@ -1,57 +1,123 @@
|
||||
<template>
|
||||
<div v-show="totalVeData.enabled">
|
||||
<div class="row row-cols-1 row-cols-md-3 g-3">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalYieldTotal') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalData.YieldTotal.v, 'decimal', {
|
||||
minimumFractionDigits: totalData.YieldTotal.d,
|
||||
maximumFractionDigits: totalData.YieldTotal.d
|
||||
})}}
|
||||
<small class="text-muted">{{ totalData.YieldTotal.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.MpptTotalYieldTotal') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalVeData.total.YieldTotal.v, 'decimal', {
|
||||
minimumFractionDigits: totalVeData.total.YieldTotal.d,
|
||||
maximumFractionDigits: totalVeData.total.YieldTotal.d
|
||||
}) }}
|
||||
<small class="text-muted">{{ totalVeData.total.YieldTotal.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalYieldDay') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalData.YieldDay.v, 'decimal', {
|
||||
minimumFractionDigits: totalData.YieldDay.d,
|
||||
maximumFractionDigits: totalData.YieldDay.d
|
||||
})}}
|
||||
<small class="text-muted">{{ totalData.YieldDay.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.MpptTotalYieldDay') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalVeData.total.YieldDay.v, 'decimal', {
|
||||
minimumFractionDigits: totalVeData.total.YieldDay.d,
|
||||
maximumFractionDigits: totalVeData.total.YieldDay.d
|
||||
}) }}
|
||||
<small class="text-muted">{{ totalVeData.total.YieldDay.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalPower') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalData.Power.v, 'decimal', {
|
||||
minimumFractionDigits: totalData.Power.d,
|
||||
maximumFractionDigits: totalData.Power.d
|
||||
})}}
|
||||
<small class="text-muted">{{ totalData.Power.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.MpptTotalPower') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalVeData.total.Power.v, 'decimal', {
|
||||
minimumFractionDigits: totalVeData.total.Power.d,
|
||||
maximumFractionDigits: totalVeData.total.Power.d
|
||||
}) }}
|
||||
<small class="text-muted">{{ totalVeData.total.Power.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cols-1 row-cols-md-3 g-3">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.InverterTotalYieldTotal') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalData.YieldTotal.v, 'decimal', {
|
||||
minimumFractionDigits: totalData.YieldTotal.d,
|
||||
maximumFractionDigits: totalData.YieldTotal.d
|
||||
}) }}
|
||||
<small class="text-muted">{{ totalData.YieldTotal.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.InverterTotalYieldDay') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalData.YieldDay.v, 'decimal', {
|
||||
minimumFractionDigits: totalData.YieldDay.d,
|
||||
maximumFractionDigits: totalData.YieldDay.d
|
||||
}) }}
|
||||
<small class="text-muted">{{ totalData.YieldDay.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.InverterTotalPower') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalData.Power.v, 'decimal', {
|
||||
minimumFractionDigits: totalData.Power.d,
|
||||
maximumFractionDigits: totalData.Power.d
|
||||
}) }}
|
||||
<small class="text-muted">{{ totalData.Power.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="totalBattData.enabled">
|
||||
<div class="row row-cols-1 row-cols-md-3 g-3">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.BatterySoc') }}</div>
|
||||
<div class="card-body card-text text-center">
|
||||
<h2>
|
||||
{{ $n(totalBattData.soc.v, 'decimal', {
|
||||
minimumFractionDigits: totalBattData.soc.d,
|
||||
maximumFractionDigits: totalBattData.soc.d
|
||||
}) }}
|
||||
<small class="text-muted">{{ totalBattData.soc.u }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Total } from '@/types/LiveDataStatus';
|
||||
import type { Battery, Total, Vedirect } from '@/types/LiveDataStatus';
|
||||
import { defineComponent, type PropType } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
totalData: { type: Object as PropType<Total>, required: true },
|
||||
},
|
||||
props: {
|
||||
totalData: { type: Object as PropType<Total>, required: true },
|
||||
totalVeData: { type: Object as PropType<Vedirect>, required: true },
|
||||
totalBattData: { type: Object as PropType<Battery>, required: true },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -28,7 +28,8 @@
|
||||
"Login": "Anmelden"
|
||||
},
|
||||
"base": {
|
||||
"Loading": "Lade..."
|
||||
"Loading": "Lade...",
|
||||
"Reload": "Aktualisieren"
|
||||
},
|
||||
"localeswitcher": {
|
||||
"Dark": "Dunkel",
|
||||
@ -325,9 +326,13 @@
|
||||
"Unit": "Einheit"
|
||||
},
|
||||
"invertertotalinfo": {
|
||||
"TotalYieldTotal": "Gesamtertrag Insgesamt",
|
||||
"TotalYieldDay": "Gesamtertrag Heute",
|
||||
"TotalPower": "Gesamtleistung"
|
||||
"InverterTotalYieldTotal": "Inverter Gesamtertrag Insgesamt",
|
||||
"InverterTotalYieldDay": "Inverter Gesamtertrag Heute",
|
||||
"InverterTotalPower": "Inverter Gesamtleistung",
|
||||
"MpptTotalYieldTotal": "MPPT Gesamtertrag Insgesamt",
|
||||
"MpptTotalYieldDay": "MPPT Gesamtertrag Heute",
|
||||
"MpptTotalPower": "MPPT Gesamtleistung",
|
||||
"BatterySoc": "Ladezustand"
|
||||
},
|
||||
"inverterchannelproperty": {
|
||||
"Power": "Leistung",
|
||||
|
||||
@ -28,7 +28,8 @@
|
||||
"Login": "Login"
|
||||
},
|
||||
"base": {
|
||||
"Loading": "Loading..."
|
||||
"Loading": "Loading...",
|
||||
"Reload": "Reload"
|
||||
},
|
||||
"localeswitcher": {
|
||||
"Dark": "Dark",
|
||||
@ -325,9 +326,13 @@
|
||||
"Unit": "Unit"
|
||||
},
|
||||
"invertertotalinfo": {
|
||||
"TotalYieldTotal": "Total Yield Total",
|
||||
"TotalYieldDay": "Total Yield Day",
|
||||
"TotalPower": "Total Power"
|
||||
"InverterTotalYieldTotal": "Inverter Total Yield Total",
|
||||
"InverterTotalYieldDay": "Inverter Total Yield Day",
|
||||
"InverterTotalPower": "Inverter Total Power",
|
||||
"MpptTotalYieldTotal": "MPPT Total Yield Total",
|
||||
"MpptTotalYieldDay": "MPPT Total Yield Day",
|
||||
"MpptTotalPower": "MPPT Total Power",
|
||||
"BatterySoc": "State of charge"
|
||||
},
|
||||
"inverterchannelproperty": {
|
||||
"Power": "Power",
|
||||
|
||||
@ -28,7 +28,8 @@
|
||||
"Login": "Connexion"
|
||||
},
|
||||
"base": {
|
||||
"Loading": "Chargement..."
|
||||
"Loading": "Chargement...",
|
||||
"Reload": "Reload"
|
||||
},
|
||||
"localeswitcher": {
|
||||
"Dark": "Dark",
|
||||
@ -324,9 +325,13 @@
|
||||
"Unit": "Unité"
|
||||
},
|
||||
"invertertotalinfo": {
|
||||
"TotalYieldTotal": "Rendement total",
|
||||
"TotalYieldDay": "Rendement du jour",
|
||||
"TotalPower": "Puissance de l'installation"
|
||||
"InverterTotalYieldTotal": "Onduleurs rendement total",
|
||||
"InverterTotalYieldDay": "Onduleurs rendement du jour",
|
||||
"InverterTotalPower": "Onduleurs puissance de l'installation",
|
||||
"MpptTotalYieldTotal": "MPPT rendement total",
|
||||
"MpptTotalYieldDay": "MPPT rendement du jour",
|
||||
"MpptTotalPower": "MPPT puissance de l'installation",
|
||||
"BatterySoc": "State of charge"
|
||||
},
|
||||
"inverterchannelproperty": {
|
||||
"Power": "Puissance",
|
||||
|
||||
@ -24,6 +24,7 @@ export interface Inverter {
|
||||
serial: number;
|
||||
name: string;
|
||||
data_age: number;
|
||||
poll_enabled: boolean;
|
||||
reachable: boolean;
|
||||
producing: boolean;
|
||||
limit_relative: number;
|
||||
@ -48,6 +49,7 @@ export interface Hints {
|
||||
|
||||
export interface Vedirect {
|
||||
enabled: boolean;
|
||||
total: Total;
|
||||
}
|
||||
|
||||
export interface Huawei {
|
||||
@ -56,6 +58,7 @@ export interface Huawei {
|
||||
|
||||
export interface Battery {
|
||||
enabled: boolean;
|
||||
soc: ValueObject;
|
||||
}
|
||||
|
||||
export interface LiveData {
|
||||
|
||||
@ -87,7 +87,7 @@ export default defineComponent({
|
||||
|
||||
this.socket.onopen = function (event) {
|
||||
console.log(event);
|
||||
console.log("Successfuly connected to the echo websocket server...");
|
||||
console.log("Successfully connected to the echo websocket server...");
|
||||
};
|
||||
|
||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<BasePage :title="$t('home.LiveData')" :isLoading="dataLoading" :isWideScreen="true">
|
||||
<HintView :hints="liveData.hints" />
|
||||
<InverterTotalInfo :totalData="liveData.total" /><br />
|
||||
<InverterTotalInfo :totalData="liveData.total" :totalVeData="liveData.vedirect" :totalBattData="liveData.battery"/><br />
|
||||
<div class="row gy-3">
|
||||
<div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { 'display': 'none' } : {}]">
|
||||
<div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
||||
@ -27,9 +27,10 @@
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center"
|
||||
:class="{
|
||||
'text-bg-danger': !inverter.reachable,
|
||||
'text-bg-warning': inverter.reachable && !inverter.producing,
|
||||
'text-bg-primary': inverter.reachable && inverter.producing,
|
||||
'text-bg-tertiary': !inverter.poll_enabled,
|
||||
'text-bg-danger': inverter.poll_enabled && !inverter.reachable,
|
||||
'text-bg-warning': inverter.poll_enabled && inverter.reachable && !inverter.producing,
|
||||
'text-bg-primary': inverter.poll_enabled && inverter.reachable && inverter.producing,
|
||||
}">
|
||||
<div class="p-1 flex-grow-1">
|
||||
<div class="d-flex flex-wrap">
|
||||
@ -502,7 +503,7 @@ export default defineComponent({
|
||||
|
||||
this.socket.onopen = function (event) {
|
||||
console.log(event);
|
||||
console.log("Successfuly connected to the echo websocket server...");
|
||||
console.log("Successfully connected to the echo websocket server...");
|
||||
};
|
||||
|
||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BasePage :title="$t('mqttinfo.MqttInformation')" :isLoading="dataLoading">
|
||||
<BasePage :title="$t('mqttinfo.MqttInformation')" :isLoading="dataLoading" :show-reload="true" @reload="getMqttInfo">
|
||||
<CardElement :text="$t('mqttinfo.ConfigurationSummary')" textVariant="text-bg-primary">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BasePage :title="$t('networkinfo.NetworkInformation')" :isLoading="dataLoading">
|
||||
<BasePage :title="$t('networkinfo.NetworkInformation')" :isLoading="dataLoading" :show-reload="true" @reload="getNetworkInfo">
|
||||
<WifiStationInfo :networkStatus="networkDataList" />
|
||||
<div class="mt-5"></div>
|
||||
<WifiApInfo :networkStatus="networkDataList" />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BasePage :title="$t('ntpinfo.NtpInformation')" :isLoading="dataLoading">
|
||||
<BasePage :title="$t('ntpinfo.NtpInformation')" :isLoading="dataLoading" :show-reload="true" @reload="getNtpInfo">
|
||||
<CardElement :text="$t('ntpinfo.ConfigurationSummary')" textVariant="text-bg-primary">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BasePage :title="$t('systeminfo.SystemInfo')" :isLoading="dataLoading">
|
||||
<BasePage :title="$t('systeminfo.SystemInfo')" :isLoading="dataLoading" :show-reload="true" @reload="getSystemInfo">
|
||||
<FirmwareInfo :systemStatus="systemDataList" />
|
||||
<div class="mt-5"></div>
|
||||
<HardwareInfo :systemStatus="systemDataList" />
|
||||
@ -50,8 +50,16 @@ export default defineComponent({
|
||||
})
|
||||
},
|
||||
getUpdateInfo() {
|
||||
// If the left char is a "g" the value is the git hash (remove the "g")
|
||||
this.systemDataList.git_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g' ? this.systemDataList.git_hash?.substring(1) : this.systemDataList.git_hash;
|
||||
|
||||
// Handle format "v0.1-5-gabcdefh"
|
||||
if (this.systemDataList.git_hash.lastIndexOf("-") >= 0) {
|
||||
this.systemDataList.git_hash = this.systemDataList.git_hash.substring(this.systemDataList.git_hash.lastIndexOf("-") + 2)
|
||||
}
|
||||
|
||||
const fetchUrl = "https://api.github.com/repos/tbnobody/OpenDTU/compare/"
|
||||
+ this.systemDataList.git_hash?.substring(1) + "...HEAD";
|
||||
+ this.systemDataList.git_hash + "...HEAD";
|
||||
|
||||
fetch(fetchUrl)
|
||||
.then((response) => {
|
||||
|
||||
@ -136,14 +136,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403"
|
||||
integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==
|
||||
|
||||
"@eslint/eslintrc@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.1.tgz#7888fe7ec8f21bc26d646dbd2c11cd776e21192d"
|
||||
integrity sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==
|
||||
"@eslint/eslintrc@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02"
|
||||
integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==
|
||||
dependencies:
|
||||
ajv "^6.12.4"
|
||||
debug "^4.3.2"
|
||||
espree "^9.5.0"
|
||||
espree "^9.5.1"
|
||||
globals "^13.19.0"
|
||||
ignore "^5.2.0"
|
||||
import-fresh "^3.2.1"
|
||||
@ -151,10 +151,10 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@8.36.0":
|
||||
version "8.36.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe"
|
||||
integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==
|
||||
"@eslint/js@8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d"
|
||||
integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==
|
||||
|
||||
"@humanwhocodes/config-array@^0.11.8":
|
||||
version "0.11.8"
|
||||
@ -361,10 +361,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||
|
||||
"@types/node@^18.15.10":
|
||||
version "18.15.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.10.tgz#4ee2171c3306a185d1208dad5f44dae3dee4cfe3"
|
||||
integrity sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==
|
||||
"@types/node@^18.15.11":
|
||||
version "18.15.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
|
||||
integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==
|
||||
|
||||
"@types/spark-md5@^3.0.2":
|
||||
version "3.0.2"
|
||||
@ -730,10 +730,10 @@ bootstrap-icons-vue@^1.10.3:
|
||||
resolved "https://registry.yarnpkg.com/bootstrap-icons-vue/-/bootstrap-icons-vue-1.10.3.tgz#ae725513c9655ce86effa2a0b09e9e65b02c8f1a"
|
||||
integrity sha512-BzqmLufgHjFvSReJ1GQqNkl780UFK0rWT4Y1IQC7lZClXyOSsM5Ipw04BnuVmmrqgtSxzak83jcBwLJgCK3scg==
|
||||
|
||||
bootstrap@^5.3.0-alpha2:
|
||||
version "5.3.0-alpha2"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.0-alpha2.tgz#81ac5909258dc3abb3cc08348853d121ecac619d"
|
||||
integrity sha512-r1ayh5d56rhwrezVjhM8bt8tiUcdQ8J9JeCwCTAeKSpOrCWH5V5S6XiJ26DqbX7o5QSH5HIKm7W88CKxVjhCVQ==
|
||||
bootstrap@^5.3.0-alpha3:
|
||||
version "5.3.0-alpha3"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.0-alpha3.tgz#ad64d9a663c53ab7aca99c560e0bd16b5e023441"
|
||||
integrity sha512-FBhOWMxkCFr74hesJdchLXhqagPTXS+kRNU3gE0FR5Ki/AdPSz32Ik96Z28+yBluCnE/pc9st7l1yPwKgbtfSA==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
@ -1064,15 +1064,20 @@ eslint-visitor-keys@^3.3.0:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
||||
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
||||
|
||||
eslint@^8.36.0:
|
||||
version "8.36.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf"
|
||||
integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==
|
||||
eslint-visitor-keys@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc"
|
||||
integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==
|
||||
|
||||
eslint@^8.37.0:
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.37.0.tgz#1f660ef2ce49a0bfdec0b0d698e0b8b627287412"
|
||||
integrity sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@eslint-community/regexpp" "^4.4.0"
|
||||
"@eslint/eslintrc" "^2.0.1"
|
||||
"@eslint/js" "8.36.0"
|
||||
"@eslint/eslintrc" "^2.0.2"
|
||||
"@eslint/js" "8.37.0"
|
||||
"@humanwhocodes/config-array" "^0.11.8"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
"@nodelib/fs.walk" "^1.2.8"
|
||||
@ -1083,8 +1088,8 @@ eslint@^8.36.0:
|
||||
doctrine "^3.0.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
eslint-scope "^7.1.1"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
espree "^9.5.0"
|
||||
eslint-visitor-keys "^3.4.0"
|
||||
espree "^9.5.1"
|
||||
esquery "^1.4.2"
|
||||
esutils "^2.0.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
@ -1128,14 +1133,14 @@ espree@^9.3.1:
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
espree@^9.5.0:
|
||||
version "9.5.0"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113"
|
||||
integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==
|
||||
espree@^9.5.1:
|
||||
version "9.5.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4"
|
||||
integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==
|
||||
dependencies:
|
||||
acorn "^8.8.0"
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
eslint-visitor-keys "^3.4.0"
|
||||
|
||||
esprima@^4.0.1:
|
||||
version "4.0.1"
|
||||
@ -2316,10 +2321,10 @@ type-fest@^0.20.2:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
|
||||
typescript@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5"
|
||||
integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==
|
||||
typescript@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.3.tgz#fe976f0c826a88d0a382007681cbb2da44afdedf"
|
||||
integrity sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user