Merge remote-tracking branch 'tbnobody/OpenDTU/master'
15
.github/workflows/build.yml
vendored
@ -1,6 +1,14 @@
|
|||||||
name: OpenDTU Build
|
name: OpenDTU Build
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- docs/**
|
||||||
|
- '**/*.md'
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- docs/**
|
||||||
|
- '**/*.md'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get_default_envs:
|
get_default_envs:
|
||||||
@ -92,12 +100,15 @@ jobs:
|
|||||||
name: opendtu-${{ matrix.environment }}
|
name: opendtu-${{ matrix.environment }}
|
||||||
path: |
|
path: |
|
||||||
.pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin
|
.pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin
|
||||||
|
.pio/build/${{ matrix.environment }}/partitions.bin
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
name: opendtu-release
|
name: opendtu-release
|
||||||
path: .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin
|
path: |
|
||||||
|
.pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin
|
||||||
|
.pio/build/${{ matrix.environment }}/partitions.bin
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Create Release
|
name: Create Release
|
||||||
|
|||||||
21
.github/workflows/cpplint.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: cpplint
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install cpplint
|
||||||
|
- name: Linting
|
||||||
|
run: |
|
||||||
|
cpplint --repository=. --recursive --filter=-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles
|
||||||
65
README.md
@ -1,5 +1,8 @@
|
|||||||
# OpenDTU_VeDirect
|
# OpenDTU_VeDirect
|
||||||
|
|
||||||
|
[](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml)
|
||||||
|
[](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml)
|
||||||
|
|
||||||
## Background
|
## Background
|
||||||
This project was started from [this](https://www.mikrocontroller.net/topic/525778) discussion (Mikrocontroller.net).
|
This project was started from [this](https://www.mikrocontroller.net/topic/525778) discussion (Mikrocontroller.net).
|
||||||
It was the goal to replace the original Hoymiles DTU (Telemetry Gateway) with their cloud access. With a lot of reverse engineering the Hoymiles protocol was decrypted and analyzed.
|
It was the goal to replace the original Hoymiles DTU (Telemetry Gateway) with their cloud access. With a lot of reverse engineering the Hoymiles protocol was decrypted and analyzed.
|
||||||
@ -55,6 +58,7 @@ Sends text raw data as difined in VE.Direct spec.
|
|||||||
* Read live data from inverter
|
* Read live data from inverter
|
||||||
* Show inverters internal event log
|
* Show inverters internal event log
|
||||||
* Show inverter information like firmware version, firmware build date, hardware revision and hardware version
|
* Show inverter information like firmware version, firmware build date, hardware revision and hardware version
|
||||||
|
* Show current inverter limit (setting the limit is not yet implemented)
|
||||||
* Uses ESP32 microcontroller and NRF24L01+
|
* Uses ESP32 microcontroller and NRF24L01+
|
||||||
* Multi-Inverter support
|
* Multi-Inverter support
|
||||||
* MQTT support (with TLS)
|
* MQTT support (with TLS)
|
||||||
@ -75,6 +79,39 @@ Sends text raw data as difined in VE.Direct spec.
|
|||||||
* Build with [Vue.js](https://vuejs.org)
|
* Build with [Vue.js](https://vuejs.org)
|
||||||
* Source is written in TypeScript
|
* Source is written in TypeScript
|
||||||
|
|
||||||
|
## Hardware you need
|
||||||
|
|
||||||
|
### ESP32 board
|
||||||
|
For ease of use, buy a "ESP32 DEVKIT DOIT" or "ESP32 NodeMCU Development Board" with an ESP32-S3 or ESP-WROOM-32 chipset on it.
|
||||||
|
|
||||||
|
Sample Picture:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Also supported: Board with Ethernet-Connector and Power-over-Ethernet [Olimex ESP32-POE](https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware)
|
||||||
|
|
||||||
|
#### NRF24L01+ radio board
|
||||||
|
The PLUS sign is IMPORTANT! There are different variants available, with antenna on the printed circuit board or external antenna.
|
||||||
|
|
||||||
|
Sample picture:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Buy your hardware from a trusted source, at best from a dealer/online shop in your country where you have support and the right to return non-functional hardware.
|
||||||
|
When you want to buy from Amazon, AliExpress, eBay etc., take note that there is a lot of low-quality or fake hardware offered. Read customer comments and ratings carefully!
|
||||||
|
|
||||||
|
A heavily incomplete list of trusted hardware shops in germany is:
|
||||||
|
|
||||||
|
* [AZ-Delivery](https://www.az-delivery.de/)
|
||||||
|
* [Makershop](https://www.makershop.de/)
|
||||||
|
* [Berrybase](https://www.berrybase.de/)
|
||||||
|
|
||||||
|
This list is for your convenience only, the project is not related to any of these shops.
|
||||||
|
|
||||||
|
#### Power supply
|
||||||
|
Use a power suppy with 5V and 1A. The USB cable connected to your PC/Notebook may be powerful enough or may be not.
|
||||||
|
|
||||||
|
|
||||||
## Wiring up
|
## Wiring up
|
||||||
### Schematic
|
### Schematic
|
||||||

|

|
||||||
@ -98,11 +135,13 @@ This can be achieved by editing the 'platformio.ini' file and add/change one or
|
|||||||
|
|
||||||
## Flashing and starting up
|
## Flashing and starting up
|
||||||
### with Visual Studio Code
|
### with Visual Studio Code
|
||||||
* Install [Visual Studio Code](https://code.visualstudio.com/download)
|
* Install [Visual Studio Code](https://code.visualstudio.com/download) (from now named "vscode")
|
||||||
* In Visual Studio Code, install the [PlatformIO Extension](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide)
|
* In Visual Studio Code, install the [PlatformIO Extension](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide)
|
||||||
* Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur)
|
* Install git and enable git in vscode - [git download](https://git-scm.com/downloads/) - [Instructions](https://www.jcchouinard.com/install-git-in-vscode/)
|
||||||
* In Visual Studio Code, choose File --> Open Folder and select the previously downloaded source code. (You have to select the folder which contains the "platformio.ini" file)
|
* Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur): Inside vscode open the command palette by pressing `CTRL` + `SHIFT` + `P`. Enter `git clone`, add the repository-URL `https://github.com/tbnobody/OpenDTU`. Next you have to choose (or create) a target directory.
|
||||||
* Adjust the COM port in the file "platformio.ini". It occurs twice:
|
* In vscode, choose File --> Open Folder and select the previously downloaded source code. (You have to select the folder which contains the "platformio.ini" file)
|
||||||
|
* There is a short [Video](https://youtu.be/9cA_esv3zeA) showing these steps.
|
||||||
|
* Adjust the COM port in the file "platformio.ini" for your USB-serial-converter. It occurs twice:
|
||||||
* upload_port
|
* upload_port
|
||||||
* monitor_port
|
* monitor_port
|
||||||
* Select the arrow button in the status bar (PlatformIO: Upload) to compile and upload the firmware. During the compilation, all required libraries are downloaded automatically.
|
* Select the arrow button in the status bar (PlatformIO: Upload) to compile and upload the firmware. During the compilation, all required libraries are downloaded automatically.
|
||||||
@ -152,3 +191,21 @@ After the successful upload, the OpenDTU immediately restarts into the new firmw
|
|||||||
|
|
||||||
* Building the microcontroller firmware
|
* Building the microcontroller firmware
|
||||||
* Visual Studio Code with the PlatformIO Extension is required for building
|
* Visual Studio Code with the PlatformIO Extension is required for building
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
* First: When there is no light on the solar panels, the inverter completely turns off and does not answer to OpenDTU! So if you assembled your OpenDTU in the evening, wait until tomorrow.
|
||||||
|
* When there is no data received from the inverter(s) - try to reduce the distance between the openDTU and the inverter (e.g. move it to the window towards the roof)
|
||||||
|
* Under Settings -> DTU Settings you can increase the transmit power "PA level". Default is "minimum".
|
||||||
|
* The NRF24L01+ needs relatively much current. With bad power supply (and especially bad cables!) a 10uF capacitor soldered directly to the NRF24L01+ board connector brings more stability (pin 1+2 are the power supply). Note the polarity of the capacitor....
|
||||||
|
* You can try to use an USB power supply with 1A or more instead of connecting the ESP32 to the computer.
|
||||||
|
* Try a different USB cable. Once again, a stable power source is important. Some USB cables are made of much plastic and very little copper inside.
|
||||||
|
* Double-Check that you have a radio module NRF24L01+ with a plus sign at the end. NRF24L01 module without the plus are not compatible with this project.
|
||||||
|
* There is no possibility of auto-discovering the inverters. Double-Check you have entered the serial numbers of the inverters correctly.
|
||||||
|
* OpenDTU needs access to a working NTP server to get the current date & time.
|
||||||
|
* If your problem persists, check the [Issues on Github](https://github.com/tbnobody/OpenDTU/issues). Please inspect not only the open issues, also the closed issues contain useful information.
|
||||||
|
* Another source of information are the [Discussions](https://github.com/tbnobody/OpenDTU/discussions/)
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
- [Ahoy](https://github.com/grindylow/ahoy)
|
||||||
|
- [DTU Simulator](https://github.com/Ziyatoe/DTUsimMI1x00-Hoymiles)
|
||||||
|
- [OpenDTU extended to talk to Victrons MPPT battery chargers (Ve.Direct)](https://github.com/helgeerbe/OpenDTU_VeDirect)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
BIN
docs/nodemcu-esp32.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
docs/nrf24l01plus.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 57 KiB |
BIN
docs/screenshots/14_ConfigManagement.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
@ -50,7 +50,7 @@ public:
|
|||||||
IPAddress gatewayIP();
|
IPAddress gatewayIP();
|
||||||
IPAddress dnsIP(uint8_t dns_no = 0);
|
IPAddress dnsIP(uint8_t dns_no = 0);
|
||||||
String macAddress();
|
String macAddress();
|
||||||
const char* getHostname();
|
static String getHostname();
|
||||||
bool isConnected();
|
bool isConnected();
|
||||||
network_mode NetworkMode();
|
network_mode NetworkMode();
|
||||||
|
|
||||||
@ -62,12 +62,13 @@ private:
|
|||||||
void setStaticIp();
|
void setStaticIp();
|
||||||
void setupMode();
|
void setupMode();
|
||||||
void NetworkEvent(WiFiEvent_t event);
|
void NetworkEvent(WiFiEvent_t event);
|
||||||
|
static uint32_t getChipId();
|
||||||
bool adminEnabled = true;
|
bool adminEnabled = true;
|
||||||
bool forceDisconnection = false;
|
bool forceDisconnection = false;
|
||||||
int adminTimeoutCounter = 0;
|
int adminTimeoutCounter = 0;
|
||||||
int connectTimeoutTimer = 0;
|
int connectTimeoutTimer = 0;
|
||||||
int connectRedoTimer = 0;
|
int connectRedoTimer = 0;
|
||||||
unsigned long lastTimerCall = 0;
|
uint32_t lastTimerCall = 0;
|
||||||
const byte DNS_PORT = 53;
|
const byte DNS_PORT = 53;
|
||||||
IPAddress apIp;
|
IPAddress apIp;
|
||||||
IPAddress apNetmask;
|
IPAddress apNetmask;
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "WebApi_config.h"
|
||||||
#include "WebApi_devinfo.h"
|
#include "WebApi_devinfo.h"
|
||||||
#include "WebApi_dtu.h"
|
#include "WebApi_dtu.h"
|
||||||
#include "WebApi_eventlog.h"
|
#include "WebApi_eventlog.h"
|
||||||
#include "WebApi_firmware.h"
|
#include "WebApi_firmware.h"
|
||||||
#include "WebApi_inverter.h"
|
#include "WebApi_inverter.h"
|
||||||
|
#include "WebApi_limit.h"
|
||||||
#include "WebApi_mqtt.h"
|
#include "WebApi_mqtt.h"
|
||||||
#include "WebApi_network.h"
|
#include "WebApi_network.h"
|
||||||
#include "WebApi_ntp.h"
|
#include "WebApi_ntp.h"
|
||||||
@ -26,11 +28,13 @@ private:
|
|||||||
AsyncWebServer _server;
|
AsyncWebServer _server;
|
||||||
AsyncEventSource _events;
|
AsyncEventSource _events;
|
||||||
|
|
||||||
|
WebApiConfigClass _webApiConfig;
|
||||||
WebApiDevInfoClass _webApiDevInfo;
|
WebApiDevInfoClass _webApiDevInfo;
|
||||||
WebApiDtuClass _webApiDtu;
|
WebApiDtuClass _webApiDtu;
|
||||||
WebApiEventlogClass _webApiEventlog;
|
WebApiEventlogClass _webApiEventlog;
|
||||||
WebApiFirmwareClass _webApiFirmware;
|
WebApiFirmwareClass _webApiFirmware;
|
||||||
WebApiInverterClass _webApiInverter;
|
WebApiInverterClass _webApiInverter;
|
||||||
|
WebApiLimitClass _webApiLimit;
|
||||||
WebApiMqttClass _webApiMqtt;
|
WebApiMqttClass _webApiMqtt;
|
||||||
WebApiNetworkClass _webApiNetwork;
|
WebApiNetworkClass _webApiNetwork;
|
||||||
WebApiNtpClass _webApiNtp;
|
WebApiNtpClass _webApiNtp;
|
||||||
|
|||||||
18
include/WebApi_config.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
class WebApiConfigClass {
|
||||||
|
public:
|
||||||
|
void init(AsyncWebServer* server);
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onConfigGet(AsyncWebServerRequest* request);
|
||||||
|
void onConfigDelete(AsyncWebServerRequest* request);
|
||||||
|
void onConfigUploadFinish(AsyncWebServerRequest* request);
|
||||||
|
void onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final);
|
||||||
|
|
||||||
|
AsyncWebServer* _server;
|
||||||
|
};
|
||||||
15
include/WebApi_limit.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
class WebApiLimitClass {
|
||||||
|
public:
|
||||||
|
void init(AsyncWebServer* server);
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onLimitStatus(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
|
AsyncWebServer* _server;
|
||||||
|
};
|
||||||
@ -14,7 +14,7 @@ private:
|
|||||||
void onMqttStatus(AsyncWebServerRequest* request);
|
void onMqttStatus(AsyncWebServerRequest* request);
|
||||||
void onMqttAdminGet(AsyncWebServerRequest* request);
|
void onMqttAdminGet(AsyncWebServerRequest* request);
|
||||||
void onMqttAdminPost(AsyncWebServerRequest* request);
|
void onMqttAdminPost(AsyncWebServerRequest* request);
|
||||||
String getRootCaCertInfo(char* cert);
|
String getRootCaCertInfo(const char* cert);
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
};
|
};
|
||||||
@ -22,6 +22,6 @@ private:
|
|||||||
|
|
||||||
uint32_t _lastWsPublish = 0;
|
uint32_t _lastWsPublish = 0;
|
||||||
uint32_t _lastInvUpdateCheck = 0;
|
uint32_t _lastInvUpdateCheck = 0;
|
||||||
unsigned long _lastWsCleanup = 0;
|
uint32_t _lastWsCleanup = 0;
|
||||||
uint32_t _newestInverterTimestamp = 0;
|
uint32_t _newestInverterTimestamp = 0;
|
||||||
};
|
};
|
||||||
@ -31,6 +31,12 @@ void HoymilesClass::loop()
|
|||||||
// Fetch event log
|
// Fetch event log
|
||||||
iv->sendAlarmLogRequest(_radio.get());
|
iv->sendAlarmLogRequest(_radio.get());
|
||||||
|
|
||||||
|
// Fetch limit
|
||||||
|
if ((iv->SystemConfigPara()->getLastUpdate() == 0) || (millis() - iv->SystemConfigPara()->getLastUpdate() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL)) {
|
||||||
|
Serial.println("Request SystemConfigPara");
|
||||||
|
iv->sendSystemConfigParaRequest(_radio.get());
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch dev info (but first fetch stats)
|
// Fetch dev info (but first fetch stats)
|
||||||
if (iv->Statistics()->getLastUpdate() > 0 && (iv->DevInfo()->getLastUpdateAll() == 0 || iv->DevInfo()->getLastUpdateSample() == 0)) {
|
if (iv->Statistics()->getLastUpdate() > 0 && (iv->DevInfo()->getLastUpdateAll() == 0 || iv->DevInfo()->getLastUpdateSample() == 0)) {
|
||||||
Serial.println(F("Request device info"));
|
Serial.println(F("Request device info"));
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#define HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL (10 * 60 * 1000) // 10 minutes
|
||||||
|
|
||||||
class HoymilesClass {
|
class HoymilesClass {
|
||||||
public:
|
public:
|
||||||
void init();
|
void init();
|
||||||
@ -28,7 +30,7 @@ private:
|
|||||||
std::vector<std::shared_ptr<InverterAbstract>> _inverters;
|
std::vector<std::shared_ptr<InverterAbstract>> _inverters;
|
||||||
std::unique_ptr<HoymilesRadio> _radio;
|
std::unique_ptr<HoymilesRadio> _radio;
|
||||||
|
|
||||||
uint32_t _pollInterval;
|
uint32_t _pollInterval = 0;
|
||||||
uint32_t _lastPoll = 0;
|
uint32_t _lastPoll = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,6 @@
|
|||||||
#include <nRF24L01.h>
|
#include <nRF24L01.h>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
// number of fragments hold in buffer
|
// number of fragments hold in buffer
|
||||||
#define FRAGMENT_BUFFER_SIZE 30
|
#define FRAGMENT_BUFFER_SIZE 30
|
||||||
|
|
||||||
@ -57,7 +55,7 @@ public:
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
T* enqueCommand()
|
T* enqueCommand()
|
||||||
{
|
{
|
||||||
_commandQueue.push(make_shared<T>());
|
_commandQueue.push(std::make_shared<T>());
|
||||||
return static_cast<T*>(_commandQueue.back().get());
|
return static_cast<T*>(_commandQueue.back().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,12 +73,12 @@ private:
|
|||||||
std::unique_ptr<SPIClass> _hspi;
|
std::unique_ptr<SPIClass> _hspi;
|
||||||
std::unique_ptr<RF24> _radio;
|
std::unique_ptr<RF24> _radio;
|
||||||
uint8_t _rxChLst[5] = { 3, 23, 40, 61, 75 };
|
uint8_t _rxChLst[5] = { 3, 23, 40, 61, 75 };
|
||||||
uint8_t _rxChIdx;
|
uint8_t _rxChIdx = 0;
|
||||||
|
|
||||||
uint8_t _txChLst[5] = { 3, 23, 40, 61, 75 };
|
uint8_t _txChLst[5] = { 3, 23, 40, 61, 75 };
|
||||||
uint8_t _txChIdx;
|
uint8_t _txChIdx = 0;
|
||||||
|
|
||||||
volatile bool _packetReceived;
|
volatile bool _packetReceived = false;
|
||||||
|
|
||||||
CircularBuffer<fragment_t, FRAGMENT_BUFFER_SIZE> _rxBuffer;
|
CircularBuffer<fragment_t, FRAGMENT_BUFFER_SIZE> _rxBuffer;
|
||||||
TimeoutHelper _rxTimeout;
|
TimeoutHelper _rxTimeout;
|
||||||
@ -89,5 +87,5 @@ private:
|
|||||||
|
|
||||||
bool _busyFlag = false;
|
bool _busyFlag = false;
|
||||||
|
|
||||||
queue<shared_ptr<CommandAbstract>> _commandQueue;
|
std::queue<std::shared_ptr<CommandAbstract>> _commandQueue;
|
||||||
};
|
};
|
||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class AlarmDataCommand : public MultiDataCommand {
|
class AlarmDataCommand : public MultiDataCommand {
|
||||||
public:
|
public:
|
||||||
AlarmDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
explicit AlarmDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
||||||
|
|
||||||
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
||||||
};
|
};
|
||||||
@ -10,7 +10,7 @@ class InverterAbstract;
|
|||||||
|
|
||||||
class CommandAbstract {
|
class CommandAbstract {
|
||||||
public:
|
public:
|
||||||
CommandAbstract(uint64_t target_address = 0, uint64_t router_address = 0);
|
explicit CommandAbstract(uint64_t target_address = 0, uint64_t router_address = 0);
|
||||||
virtual ~CommandAbstract() {};
|
virtual ~CommandAbstract() {};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
@ -4,5 +4,5 @@
|
|||||||
|
|
||||||
class DevControlCommand : public CommandAbstract {
|
class DevControlCommand : public CommandAbstract {
|
||||||
public:
|
public:
|
||||||
DevControlCommand(uint64_t target_address = 0, uint64_t router_address = 0);
|
explicit DevControlCommand(uint64_t target_address = 0, uint64_t router_address = 0);
|
||||||
};
|
};
|
||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class DevInfoAllCommand : public MultiDataCommand {
|
class DevInfoAllCommand : public MultiDataCommand {
|
||||||
public:
|
public:
|
||||||
DevInfoAllCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
explicit DevInfoAllCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
||||||
|
|
||||||
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
||||||
};
|
};
|
||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class DevInfoSampleCommand : public MultiDataCommand {
|
class DevInfoSampleCommand : public MultiDataCommand {
|
||||||
public:
|
public:
|
||||||
DevInfoSampleCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
explicit DevInfoSampleCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
||||||
|
|
||||||
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
||||||
};
|
};
|
||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
class MultiDataCommand : public CommandAbstract {
|
class MultiDataCommand : public CommandAbstract {
|
||||||
public:
|
public:
|
||||||
MultiDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, uint8_t data_type = 0, time_t time = 0);
|
explicit MultiDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, uint8_t data_type = 0, time_t time = 0);
|
||||||
|
|
||||||
void setTime(time_t time);
|
void setTime(time_t time);
|
||||||
time_t getTime();
|
time_t getTime();
|
||||||
|
|||||||
@ -4,5 +4,5 @@
|
|||||||
|
|
||||||
class ParaSetCommand : public CommandAbstract {
|
class ParaSetCommand : public CommandAbstract {
|
||||||
public:
|
public:
|
||||||
ParaSetCommand(uint64_t target_address = 0, uint64_t router_address = 0);
|
explicit ParaSetCommand(uint64_t target_address = 0, uint64_t router_address = 0);
|
||||||
};
|
};
|
||||||
13
lib/Hoymiles/src/commands/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Class hierarchy
|
||||||
|
|
||||||
|
* CommandAbstract
|
||||||
|
* DevControlCommand
|
||||||
|
* MultiDataCommand
|
||||||
|
* AlarmDataCommand
|
||||||
|
* DevInfoAllCommand
|
||||||
|
* DevInfoSampleCommand
|
||||||
|
* RealTimeRunDataCommand
|
||||||
|
* SystemConfigParaCommand
|
||||||
|
* ParaSetCommand
|
||||||
|
* SingleDataCommand
|
||||||
|
* RequestFrameCommand
|
||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class RealTimeRunDataCommand : public MultiDataCommand {
|
class RealTimeRunDataCommand : public MultiDataCommand {
|
||||||
public:
|
public:
|
||||||
RealTimeRunDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
explicit RealTimeRunDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
||||||
|
|
||||||
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
||||||
};
|
};
|
||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class RequestFrameCommand : public SingleDataCommand {
|
class RequestFrameCommand : public SingleDataCommand {
|
||||||
public:
|
public:
|
||||||
RequestFrameCommand(uint64_t target_address = 0, uint64_t router_address = 0, uint8_t frame_no = 0);
|
explicit RequestFrameCommand(uint64_t target_address = 0, uint64_t router_address = 0, uint8_t frame_no = 0);
|
||||||
|
|
||||||
void setFrameNo(uint8_t frame_no);
|
void setFrameNo(uint8_t frame_no);
|
||||||
uint8_t getFrameNo();
|
uint8_t getFrameNo();
|
||||||
|
|||||||
@ -4,5 +4,5 @@
|
|||||||
|
|
||||||
class SingleDataCommand : public CommandAbstract {
|
class SingleDataCommand : public CommandAbstract {
|
||||||
public:
|
public:
|
||||||
SingleDataCommand(uint64_t target_address = 0, uint64_t router_address = 0);
|
explicit SingleDataCommand(uint64_t target_address = 0, uint64_t router_address = 0);
|
||||||
};
|
};
|
||||||
28
lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#include "SystemConfigParaCommand.h"
|
||||||
|
#include "inverters/InverterAbstract.h"
|
||||||
|
|
||||||
|
SystemConfigParaCommand::SystemConfigParaCommand(uint64_t target_address, uint64_t router_address, time_t time)
|
||||||
|
: MultiDataCommand(target_address, router_address)
|
||||||
|
{
|
||||||
|
setTime(time);
|
||||||
|
setDataType(0x05);
|
||||||
|
setTimeout(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SystemConfigParaCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id)
|
||||||
|
{
|
||||||
|
// Check CRC of whole payload
|
||||||
|
if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move all fragments into target buffer
|
||||||
|
uint8_t offs = 0;
|
||||||
|
inverter->SystemConfigPara()->clearBuffer();
|
||||||
|
for (uint8_t i = 0; i < max_fragment_id; i++) {
|
||||||
|
inverter->SystemConfigPara()->appendFragment(offs, fragment[i].fragment, fragment[i].len);
|
||||||
|
offs += (fragment[i].len);
|
||||||
|
}
|
||||||
|
inverter->SystemConfigPara()->setLastUpdate(millis());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
10
lib/Hoymiles/src/commands/SystemConfigParaCommand.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MultiDataCommand.h"
|
||||||
|
|
||||||
|
class SystemConfigParaCommand : public MultiDataCommand {
|
||||||
|
public:
|
||||||
|
explicit SystemConfigParaCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);
|
||||||
|
|
||||||
|
virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
|
||||||
|
};
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#include "crc.h"
|
#include "crc.h"
|
||||||
|
|
||||||
uint8_t crc8(uint8_t buf[], uint8_t len)
|
uint8_t crc8(const uint8_t buf[], uint8_t len)
|
||||||
{
|
{
|
||||||
uint8_t crc = CRC8_INIT;
|
uint8_t crc = CRC8_INIT;
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
for (uint8_t i = 0; i < len; i++) {
|
||||||
@ -12,7 +12,7 @@ uint8_t crc8(uint8_t buf[], uint8_t len)
|
|||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start)
|
uint16_t crc16(const uint8_t buf[], uint8_t len, uint16_t start)
|
||||||
{
|
{
|
||||||
uint16_t crc = start;
|
uint16_t crc = start;
|
||||||
uint8_t shift = 0;
|
uint8_t shift = 0;
|
||||||
@ -29,7 +29,7 @@ uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start)
|
|||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t crc16nrf24(uint8_t buf[], uint16_t lenBits, uint16_t startBit, uint16_t crcIn)
|
uint16_t crc16nrf24(const uint8_t buf[], uint16_t lenBits, uint16_t startBit, uint16_t crcIn)
|
||||||
{
|
{
|
||||||
uint16_t crc = crcIn;
|
uint16_t crc = crcIn;
|
||||||
uint8_t idx, val = buf[(startBit >> 3)];
|
uint8_t idx, val = buf[(startBit >> 3)];
|
||||||
|
|||||||
@ -8,6 +8,6 @@
|
|||||||
#define CRC16_MODBUS_POLYNOM 0xA001
|
#define CRC16_MODBUS_POLYNOM 0xA001
|
||||||
#define CRC16_NRF24_POLYNOM 0x1021
|
#define CRC16_NRF24_POLYNOM 0x1021
|
||||||
|
|
||||||
uint8_t crc8(uint8_t buf[], uint8_t len);
|
uint8_t crc8(const uint8_t buf[], uint8_t len);
|
||||||
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff);
|
uint16_t crc16(const uint8_t buf[], uint8_t len, uint16_t start = 0xffff);
|
||||||
uint16_t crc16nrf24(uint8_t buf[], uint16_t lenBits, uint16_t startBit = 0, uint16_t crcIn = 0xffff);
|
uint16_t crc16nrf24(const uint8_t buf[], uint16_t lenBits, uint16_t startBit = 0, uint16_t crcIn = 0xffff);
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class HM_1CH : public HM_Abstract {
|
class HM_1CH : public HM_Abstract {
|
||||||
public:
|
public:
|
||||||
HM_1CH(uint64_t serial);
|
explicit HM_1CH(uint64_t serial);
|
||||||
static bool isValidSerial(uint64_t serial);
|
static bool isValidSerial(uint64_t serial);
|
||||||
String typeName();
|
String typeName();
|
||||||
const byteAssign_t* getByteAssignment();
|
const byteAssign_t* getByteAssignment();
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class HM_2CH : public HM_Abstract {
|
class HM_2CH : public HM_Abstract {
|
||||||
public:
|
public:
|
||||||
HM_2CH(uint64_t serial);
|
explicit HM_2CH(uint64_t serial);
|
||||||
static bool isValidSerial(uint64_t serial);
|
static bool isValidSerial(uint64_t serial);
|
||||||
String typeName();
|
String typeName();
|
||||||
const byteAssign_t* getByteAssignment();
|
const byteAssign_t* getByteAssignment();
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
class HM_4CH : public HM_Abstract {
|
class HM_4CH : public HM_Abstract {
|
||||||
public:
|
public:
|
||||||
HM_4CH(uint64_t serial);
|
explicit HM_4CH(uint64_t serial);
|
||||||
static bool isValidSerial(uint64_t serial);
|
static bool isValidSerial(uint64_t serial);
|
||||||
String typeName();
|
String typeName();
|
||||||
const byteAssign_t* getByteAssignment();
|
const byteAssign_t* getByteAssignment();
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include "commands/DevInfoAllCommand.h"
|
#include "commands/DevInfoAllCommand.h"
|
||||||
#include "commands/DevInfoSampleCommand.h"
|
#include "commands/DevInfoSampleCommand.h"
|
||||||
#include "commands/RealTimeRunDataCommand.h"
|
#include "commands/RealTimeRunDataCommand.h"
|
||||||
|
#include "commands/SystemConfigParaCommand.h"
|
||||||
|
|
||||||
HM_Abstract::HM_Abstract(uint64_t serial)
|
HM_Abstract::HM_Abstract(uint64_t serial)
|
||||||
: InverterAbstract(serial) {};
|
: InverterAbstract(serial) {};
|
||||||
@ -70,3 +71,20 @@ bool HM_Abstract::sendDevInfoRequest(HoymilesRadio* radio)
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HM_Abstract::sendSystemConfigParaRequest(HoymilesRadio* radio)
|
||||||
|
{
|
||||||
|
struct tm timeinfo;
|
||||||
|
if (!getLocalTime(&timeinfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t now;
|
||||||
|
time(&now);
|
||||||
|
|
||||||
|
SystemConfigParaCommand* cmd = radio->enqueCommand<SystemConfigParaCommand>();
|
||||||
|
cmd->setTime(now);
|
||||||
|
cmd->setTargetAddress(serial());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -4,10 +4,11 @@
|
|||||||
|
|
||||||
class HM_Abstract : public InverterAbstract {
|
class HM_Abstract : public InverterAbstract {
|
||||||
public:
|
public:
|
||||||
HM_Abstract(uint64_t serial);
|
explicit HM_Abstract(uint64_t serial);
|
||||||
bool sendStatsRequest(HoymilesRadio* radio);
|
bool sendStatsRequest(HoymilesRadio* radio);
|
||||||
bool sendAlarmLogRequest(HoymilesRadio* radio);
|
bool sendAlarmLogRequest(HoymilesRadio* radio);
|
||||||
bool sendDevInfoRequest(HoymilesRadio* radio);
|
bool sendDevInfoRequest(HoymilesRadio* radio);
|
||||||
|
bool sendSystemConfigParaRequest(HoymilesRadio* radio);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t _lastAlarmLogCnt = 0;
|
uint8_t _lastAlarmLogCnt = 0;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ InverterAbstract::InverterAbstract(uint64_t serial)
|
|||||||
_alarmLogParser.reset(new AlarmLogParser());
|
_alarmLogParser.reset(new AlarmLogParser());
|
||||||
_devInfoParser.reset(new DevInfoParser());
|
_devInfoParser.reset(new DevInfoParser());
|
||||||
_statisticsParser.reset(new StatisticsParser());
|
_statisticsParser.reset(new StatisticsParser());
|
||||||
|
_systemConfigParaParser.reset(new SystemConfigParaParser());
|
||||||
}
|
}
|
||||||
|
|
||||||
void InverterAbstract::init()
|
void InverterAbstract::init()
|
||||||
@ -54,6 +55,11 @@ StatisticsParser* InverterAbstract::Statistics()
|
|||||||
return _statisticsParser.get();
|
return _statisticsParser.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SystemConfigParaParser* InverterAbstract::SystemConfigPara()
|
||||||
|
{
|
||||||
|
return _systemConfigParaParser.get();
|
||||||
|
}
|
||||||
|
|
||||||
void InverterAbstract::clearRxFragmentBuffer()
|
void InverterAbstract::clearRxFragmentBuffer()
|
||||||
{
|
{
|
||||||
memset(_rxFragmentBuffer, 0, MAX_RF_FRAGMENT_COUNT * sizeof(fragment_t));
|
memset(_rxFragmentBuffer, 0, MAX_RF_FRAGMENT_COUNT * sizeof(fragment_t));
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../parser/AlarmLogParser.h"
|
#include "../parser/AlarmLogParser.h"
|
||||||
#include "../parser/StatisticsParser.h"
|
|
||||||
#include "../parser/DevInfoParser.h"
|
#include "../parser/DevInfoParser.h"
|
||||||
|
#include "../parser/StatisticsParser.h"
|
||||||
|
#include "../parser/SystemConfigParaParser.h"
|
||||||
#include "HoymilesRadio.h"
|
#include "HoymilesRadio.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
@ -24,7 +25,7 @@ class CommandAbstract;
|
|||||||
|
|
||||||
class InverterAbstract {
|
class InverterAbstract {
|
||||||
public:
|
public:
|
||||||
InverterAbstract(uint64_t serial);
|
explicit InverterAbstract(uint64_t serial);
|
||||||
void init();
|
void init();
|
||||||
uint64_t serial();
|
uint64_t serial();
|
||||||
void setName(const char* name);
|
void setName(const char* name);
|
||||||
@ -40,14 +41,16 @@ public:
|
|||||||
virtual bool sendStatsRequest(HoymilesRadio* radio) = 0;
|
virtual bool sendStatsRequest(HoymilesRadio* radio) = 0;
|
||||||
virtual bool sendAlarmLogRequest(HoymilesRadio* radio) = 0;
|
virtual bool sendAlarmLogRequest(HoymilesRadio* radio) = 0;
|
||||||
virtual bool sendDevInfoRequest(HoymilesRadio* radio) = 0;
|
virtual bool sendDevInfoRequest(HoymilesRadio* radio) = 0;
|
||||||
|
virtual bool sendSystemConfigParaRequest(HoymilesRadio* radio) = 0;
|
||||||
|
|
||||||
AlarmLogParser* EventLog();
|
AlarmLogParser* EventLog();
|
||||||
DevInfoParser* DevInfo();
|
DevInfoParser* DevInfo();
|
||||||
StatisticsParser* Statistics();
|
StatisticsParser* Statistics();
|
||||||
|
SystemConfigParaParser* SystemConfigPara();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
serial_u _serial;
|
serial_u _serial;
|
||||||
char _name[MAX_NAME_LENGTH];
|
char _name[MAX_NAME_LENGTH] = "";
|
||||||
fragment_t _rxFragmentBuffer[MAX_RF_FRAGMENT_COUNT];
|
fragment_t _rxFragmentBuffer[MAX_RF_FRAGMENT_COUNT];
|
||||||
uint8_t _rxFragmentMaxPacketId = 0;
|
uint8_t _rxFragmentMaxPacketId = 0;
|
||||||
uint8_t _rxFragmentLastPacketId = 0;
|
uint8_t _rxFragmentLastPacketId = 0;
|
||||||
@ -56,4 +59,5 @@ private:
|
|||||||
std::unique_ptr<AlarmLogParser> _alarmLogParser;
|
std::unique_ptr<AlarmLogParser> _alarmLogParser;
|
||||||
std::unique_ptr<DevInfoParser> _devInfoParser;
|
std::unique_ptr<DevInfoParser> _devInfoParser;
|
||||||
std::unique_ptr<StatisticsParser> _statisticsParser;
|
std::unique_ptr<StatisticsParser> _statisticsParser;
|
||||||
|
std::unique_ptr<SystemConfigParaParser> _systemConfigParaParser;
|
||||||
};
|
};
|
||||||
@ -272,5 +272,5 @@ int AlarmLogParser::getTimezoneOffset()
|
|||||||
ptm->tm_isdst = -1;
|
ptm->tm_isdst = -1;
|
||||||
gmt = mktime(ptm);
|
gmt = mktime(ptm);
|
||||||
|
|
||||||
return (int)difftime(rawtime, gmt);
|
return static_cast<int>(difftime(rawtime, gmt));
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ uint16_t DevInfoParser::getHwVersion()
|
|||||||
/* struct tm to seconds since Unix epoch */
|
/* struct tm to seconds since Unix epoch */
|
||||||
time_t DevInfoParser::timegm(struct tm* t)
|
time_t DevInfoParser::timegm(struct tm* t)
|
||||||
{
|
{
|
||||||
register long year;
|
register uint32_t year;
|
||||||
register time_t result;
|
register time_t result;
|
||||||
#define MONTHSPERYEAR 12 /* months per calendar year */
|
#define MONTHSPERYEAR 12 /* months per calendar year */
|
||||||
static const int cumdays[MONTHSPERYEAR] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
|
static const int cumdays[MONTHSPERYEAR] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
#include "Parser.h"
|
#include "Parser.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
@ -30,9 +31,9 @@ private:
|
|||||||
uint32_t _lastUpdateAll = 0;
|
uint32_t _lastUpdateAll = 0;
|
||||||
uint32_t _lastUpdateSample = 0;
|
uint32_t _lastUpdateSample = 0;
|
||||||
|
|
||||||
uint8_t _payloadDevInfoAll[DEV_INFO_SIZE];
|
uint8_t _payloadDevInfoAll[DEV_INFO_SIZE] = {};
|
||||||
uint8_t _devInfoAllLength;
|
uint8_t _devInfoAllLength = 0;
|
||||||
|
|
||||||
uint8_t _payloadDevInfoSample[DEV_INFO_SIZE];
|
uint8_t _payloadDevInfoSample[DEV_INFO_SIZE] = {};
|
||||||
uint8_t _devInfoSampleLength;
|
uint8_t _devInfoSampleLength = 0;
|
||||||
};
|
};
|
||||||
@ -56,7 +56,7 @@ float StatisticsParser::getChannelFieldValue(uint8_t channel, uint8_t fieldId)
|
|||||||
val |= _payloadStatistic[ptr];
|
val |= _payloadStatistic[ptr];
|
||||||
} while (++ptr != end);
|
} while (++ptr != end);
|
||||||
|
|
||||||
return (float)(val) / (float)(div);
|
return static_cast<float>(val) / static_cast<float>(div);
|
||||||
} else {
|
} else {
|
||||||
// Value has to be calculated
|
// Value has to be calculated
|
||||||
return calcFunctions[b[pos].start].func(this, b[pos].num);
|
return calcFunctions[b[pos].start].func(this, b[pos].num);
|
||||||
|
|||||||
@ -114,8 +114,8 @@ public:
|
|||||||
void setChannelMaxPower(uint8_t channel, uint16_t power);
|
void setChannelMaxPower(uint8_t channel, uint16_t power);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE];
|
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {};
|
||||||
uint8_t _statisticLength;
|
uint8_t _statisticLength = 0;
|
||||||
uint16_t _chanMaxPower[CH4];
|
uint16_t _chanMaxPower[CH4];
|
||||||
|
|
||||||
const byteAssign_t* _byteAssignment;
|
const byteAssign_t* _byteAssignment;
|
||||||
|
|||||||
23
lib/Hoymiles/src/parser/SystemConfigParaParser.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include "SystemConfigParaParser.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
void SystemConfigParaParser::clearBuffer()
|
||||||
|
{
|
||||||
|
memset(_payload, 0, SYSTEM_CONFIG_PARA_SIZE);
|
||||||
|
_payloadLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||||
|
{
|
||||||
|
if (offset + len > (SYSTEM_CONFIG_PARA_SIZE)) {
|
||||||
|
Serial.printf("FATAL: (%s, %d) stats packet too large for buffer\n", __FILE__, __LINE__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(&_payload[offset], payload, len);
|
||||||
|
_payloadLength += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SystemConfigParaParser::getLimitPercent()
|
||||||
|
{
|
||||||
|
return ((((uint16_t)_payload[2]) << 8) | _payload[3]) / 10;
|
||||||
|
}
|
||||||
17
lib/Hoymiles/src/parser/SystemConfigParaParser.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Parser.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#define SYSTEM_CONFIG_PARA_SIZE 16
|
||||||
|
|
||||||
|
class SystemConfigParaParser : public Parser {
|
||||||
|
public:
|
||||||
|
void clearBuffer();
|
||||||
|
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
|
||||||
|
|
||||||
|
float getLimitPercent();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t _payload[SYSTEM_CONFIG_PARA_SIZE];
|
||||||
|
uint8_t _payloadLength;
|
||||||
|
};
|
||||||
@ -14,34 +14,38 @@ default_envs = generic
|
|||||||
[env]
|
[env]
|
||||||
framework = arduino
|
framework = arduino
|
||||||
platform = espressif32@>=5
|
platform = espressif32@>=5
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-D=${PIOENV}
|
-D=${PIOENV}
|
||||||
-DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz
|
-DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||||
bblanchon/ArduinoJson @ ^6.19.4
|
bblanchon/ArduinoJson @ ^6.19.4
|
||||||
https://github.com/bertmelis/espMqttClient.git
|
https://github.com/bertmelis/espMqttClient.git#v1.2.3
|
||||||
nrf24/RF24 @ ^1.4.5
|
nrf24/RF24 @ ^1.4.5
|
||||||
extra_scripts = pre:auto_firmware_version.py
|
|
||||||
|
extra_scripts =
|
||||||
|
pre:auto_firmware_version.py
|
||||||
|
|
||||||
board_build.partitions = partitions_custom.csv
|
board_build.partitions = partitions_custom.csv
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
monitor_filters =
|
monitor_filters = time, colorize, log2file, esp32_exception_decoder
|
||||||
time
|
|
||||||
colorize
|
|
||||||
log2file
|
|
||||||
esp32_exception_decoder
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
|
|
||||||
|
|
||||||
[env:generic]
|
[env:generic]
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
monitor_port = COM4
|
monitor_port = COM4
|
||||||
upload_port = COM4
|
upload_port = COM4
|
||||||
|
|
||||||
|
|
||||||
[env:olimex_esp32_poe]
|
[env:olimex_esp32_poe]
|
||||||
|
; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware
|
||||||
|
|
||||||
board = esp32-poe
|
board = esp32-poe
|
||||||
build_flags =
|
build_flags = ${env.build_flags}
|
||||||
${env.build_flags}
|
|
||||||
-DHOYMILES_PIN_MISO=15
|
-DHOYMILES_PIN_MISO=15
|
||||||
-DHOYMILES_PIN_MOSI=2
|
-DHOYMILES_PIN_MOSI=2
|
||||||
-DHOYMILES_PIN_SCLK=14
|
-DHOYMILES_PIN_SCLK=14
|
||||||
@ -49,9 +53,28 @@ build_flags =
|
|||||||
-DHOYMILES_PIN_CE=16
|
-DHOYMILES_PIN_CE=16
|
||||||
-DHOYMILES_PIN_CS=5
|
-DHOYMILES_PIN_CS=5
|
||||||
-DOPENDTU_ETHERNET
|
-DOPENDTU_ETHERNET
|
||||||
|
|
||||||
monitor_port = COM3
|
monitor_port = COM3
|
||||||
upload_port = COM3
|
upload_port = COM3
|
||||||
|
|
||||||
|
|
||||||
|
[env:olimex_esp32_evb]
|
||||||
|
; https://www.olimex.com/Products/IoT/ESP32/ESP32-EVB/open-source-hardware
|
||||||
|
|
||||||
|
board = esp32-evb
|
||||||
|
build_flags = ${env.build_flags}
|
||||||
|
-DHOYMILES_PIN_MISO=15
|
||||||
|
-DHOYMILES_PIN_MOSI=2
|
||||||
|
-DHOYMILES_PIN_SCLK=14
|
||||||
|
-DHOYMILES_PIN_IRQ=13
|
||||||
|
-DHOYMILES_PIN_CE=16
|
||||||
|
-DHOYMILES_PIN_CS=17
|
||||||
|
-DOPENDTU_ETHERNET
|
||||||
|
|
||||||
|
monitor_port = /dev/tty.usbserial-1450
|
||||||
|
upload_port = /dev/tty.usbserial-1450
|
||||||
|
|
||||||
|
|
||||||
[env:d1 mini esp32]
|
[env:d1 mini esp32]
|
||||||
board = wemos_d1_mini32
|
board = wemos_d1_mini32
|
||||||
build_flags =
|
build_flags =
|
||||||
|
|||||||
@ -70,7 +70,7 @@ bool ConfigurationClass::write()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
config.Cfg_SaveCount++;
|
config.Cfg_SaveCount++;
|
||||||
uint8_t* bytes = (uint8_t*)&config;
|
uint8_t* bytes = reinterpret_cast<uint8_t*>(&config);
|
||||||
for (unsigned int i = 0; i < sizeof(CONFIG_T); i++) {
|
for (unsigned int i = 0; i < sizeof(CONFIG_T); i++) {
|
||||||
f.write(bytes[i]);
|
f.write(bytes[i]);
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ bool ConfigurationClass::read()
|
|||||||
if (!f) {
|
if (!f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint8_t* bytes = (uint8_t*)&config;
|
uint8_t* bytes = reinterpret_cast<uint8_t*>(&config);
|
||||||
for (unsigned int i = 0; i < sizeof(CONFIG_T); i++) {
|
for (unsigned int i = 0; i < sizeof(CONFIG_T); i++) {
|
||||||
bytes[i] = f.read();
|
bytes[i] = f.read();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@ void MqttHassPublishingClass::publishConfig()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
// Loop all inverters
|
// Loop all inverters
|
||||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||||
@ -74,7 +74,7 @@ void MqttHassPublishingClass::publishField(std::shared_ptr<InverterAbstract> inv
|
|||||||
}
|
}
|
||||||
|
|
||||||
char serial[sizeof(uint64_t) * 8 + 1];
|
char serial[sizeof(uint64_t) * 8 + 1];
|
||||||
sprintf(serial, "%0lx%08lx",
|
snprintf(serial, sizeof(serial), "%0lx%08lx",
|
||||||
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
||||||
|
|
||||||
|
|||||||
@ -19,18 +19,21 @@ void MqttPublishingClass::loop()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
|
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
|
||||||
MqttSettings.publish("dtu/uptime", String(millis() / 1000));
|
MqttSettings.publish("dtu/uptime", String(millis() / 1000));
|
||||||
MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString());
|
MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString());
|
||||||
|
if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
|
||||||
|
MqttSettings.publish("dtu/rssi", String(WiFi.RSSI()));
|
||||||
|
}
|
||||||
|
|
||||||
// Loop all inverters
|
// Loop all inverters
|
||||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||||
auto inv = Hoymiles.getInverterByPos(i);
|
auto inv = Hoymiles.getInverterByPos(i);
|
||||||
|
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
sprintf(buffer, "%0lx%08lx",
|
snprintf(buffer, sizeof(buffer), "%0lx%08lx",
|
||||||
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
||||||
String subtopic = String(buffer);
|
String subtopic = String(buffer);
|
||||||
@ -58,6 +61,11 @@ void MqttPublishingClass::loop()
|
|||||||
MqttSettings.publish(subtopic + "/device/hwversion", String(inv->DevInfo()->getHwVersion()));
|
MqttSettings.publish(subtopic + "/device/hwversion", String(inv->DevInfo()->getHwVersion()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inv->SystemConfigPara()->getLastUpdate() > 0) {
|
||||||
|
// Limit
|
||||||
|
MqttSettings.publish(subtopic + "/settings/limit", String(inv->SystemConfigPara()->getLimitPercent()));
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t lastUpdate = inv->Statistics()->getLastUpdate();
|
uint32_t lastUpdate = inv->Statistics()->getLastUpdate();
|
||||||
if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) {
|
if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) {
|
||||||
_lastPublishStats[i] = lastUpdate;
|
_lastPublishStats[i] = lastUpdate;
|
||||||
@ -94,7 +102,7 @@ String MqttPublishingClass::getTopic(std::shared_ptr<InverterAbstract> inv, uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
sprintf(buffer, "%0lx%08lx",
|
snprintf(buffer, sizeof(buffer), "%0lx%08lx",
|
||||||
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
||||||
String invSerial = String(buffer);
|
String invSerial = String(buffer);
|
||||||
|
|||||||
@ -30,7 +30,7 @@ void MqttSettingsClass::NetworkEvent(network_event event)
|
|||||||
void MqttSettingsClass::onMqttConnect(bool sessionPresent)
|
void MqttSettingsClass::onMqttConnect(bool sessionPresent)
|
||||||
{
|
{
|
||||||
Serial.println(F("Connected to MQTT."));
|
Serial.println(F("Connected to MQTT."));
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Online);
|
publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Online);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,9 +68,9 @@ void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason re
|
|||||||
void MqttSettingsClass::performConnect()
|
void MqttSettingsClass::performConnect()
|
||||||
{
|
{
|
||||||
if (NetworkSettings.isConnected() && Configuration.get().Mqtt_Enabled) {
|
if (NetworkSettings.isConnected() && Configuration.get().Mqtt_Enabled) {
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
Serial.println(F("Connecting to MQTT..."));
|
Serial.println(F("Connecting to MQTT..."));
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
willTopic = getPrefix() + config.Mqtt_LwtTopic;
|
willTopic = getPrefix() + config.Mqtt_LwtTopic;
|
||||||
clientId = NetworkSettings.getApName();
|
clientId = NetworkSettings.getApName();
|
||||||
if (config.Mqtt_Tls) {
|
if (config.Mqtt_Tls) {
|
||||||
@ -95,7 +95,7 @@ void MqttSettingsClass::performConnect()
|
|||||||
|
|
||||||
void MqttSettingsClass::performDisconnect()
|
void MqttSettingsClass::performDisconnect()
|
||||||
{
|
{
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Offline);
|
publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Offline);
|
||||||
mqttClient->disconnect();
|
mqttClient->disconnect();
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ void MqttSettingsClass::publishHass(String subtopic, String payload)
|
|||||||
|
|
||||||
void MqttSettingsClass::init()
|
void MqttSettingsClass::init()
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
NetworkSettings.onEvent(std::bind(&MqttSettingsClass::NetworkEvent, this, _1));
|
NetworkSettings.onEvent(std::bind(&MqttSettingsClass::NetworkEvent, this, _1));
|
||||||
|
|
||||||
createMqttClientObject();
|
createMqttClientObject();
|
||||||
@ -146,7 +146,7 @@ void MqttSettingsClass::createMqttClientObject()
|
|||||||
{
|
{
|
||||||
if (mqttClient != nullptr)
|
if (mqttClient != nullptr)
|
||||||
delete mqttClient;
|
delete mqttClient;
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
if (config.Mqtt_Tls) {
|
if (config.Mqtt_Tls) {
|
||||||
mqttClient = static_cast<MqttClient*>(new espMqttClientSecure);
|
mqttClient = static_cast<MqttClient*>(new espMqttClientSecure);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ NetworkSettingsClass::NetworkSettingsClass()
|
|||||||
|
|
||||||
void NetworkSettingsClass::init()
|
void NetworkSettingsClass::init()
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1));
|
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1));
|
||||||
setupMode();
|
setupMode();
|
||||||
@ -142,11 +142,7 @@ void NetworkSettingsClass::enableAdminMode()
|
|||||||
|
|
||||||
String NetworkSettingsClass::getApName()
|
String NetworkSettingsClass::getApName()
|
||||||
{
|
{
|
||||||
uint32_t chipId = 0;
|
return String(ACCESS_POINT_NAME + String(getChipId()));
|
||||||
for (int i = 0; i < 17; i += 8) {
|
|
||||||
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
|
||||||
}
|
|
||||||
return String(ACCESS_POINT_NAME + String(chipId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkSettingsClass::loop()
|
void NetworkSettingsClass::loop()
|
||||||
@ -241,32 +237,37 @@ void NetworkSettingsClass::applyConfig()
|
|||||||
void NetworkSettingsClass::setHostname()
|
void NetworkSettingsClass::setHostname()
|
||||||
{
|
{
|
||||||
Serial.print(F("Setting Hostname... "));
|
Serial.print(F("Setting Hostname... "));
|
||||||
if (strcmp(Configuration.get().WiFi_Hostname, "")) {
|
|
||||||
if (_networkMode == network_mode::WiFi) {
|
if (_networkMode == network_mode::WiFi) {
|
||||||
if (WiFi.hostname(Configuration.get().WiFi_Hostname)) {
|
if (WiFi.hostname(getHostname())) {
|
||||||
Serial.println(F("done"));
|
Serial.println(F("done"));
|
||||||
} else {
|
} else {
|
||||||
Serial.println(F("failed"));
|
Serial.println(F("failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evil bad hack to get the hostname set up correctly
|
||||||
|
WiFi.mode(WIFI_MODE_APSTA);
|
||||||
|
WiFi.mode(WIFI_MODE_STA);
|
||||||
|
setupMode();
|
||||||
}
|
}
|
||||||
#ifdef OPENDTU_ETHERNET
|
#ifdef OPENDTU_ETHERNET
|
||||||
else if (_networkMode == network_mode::Ethernet) {
|
else if (_networkMode == network_mode::Ethernet) {
|
||||||
if (ETH.setHostname(Configuration.get().WiFi_Hostname)) {
|
if (ETH.setHostname(getHostname().c_str())) {
|
||||||
Serial.println(F("done"));
|
Serial.println(F("done"));
|
||||||
} else {
|
} else {
|
||||||
Serial.println(F("failed"));
|
Serial.println(F("failed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} else {
|
|
||||||
Serial.println(F("failed (Hostname empty)"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkSettingsClass::setStaticIp()
|
void NetworkSettingsClass::setStaticIp()
|
||||||
{
|
{
|
||||||
if (_networkMode == network_mode::WiFi) {
|
if (_networkMode == network_mode::WiFi) {
|
||||||
if (!Configuration.get().WiFi_Dhcp) {
|
if (Configuration.get().WiFi_Dhcp) {
|
||||||
|
Serial.print(F("Configuring WiFi STA DHCP IP... "));
|
||||||
|
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||||
|
Serial.println(F("done"));
|
||||||
|
} else {
|
||||||
Serial.print(F("Configuring WiFi STA static IP... "));
|
Serial.print(F("Configuring WiFi STA static IP... "));
|
||||||
WiFi.config(
|
WiFi.config(
|
||||||
IPAddress(Configuration.get().WiFi_Ip),
|
IPAddress(Configuration.get().WiFi_Ip),
|
||||||
@ -280,7 +281,9 @@ void NetworkSettingsClass::setStaticIp()
|
|||||||
#ifdef OPENDTU_ETHERNET
|
#ifdef OPENDTU_ETHERNET
|
||||||
else if (_networkMode == network_mode::Ethernet) {
|
else if (_networkMode == network_mode::Ethernet) {
|
||||||
if (Configuration.get().WiFi_Dhcp) {
|
if (Configuration.get().WiFi_Dhcp) {
|
||||||
|
Serial.print(F("Configuring Ethernet DHCP IP... "));
|
||||||
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||||
|
Serial.println(F("done"));
|
||||||
} else {
|
} else {
|
||||||
Serial.print(F("Configuring Ethernet static IP... "));
|
Serial.print(F("Configuring Ethernet static IP... "));
|
||||||
ETH.config(
|
ETH.config(
|
||||||
@ -375,14 +378,43 @@ String NetworkSettingsClass::macAddress()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* NetworkSettingsClass::getHostname()
|
String NetworkSettingsClass::getHostname()
|
||||||
{
|
{
|
||||||
#ifdef OPENDTU_ETHERNET
|
const CONFIG_T& config = Configuration.get();
|
||||||
if (_networkMode == network_mode::Ethernet) {
|
char preparedHostname[WIFI_MAX_HOSTNAME_STRLEN + 1];
|
||||||
return ETH.getHostname();
|
char resultHostname[WIFI_MAX_HOSTNAME_STRLEN + 1];
|
||||||
|
uint8_t pos = 0;
|
||||||
|
|
||||||
|
uint32_t chipId = getChipId();
|
||||||
|
snprintf(preparedHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, config.WiFi_Hostname, chipId);
|
||||||
|
|
||||||
|
const char* pC = preparedHostname;
|
||||||
|
while (*pC && pos < WIFI_MAX_HOSTNAME_STRLEN) { // while !null and not over length
|
||||||
|
if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname
|
||||||
|
resultHostname[pos] = *pC;
|
||||||
|
pos++;
|
||||||
|
} else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') {
|
||||||
|
resultHostname[pos] = '-';
|
||||||
|
pos++;
|
||||||
}
|
}
|
||||||
#endif
|
// else do nothing - no leading hyphens and do not include hyphens for all other characters.
|
||||||
return WiFi.getHostname();
|
pC++;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultHostname[pos] = '\0'; // terminate string
|
||||||
|
|
||||||
|
// last character must not be hyphen
|
||||||
|
while (pos > 0 && resultHostname[pos - 1] == '-') {
|
||||||
|
resultHostname[pos - 1] = '\0';
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback if no other rule applied
|
||||||
|
if (strlen(resultHostname) == 0) {
|
||||||
|
snprintf(resultHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, APP_HOSTNAME, chipId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultHostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetworkSettingsClass::isConnected()
|
bool NetworkSettingsClass::isConnected()
|
||||||
@ -399,4 +431,13 @@ network_mode NetworkSettingsClass::NetworkMode()
|
|||||||
return _networkMode;
|
return _networkMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t NetworkSettingsClass::getChipId()
|
||||||
|
{
|
||||||
|
uint32_t chipId = 0;
|
||||||
|
for (int i = 0; i < 17; i += 8) {
|
||||||
|
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||||
|
}
|
||||||
|
return chipId;
|
||||||
|
}
|
||||||
|
|
||||||
NetworkSettingsClass NetworkSettings;
|
NetworkSettingsClass NetworkSettings;
|
||||||
@ -15,15 +15,15 @@ WebApiClass::WebApiClass()
|
|||||||
|
|
||||||
void WebApiClass::init()
|
void WebApiClass::init()
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
|
||||||
|
|
||||||
_server.addHandler(&_events);
|
_server.addHandler(&_events);
|
||||||
|
|
||||||
|
_webApiConfig.init(&_server);
|
||||||
_webApiDevInfo.init(&_server);
|
_webApiDevInfo.init(&_server);
|
||||||
_webApiDtu.init(&_server);
|
_webApiDtu.init(&_server);
|
||||||
_webApiEventlog.init(&_server);
|
_webApiEventlog.init(&_server);
|
||||||
_webApiFirmware.init(&_server);
|
_webApiFirmware.init(&_server);
|
||||||
_webApiInverter.init(&_server);
|
_webApiInverter.init(&_server);
|
||||||
|
_webApiLimit.init(&_server);
|
||||||
_webApiMqtt.init(&_server);
|
_webApiMqtt.init(&_server);
|
||||||
_webApiNetwork.init(&_server);
|
_webApiNetwork.init(&_server);
|
||||||
_webApiNtp.init(&_server);
|
_webApiNtp.init(&_server);
|
||||||
@ -38,11 +38,13 @@ void WebApiClass::init()
|
|||||||
|
|
||||||
void WebApiClass::loop()
|
void WebApiClass::loop()
|
||||||
{
|
{
|
||||||
|
_webApiConfig.loop();
|
||||||
_webApiDevInfo.loop();
|
_webApiDevInfo.loop();
|
||||||
_webApiDtu.loop();
|
_webApiDtu.loop();
|
||||||
_webApiEventlog.loop();
|
_webApiEventlog.loop();
|
||||||
_webApiFirmware.loop();
|
_webApiFirmware.loop();
|
||||||
_webApiInverter.loop();
|
_webApiInverter.loop();
|
||||||
|
_webApiLimit.loop();
|
||||||
_webApiMqtt.loop();
|
_webApiMqtt.loop();
|
||||||
_webApiNetwork.loop();
|
_webApiNetwork.loop();
|
||||||
_webApiNtp.loop();
|
_webApiNtp.loop();
|
||||||
|
|||||||
125
src/WebApi_config.cpp
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "WebApi_config.h"
|
||||||
|
#include "ArduinoJson.h"
|
||||||
|
#include "AsyncJson.h"
|
||||||
|
#include "Configuration.h"
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
void WebApiConfigClass::init(AsyncWebServer* server)
|
||||||
|
{
|
||||||
|
using std::placeholders::_1;
|
||||||
|
using std::placeholders::_2;
|
||||||
|
using std::placeholders::_3;
|
||||||
|
using std::placeholders::_4;
|
||||||
|
using std::placeholders::_5;
|
||||||
|
using std::placeholders::_6;
|
||||||
|
|
||||||
|
_server = server;
|
||||||
|
|
||||||
|
_server->on("/api/config/get", HTTP_GET, std::bind(&WebApiConfigClass::onConfigGet, this, _1));
|
||||||
|
_server->on("/api/config/delete", HTTP_POST, std::bind(&WebApiConfigClass::onConfigDelete, this, _1));
|
||||||
|
_server->on("/api/config/upload", HTTP_POST,
|
||||||
|
std::bind(&WebApiConfigClass::onConfigUploadFinish, this, _1),
|
||||||
|
std::bind(&WebApiConfigClass::onConfigUpload, this, _1, _2, _3, _4, _5, _6));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiConfigClass::loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiConfigClass::onConfigGet(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
request->send(LittleFS, CONFIG_FILENAME, String(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonObject retMsg = response->getRoot();
|
||||||
|
retMsg[F("type")] = F("warning");
|
||||||
|
|
||||||
|
if (!request->hasParam("data", true)) {
|
||||||
|
retMsg[F("message")] = F("No values found!");
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
|
if (json.length() > 1024) {
|
||||||
|
retMsg[F("message")] = F("Data too large!");
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicJsonDocument root(1024);
|
||||||
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
retMsg[F("message")] = F("Failed to parse data!");
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(root.containsKey("delete"))) {
|
||||||
|
retMsg[F("message")] = F("Values are missing!");
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root[F("delete")].as<bool>() == false) {
|
||||||
|
retMsg[F("message")] = F("Not deleted anything!");
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
retMsg[F("type")] = F("success");
|
||||||
|
retMsg[F("message")] = F("Configuration resettet. Rebooting now...");
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
|
||||||
|
LittleFS.remove(CONFIG_FILENAME);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
// the request handler is triggered after the upload has finished...
|
||||||
|
// create the response, add header, and send response
|
||||||
|
|
||||||
|
AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", "OK");
|
||||||
|
response->addHeader("Connection", "close");
|
||||||
|
response->addHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
request->send(response);
|
||||||
|
yield();
|
||||||
|
delay(1000);
|
||||||
|
yield();
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiConfigClass::onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final)
|
||||||
|
{
|
||||||
|
if (!index) {
|
||||||
|
// open the file on first call and store the file handle in the request object
|
||||||
|
request->_tempFile = LittleFS.open(CONFIG_FILENAME, "w");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len) {
|
||||||
|
// stream the incoming chunk to the opened file
|
||||||
|
request->_tempFile.write(data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (final) {
|
||||||
|
// close the file handle as the upload is now done
|
||||||
|
request->_tempFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
void WebApiDevInfoClass::init(AsyncWebServer* server)
|
void WebApiDevInfoClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
// Inverter Serial is read as HEX
|
// Inverter Serial is read as HEX
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
sprintf(buffer, "%0lx%08lx",
|
snprintf(buffer, sizeof(buffer), "%0lx%08lx",
|
||||||
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
void WebApiDtuClass::init(AsyncWebServer* server)
|
void WebApiDtuClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
@ -26,11 +26,11 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
|
|||||||
{
|
{
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
// DTU Serial is read as HEX
|
// DTU Serial is read as HEX
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
sprintf(buffer, "%0lx%08lx",
|
snprintf(buffer, sizeof(buffer), "%0lx%08lx",
|
||||||
((uint32_t)((config.Dtu_Serial >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((config.Dtu_Serial >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(config.Dtu_Serial & 0xFFFFFFFF)));
|
((uint32_t)(config.Dtu_Serial & 0xFFFFFFFF)));
|
||||||
root[F("dtu_serial")] = buffer;
|
root[F("dtu_serial")] = buffer;
|
||||||
@ -102,9 +102,9 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
char* t;
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
// 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(), &t, 16);
|
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_PollInterval = root[F("dtu_pollinterval")].as<uint32_t>();
|
||||||
config.Dtu_PaLevel = root[F("dtu_palevel")].as<uint8_t>();
|
config.Dtu_PaLevel = root[F("dtu_palevel")].as<uint8_t>();
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
void WebApiEventlogClass::init(AsyncWebServer* server)
|
void WebApiEventlogClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
@ -22,15 +22,21 @@ void WebApiEventlogClass::loop()
|
|||||||
|
|
||||||
void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
|
void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048);
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
uint64_t serial = 0;
|
||||||
auto inv = Hoymiles.getInverterByPos(i);
|
if (request->hasParam("inv")) {
|
||||||
|
String s = request->getParam("inv")->value();
|
||||||
|
serial = strtoll(s.c_str(), NULL, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
|
|
||||||
|
if (inv != nullptr) {
|
||||||
// Inverter Serial is read as HEX
|
// Inverter Serial is read as HEX
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
sprintf(buffer, "%0lx%08lx",
|
snprintf(buffer, sizeof(buffer), "%0lx%08lx",
|
||||||
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,12 @@
|
|||||||
|
|
||||||
void WebApiFirmwareClass::init(AsyncWebServer* server)
|
void WebApiFirmwareClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
using std::placeholders::_2;
|
||||||
|
using std::placeholders::_3;
|
||||||
|
using std::placeholders::_4;
|
||||||
|
using std::placeholders::_5;
|
||||||
|
using std::placeholders::_6;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
void WebApiInverterClass::init(AsyncWebServer* server)
|
void WebApiInverterClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
|||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
JsonArray data = root.createNestedArray(F("inverter"));
|
JsonArray data = root.createNestedArray(F("inverter"));
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
if (config.Inverter[i].Serial > 0) {
|
if (config.Inverter[i].Serial > 0) {
|
||||||
@ -42,7 +42,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
// Inverter Serial is read as HEX
|
// Inverter Serial is read as HEX
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
sprintf(buffer, "%0lx%08lx",
|
snprintf(buffer, sizeof(buffer), "%0lx%08lx",
|
||||||
((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF)));
|
((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF)));
|
||||||
obj[F("serial")] = buffer;
|
obj[F("serial")] = buffer;
|
||||||
@ -126,9 +126,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* t;
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
inverter->Serial = strtoll(root[F("serial")].as<String>().c_str(), &t, 16);
|
inverter->Serial = strtoll(root[F("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[F("name")].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
@ -224,8 +223,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root[F("id")].as<uint8_t>()];
|
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root[F("id")].as<uint8_t>()];
|
||||||
|
|
||||||
char* t;
|
uint64_t new_serial = strtoll(root[F("serial")].as<String>().c_str(), NULL, 16);
|
||||||
uint64_t new_serial = strtoll(root[F("serial")].as<String>().c_str(), &t, 16);
|
|
||||||
uint64_t old_serial = inverter.Serial;
|
uint64_t old_serial = inverter.Serial;
|
||||||
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
|||||||
42
src/WebApi_limit.cpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "WebApi_limit.h"
|
||||||
|
#include "ArduinoJson.h"
|
||||||
|
#include "AsyncJson.h"
|
||||||
|
#include "Hoymiles.h"
|
||||||
|
|
||||||
|
void WebApiLimitClass::init(AsyncWebServer* server)
|
||||||
|
{
|
||||||
|
using std::placeholders::_1;
|
||||||
|
|
||||||
|
_server = server;
|
||||||
|
|
||||||
|
_server->on("/api/limit/status", HTTP_GET, std::bind(&WebApiLimitClass::onLimitStatus, this, _1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiLimitClass::loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||||
|
auto inv = Hoymiles.getInverterByPos(i);
|
||||||
|
|
||||||
|
// Inverter Serial is read as HEX
|
||||||
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
|
snprintf(buffer, sizeof(buffer), "%0lx%08lx",
|
||||||
|
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
||||||
|
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
||||||
|
|
||||||
|
root[buffer]["limit"] = inv->SystemConfigPara()->getLimitPercent();
|
||||||
|
}
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
void WebApiMqttClass::init(AsyncWebServer* server)
|
void WebApiMqttClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request)
|
|||||||
{
|
{
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("mqtt_enabled")] = config.Mqtt_Enabled;
|
root[F("mqtt_enabled")] = config.Mqtt_Enabled;
|
||||||
root[F("mqtt_hostname")] = config.Mqtt_Hostname;
|
root[F("mqtt_hostname")] = config.Mqtt_Hostname;
|
||||||
@ -56,7 +56,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
|
|||||||
{
|
{
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("mqtt_enabled")] = config.Mqtt_Enabled;
|
root[F("mqtt_enabled")] = config.Mqtt_Enabled;
|
||||||
root[F("mqtt_hostname")] = config.Mqtt_Hostname;
|
root[F("mqtt_hostname")] = config.Mqtt_Hostname;
|
||||||
@ -240,21 +240,21 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
config.Mqtt_Enabled = root[F("mqtt_enabled")].as<bool>();
|
config.Mqtt_Enabled = root[F("mqtt_enabled")].as<bool>();
|
||||||
config.Mqtt_Retain = root[F("mqtt_retain")].as<bool>();
|
config.Mqtt_Retain = root[F("mqtt_retain")].as<bool>();
|
||||||
config.Mqtt_Tls = root[F("mqtt_tls")].as<bool>();
|
config.Mqtt_Tls = root[F("mqtt_tls")].as<bool>();
|
||||||
strcpy(config.Mqtt_RootCaCert, root[F("mqtt_root_ca_cert")].as<String>().c_str());
|
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>();
|
config.Mqtt_Port = root[F("mqtt_port")].as<uint>();
|
||||||
strcpy(config.Mqtt_Hostname, root[F("mqtt_hostname")].as<String>().c_str());
|
strlcpy(config.Mqtt_Hostname, root[F("mqtt_hostname")].as<String>().c_str(), sizeof(config.Mqtt_Hostname));
|
||||||
strcpy(config.Mqtt_Username, root[F("mqtt_username")].as<String>().c_str());
|
strlcpy(config.Mqtt_Username, root[F("mqtt_username")].as<String>().c_str(), sizeof(config.Mqtt_Username));
|
||||||
strcpy(config.Mqtt_Password, root[F("mqtt_password")].as<String>().c_str());
|
strlcpy(config.Mqtt_Password, root[F("mqtt_password")].as<String>().c_str(), sizeof(config.Mqtt_Password));
|
||||||
strcpy(config.Mqtt_Topic, root[F("mqtt_topic")].as<String>().c_str());
|
strlcpy(config.Mqtt_Topic, root[F("mqtt_topic")].as<String>().c_str(), sizeof(config.Mqtt_Topic));
|
||||||
strcpy(config.Mqtt_LwtTopic, root[F("mqtt_lwt_topic")].as<String>().c_str());
|
strlcpy(config.Mqtt_LwtTopic, root[F("mqtt_lwt_topic")].as<String>().c_str(), sizeof(config.Mqtt_LwtTopic));
|
||||||
strcpy(config.Mqtt_LwtValue_Online, root[F("mqtt_lwt_online")].as<String>().c_str());
|
strlcpy(config.Mqtt_LwtValue_Online, root[F("mqtt_lwt_online")].as<String>().c_str(), sizeof(config.Mqtt_LwtValue_Online));
|
||||||
strcpy(config.Mqtt_LwtValue_Offline, root[F("mqtt_lwt_offline")].as<String>().c_str());
|
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_PublishInterval = root[F("mqtt_publish_interval")].as<uint32_t>();
|
||||||
config.Mqtt_Hass_Enabled = root[F("mqtt_hass_enabled")].as<bool>();
|
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_Expire = root[F("mqtt_hass_expire")].as<bool>();
|
||||||
config.Mqtt_Hass_Retain = root[F("mqtt_hass_retain")].as<bool>();
|
config.Mqtt_Hass_Retain = root[F("mqtt_hass_retain")].as<bool>();
|
||||||
config.Mqtt_Hass_IndividualPanels = root[F("mqtt_hass_individualpanels")].as<bool>();
|
config.Mqtt_Hass_IndividualPanels = root[F("mqtt_hass_individualpanels")].as<bool>();
|
||||||
strcpy(config.Mqtt_Hass_Topic, root[F("mqtt_hass_topic")].as<String>().c_str());
|
strlcpy(config.Mqtt_Hass_Topic, root[F("mqtt_hass_topic")].as<String>().c_str(), sizeof(config.Mqtt_Hass_Topic));
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
|
|
||||||
retMsg[F("type")] = F("success");
|
retMsg[F("type")] = F("success");
|
||||||
@ -267,18 +267,18 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
MqttHassPublishing.forceUpdate();
|
MqttHassPublishing.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
String WebApiMqttClass::getRootCaCertInfo(char* cert)
|
String WebApiMqttClass::getRootCaCertInfo(const char* cert)
|
||||||
{
|
{
|
||||||
char rootCaCertInfo[1024] = "";
|
char rootCaCertInfo[1024] = "";
|
||||||
|
|
||||||
mbedtls_x509_crt global_cacert;
|
mbedtls_x509_crt global_cacert;
|
||||||
|
|
||||||
strcpy(rootCaCertInfo, "Can't parse root ca");
|
strlcpy(rootCaCertInfo, "Can't parse root ca", sizeof(rootCaCertInfo));
|
||||||
|
|
||||||
mbedtls_x509_crt_init(&global_cacert);
|
mbedtls_x509_crt_init(&global_cacert);
|
||||||
int ret = mbedtls_x509_crt_parse(&global_cacert, const_cast<unsigned char*>((unsigned char*)cert), 1 + strlen(cert));
|
int ret = mbedtls_x509_crt_parse(&global_cacert, const_cast<unsigned char*>((unsigned char*)cert), 1 + strlen(cert));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
sprintf(rootCaCertInfo, "Can't parse root ca: mbedtls_x509_crt_parse returned -0x%x\n\n", -ret);
|
snprintf(rootCaCertInfo, sizeof(rootCaCertInfo), "Can't parse root ca: mbedtls_x509_crt_parse returned -0x%x\n\n", -ret);
|
||||||
mbedtls_x509_crt_free(&global_cacert);
|
mbedtls_x509_crt_free(&global_cacert);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
void WebApiNetworkClass::init(AsyncWebServer* server)
|
void WebApiNetworkClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request)
|
|||||||
root[F("sta_status")] = ((WiFi.getMode() & WIFI_STA) != 0);
|
root[F("sta_status")] = ((WiFi.getMode() & WIFI_STA) != 0);
|
||||||
root[F("sta_ssid")] = WiFi.SSID();
|
root[F("sta_ssid")] = WiFi.SSID();
|
||||||
root[F("sta_rssi")] = WiFi.RSSI();
|
root[F("sta_rssi")] = WiFi.RSSI();
|
||||||
|
root[F("network_hostname")] = NetworkSettings.getHostname();
|
||||||
root[F("network_ip")] = NetworkSettings.localIP().toString();
|
root[F("network_ip")] = NetworkSettings.localIP().toString();
|
||||||
root[F("network_netmask")] = NetworkSettings.subnetMask().toString();
|
root[F("network_netmask")] = NetworkSettings.subnetMask().toString();
|
||||||
root[F("network_gateway")] = NetworkSettings.gatewayIP().toString();
|
root[F("network_gateway")] = NetworkSettings.gatewayIP().toString();
|
||||||
@ -53,7 +54,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
|
|||||||
{
|
{
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("hostname")] = config.WiFi_Hostname;
|
root[F("hostname")] = config.WiFi_Hostname;
|
||||||
root[F("dhcp")] = config.WiFi_Dhcp;
|
root[F("dhcp")] = config.WiFi_Dhcp;
|
||||||
@ -184,9 +185,9 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
|||||||
config.WiFi_Dns2[1] = dns2[1];
|
config.WiFi_Dns2[1] = dns2[1];
|
||||||
config.WiFi_Dns2[2] = dns2[2];
|
config.WiFi_Dns2[2] = dns2[2];
|
||||||
config.WiFi_Dns2[3] = dns2[3];
|
config.WiFi_Dns2[3] = dns2[3];
|
||||||
strcpy(config.WiFi_Ssid, root[F("ssid")].as<String>().c_str());
|
strlcpy(config.WiFi_Ssid, root[F("ssid")].as<String>().c_str(), sizeof(config.WiFi_Ssid));
|
||||||
strcpy(config.WiFi_Password, root[F("password")].as<String>().c_str());
|
strlcpy(config.WiFi_Password, root[F("password")].as<String>().c_str(), sizeof(config.WiFi_Password));
|
||||||
strcpy(config.WiFi_Hostname, root[F("hostname")].as<String>().c_str());
|
strlcpy(config.WiFi_Hostname, root[F("hostname")].as<String>().c_str(), sizeof(config.WiFi_Hostname));
|
||||||
if (root[F("dhcp")].as<bool>()) {
|
if (root[F("dhcp")].as<bool>()) {
|
||||||
config.WiFi_Dhcp = true;
|
config.WiFi_Dhcp = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
void WebApiNtpClass::init(AsyncWebServer* server)
|
void WebApiNtpClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request)
|
|||||||
{
|
{
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("ntp_server")] = config.Ntp_Server;
|
root[F("ntp_server")] = config.Ntp_Server;
|
||||||
root[F("ntp_timezone")] = config.Ntp_Timezone;
|
root[F("ntp_timezone")] = config.Ntp_Timezone;
|
||||||
@ -52,7 +52,7 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request)
|
|||||||
{
|
{
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("ntp_server")] = config.Ntp_Server;
|
root[F("ntp_server")] = config.Ntp_Server;
|
||||||
root[F("ntp_timezone")] = config.Ntp_Timezone;
|
root[F("ntp_timezone")] = config.Ntp_Timezone;
|
||||||
@ -123,9 +123,9 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
strcpy(config.Ntp_Server, root[F("ntp_server")].as<String>().c_str());
|
strlcpy(config.Ntp_Server, root[F("ntp_server")].as<String>().c_str(), sizeof(config.Ntp_Server));
|
||||||
strcpy(config.Ntp_Timezone, root[F("ntp_timezone")].as<String>().c_str());
|
strlcpy(config.Ntp_Timezone, root[F("ntp_timezone")].as<String>().c_str(), sizeof(config.Ntp_Timezone));
|
||||||
strcpy(config.Ntp_TimezoneDescr, root[F("ntp_timezone_descr")].as<String>().c_str());
|
strlcpy(config.Ntp_TimezoneDescr, root[F("ntp_timezone_descr")].as<String>().c_str(), sizeof(config.Ntp_TimezoneDescr));
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
|
|
||||||
retMsg[F("type")] = F("success");
|
retMsg[F("type")] = F("success");
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
void WebApiSysstatusClass::init(AsyncWebServer* server)
|
void WebApiSysstatusClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
|
|||||||
root[F("cfgsavecount")] = Configuration.get().Cfg_SaveCount;
|
root[F("cfgsavecount")] = Configuration.get().Cfg_SaveCount;
|
||||||
|
|
||||||
char version[16];
|
char version[16];
|
||||||
sprintf(version, "%d.%d.%d", CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff);
|
snprintf(version, sizeof(version), "%d.%d.%d", CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff);
|
||||||
root[F("firmware_version")] = version;
|
root[F("firmware_version")] = version;
|
||||||
root[F("git_hash")] = AUTO_GIT_HASH;
|
root[F("git_hash")] = AUTO_GIT_HASH;
|
||||||
|
|
||||||
|
|||||||
@ -16,8 +16,6 @@ extern const uint8_t file_app_js_end[] asm("_binary_webapp_dist_js_app_js_gz_end
|
|||||||
|
|
||||||
void WebApiWebappClass::init(AsyncWebServer* server)
|
void WebApiWebappClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
_server->on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
_server->on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
|||||||
@ -13,7 +13,12 @@ WebApiWsLiveClass::WebApiWsLiveClass()
|
|||||||
|
|
||||||
void WebApiWsLiveClass::init(AsyncWebServer* server)
|
void WebApiWsLiveClass::init(AsyncWebServer* server)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using std::placeholders::_1;
|
||||||
|
using std::placeholders::_2;
|
||||||
|
using std::placeholders::_3;
|
||||||
|
using std::placeholders::_4;
|
||||||
|
using std::placeholders::_5;
|
||||||
|
using std::placeholders::_6;
|
||||||
|
|
||||||
_server = server;
|
_server = server;
|
||||||
_server->on("/api/livedata/status", HTTP_GET, std::bind(&WebApiWsLiveClass::onLivedataStatus, this, _1));
|
_server->on("/api/livedata/status", HTTP_GET, std::bind(&WebApiWsLiveClass::onLivedataStatus, this, _1));
|
||||||
@ -56,10 +61,9 @@ void WebApiWsLiveClass::loop()
|
|||||||
JsonVariant var = root;
|
JsonVariant var = root;
|
||||||
generateJsonResponse(var);
|
generateJsonResponse(var);
|
||||||
|
|
||||||
size_t len = measureJson(root);
|
String buffer;
|
||||||
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len); // creates a buffer (len + 1) for you.
|
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
serializeJson(root, (char*)buffer->get(), len + 1);
|
serializeJson(root, buffer);
|
||||||
_ws.textAll(buffer);
|
_ws.textAll(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +78,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
auto inv = Hoymiles.getInverterByPos(i);
|
auto inv = Hoymiles.getInverterByPos(i);
|
||||||
|
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
sprintf(buffer, "%0lx%08lx",
|
snprintf(buffer, sizeof(buffer), "%0lx%08lx",
|
||||||
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
((uint32_t)(inv->serial() & 0xFFFFFFFF)));
|
||||||
|
|
||||||
@ -135,11 +139,11 @@ void WebApiWsLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketC
|
|||||||
{
|
{
|
||||||
if (type == WS_EVT_CONNECT) {
|
if (type == WS_EVT_CONNECT) {
|
||||||
char str[64];
|
char str[64];
|
||||||
sprintf(str, "Websocket: [%s][%u] connect", server->url(), client->id());
|
snprintf(str, sizeof(str), "Websocket: [%s][%u] connect", server->url(), client->id());
|
||||||
Serial.println(str);
|
Serial.println(str);
|
||||||
} else if (type == WS_EVT_DISCONNECT) {
|
} else if (type == WS_EVT_DISCONNECT) {
|
||||||
char str[64];
|
char str[64];
|
||||||
sprintf(str, "Websocket: [%s][%u] disconnect", server->url(), client->id());
|
snprintf(str, sizeof(str), "Websocket: [%s][%u] disconnect", server->url(), client->id());
|
||||||
Serial.println(str);
|
Serial.println(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,7 +81,7 @@ void setup()
|
|||||||
|
|
||||||
// Initialize inverter communication
|
// Initialize inverter communication
|
||||||
Serial.print(F("Initialize Hoymiles interface... "));
|
Serial.print(F("Initialize Hoymiles interface... "));
|
||||||
CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
Hoymiles.init();
|
Hoymiles.init();
|
||||||
|
|
||||||
Serial.println(F(" Setting radio PA level... "));
|
Serial.println(F(" Setting radio PA level... "));
|
||||||
|
|||||||
@ -9,30 +9,30 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.6",
|
"@popperjs/core": "^2.11.6",
|
||||||
"bootstrap": "^5.2.0",
|
"bootstrap": "^5.2.1",
|
||||||
"bootstrap-icons-vue": "^1.8.1",
|
"bootstrap-icons-vue": "^1.8.1",
|
||||||
"core-js": "^3.25.0",
|
"core-js": "^3.25.1",
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2",
|
||||||
"vue": "^3.2.38",
|
"vue": "^3.2.39",
|
||||||
"vue-class-component": "^8.0.0-0",
|
"vue-class-component": "^8.0.0-0",
|
||||||
"vue-router": "^4.1.5"
|
"vue-router": "^4.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.18.13",
|
"@babel/core": "^7.19.0",
|
||||||
"@babel/eslint-parser": "^7.18.9",
|
"@babel/eslint-parser": "^7.18.9",
|
||||||
"@types/bootstrap": "^5.2.2",
|
"@types/bootstrap": "^5.2.4",
|
||||||
"@types/node": "^18.7.15",
|
"@types/node": "^18.7.16",
|
||||||
"@types/spark-md5": "^3.0.2",
|
"@types/spark-md5": "^3.0.2",
|
||||||
"@typescript-eslint/parser": "^5.36.1",
|
"@typescript-eslint/parser": "^5.36.2",
|
||||||
"@vue/cli-plugin-babel": "~5.0.8",
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||||
"@vue/cli-plugin-router": "^5.0.6",
|
"@vue/cli-plugin-router": "^5.0.6",
|
||||||
"@vue/cli-plugin-typescript": "^5.0.8",
|
"@vue/cli-plugin-typescript": "^5.0.8",
|
||||||
"@vue/cli-service": "~5.0.8",
|
"@vue/cli-service": "~5.0.8",
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^11.0.1",
|
||||||
"eslint": "^8.23.0",
|
"eslint": "^8.23.1",
|
||||||
"eslint-plugin-vue": "^9.4.0",
|
"eslint-plugin-vue": "^9.4.0",
|
||||||
"typescript": "^4.8.2",
|
"typescript": "^4.8.3",
|
||||||
"vue-cli-plugin-compression": "~2.0.0"
|
"vue-cli-plugin-compression": "~2.0.0"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|||||||
@ -1,10 +1,101 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-xxl" role="main">
|
<div class="container-xxl" role="main">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>About</h1>
|
<h1>About OpenDTU</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="accordion" id="accordionExample">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingOne">
|
||||||
|
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||||
|
<span class="badge bg-secondary">
|
||||||
|
<BIconInfoCircle class="fs-4" />
|
||||||
|
</span> Project Origin
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne"
|
||||||
|
data-bs-parent="#accordionExample">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p class="fw-normal">
|
||||||
This project was started from
|
This project was started from
|
||||||
<a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion.
|
<a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion.
|
||||||
(Mikrocontroller.net)</a>
|
(Mikrocontroller.net)</a>
|
||||||
|
</p>
|
||||||
|
<p class="fw-normal">
|
||||||
|
The Hoymiles protocol was decrypted through the voluntary efforts of many participants.
|
||||||
|
OpenDTU,
|
||||||
|
among others, was developed based on this work. The project is licensed under an Open Source
|
||||||
|
License (<a href="https://www.gnu.de/documents/gpl-2.0.de.html" target="_blank">GNU General
|
||||||
|
Public License version 2</a>).
|
||||||
|
</p>
|
||||||
|
<p class="fw-normal">
|
||||||
|
The software was developed to the best of our knowledge and belief. Nevertheless, no
|
||||||
|
liability
|
||||||
|
can be accepted for a malfunction or guarantee loss of the inverter.
|
||||||
|
</p>
|
||||||
|
<p class="fw-normal">
|
||||||
|
OpenDTU is freely available. If you paid money for the software, you probably got ripped
|
||||||
|
off.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingTwo">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||||
|
<span class="badge bg-secondary">
|
||||||
|
<BIconActivity class="fs-4" />
|
||||||
|
</span> News & Updates
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo"
|
||||||
|
data-bs-parent="#accordionExample">
|
||||||
|
<div class="accordion-body">
|
||||||
|
New updates can be found on Github: <a href="https://github.com/tbnobody/OpenDTU"
|
||||||
|
target="_blank">https://github.com/tbnobody/OpenDTU</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingThree">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
||||||
|
<span class="badge bg-secondary">
|
||||||
|
<BIconBug class="fs-4" />
|
||||||
|
</span> Error Reporting
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree"
|
||||||
|
data-bs-parent="#accordionExample">
|
||||||
|
<div class="accordion-body">
|
||||||
|
Please report issues using the feature provided by <a
|
||||||
|
href="https://github.com/tbnobody/OpenDTU/issues" target="_blank">Github</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingFour">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
|
||||||
|
<span class="badge bg-secondary">
|
||||||
|
<BIconChat class="fs-4" />
|
||||||
|
</span> Discussion
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour"
|
||||||
|
data-bs-parent="#accordionExample">
|
||||||
|
<div class="accordion-body">
|
||||||
|
Discuss with us on <a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a> or <a
|
||||||
|
href="https://github.com/tbnobody/OpenDTU/discussions" target="_blank">Github</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
219
webapp/src/components/ConfigAdminView.vue
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container-xxl" role="main">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Config Management</h1>
|
||||||
|
</div>
|
||||||
|
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
|
||||||
|
{{ alertMessage }}
|
||||||
|
</BootstrapAlert>
|
||||||
|
|
||||||
|
<div class="text-center" v-if="loading">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="!loading">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header text-white bg-primary">Backup: Configuration File Backup</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
Backup the configuration file
|
||||||
|
<button class="btn btn-primary" @click="downloadConfig">Backup
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-5">
|
||||||
|
<div class="card-header text-white bg-primary">Restore: Restore the Configuration File</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
|
||||||
|
<div v-if="!uploading && UploadError != ''">
|
||||||
|
<p class="h1 mb-2">
|
||||||
|
<BIconExclamationCircleFill />
|
||||||
|
</p>
|
||||||
|
<span style="vertical-align: middle" class="ml-2">
|
||||||
|
{{ UploadError }}
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<button class="btn btn-light" @click="clear">
|
||||||
|
<BIconArrowLeft /> Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!uploading && UploadSuccess">
|
||||||
|
<span class="h1 mb-2">
|
||||||
|
<BIconCheckCircle />
|
||||||
|
</span>
|
||||||
|
<span> Upload Success </span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<button class="btn btn-primary" @click="clear">
|
||||||
|
<BIconArrowLeft /> Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!uploading">
|
||||||
|
<div class="form-group pt-2 mt-3">
|
||||||
|
<input class="form-control" type="file" ref="file" accept=".bin" @change="uploadConfig" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="uploading">
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar" role="progressbar" :style="{ width: progress + '%' }"
|
||||||
|
v-bind:aria-valuenow="progress" aria-valuemin="0" aria-valuemax="100">
|
||||||
|
{{ progress }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-danger mt-3" role="alert">
|
||||||
|
<b>Note:</b> This operation replaces the configuration file with the restored configuration and
|
||||||
|
restarts OpenDTU to apply all settings.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-5">
|
||||||
|
<div class="card-header text-white bg-primary">Initialize: Perform Factory Reset</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
|
||||||
|
<button class="btn btn-danger" @click="onFactoryResetModal">Restore Factory-Default Settings
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="alert alert-danger mt-3" role="alert">
|
||||||
|
<b>Note:</b> Click Restore Factory-Default Settings to restore and initialize the
|
||||||
|
factory-default settings and reboot.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="modal" id="factoryReset" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Factory Reset</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
Are you sure you want to delete the current configuration and reset all settings to their
|
||||||
|
factory defaults?
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" @click="onFactoryResetCancel"
|
||||||
|
data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-danger" @click="onFactoryResetPerform">Factory
|
||||||
|
Reset!</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import * as bootstrap from 'bootstrap';
|
||||||
|
import BootstrapAlert from "@/components/partials/BootstrapAlert.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
BootstrapAlert,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modalFactoryReset: {} as bootstrap.Modal,
|
||||||
|
alertMessage: "",
|
||||||
|
alertType: "info",
|
||||||
|
showAlert: false,
|
||||||
|
loading: true,
|
||||||
|
uploading: false,
|
||||||
|
progress: 0,
|
||||||
|
UploadError: "",
|
||||||
|
UploadSuccess: false,
|
||||||
|
file: {} as Blob,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.modalFactoryReset = new bootstrap.Modal('#factoryReset');
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onFactoryResetModal() {
|
||||||
|
this.modalFactoryReset.show();
|
||||||
|
},
|
||||||
|
onFactoryResetCancel() {
|
||||||
|
this.modalFactoryReset.hide();
|
||||||
|
},
|
||||||
|
onFactoryResetPerform() {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("data", JSON.stringify({ delete: true }));
|
||||||
|
|
||||||
|
fetch("/api/config/delete", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
if (response.status != 200) {
|
||||||
|
throw response.status;
|
||||||
|
} else {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(response) => {
|
||||||
|
this.alertMessage = response.message;
|
||||||
|
this.alertType = response.type;
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.modalFactoryReset.hide();
|
||||||
|
},
|
||||||
|
downloadConfig() {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = "/api/config/get"
|
||||||
|
link.download = 'config.bin'
|
||||||
|
link.click()
|
||||||
|
},
|
||||||
|
uploadConfig(event: Event | null) {
|
||||||
|
this.uploading = true;
|
||||||
|
const formData = new FormData();
|
||||||
|
if (event !== null) {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
if (target.files !== null) {
|
||||||
|
this.file = target.files[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const request = new XMLHttpRequest();
|
||||||
|
request.addEventListener("load", () => {
|
||||||
|
// request.response will hold the response from the server
|
||||||
|
if (request.status === 200) {
|
||||||
|
this.UploadSuccess = true;
|
||||||
|
} else if (request.status !== 500) {
|
||||||
|
this.UploadError = `[HTTP ERROR] ${request.statusText}`;
|
||||||
|
} else {
|
||||||
|
this.UploadError = request.responseText;
|
||||||
|
}
|
||||||
|
this.uploading = false;
|
||||||
|
this.progress = 0;
|
||||||
|
});
|
||||||
|
// Upload progress
|
||||||
|
request.upload.addEventListener("progress", (e) => {
|
||||||
|
this.progress = Math.trunc((e.loaded / e.total) * 100);
|
||||||
|
});
|
||||||
|
request.withCredentials = true;
|
||||||
|
|
||||||
|
formData.append("config", this.file, "config");
|
||||||
|
request.open("post", "/api/config/upload");
|
||||||
|
request.send(formData);
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.UploadError = "";
|
||||||
|
this.UploadSuccess = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -39,6 +39,14 @@
|
|||||||
{{ inverter.data_age }} seconds)
|
{{ inverter.data_age }} seconds)
|
||||||
|
|
||||||
<div class="btn-toolbar" role="toolbar">
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<div class="btn-group me-2" role="group">
|
||||||
|
<button type="button" class="btn btn-sm btn-danger"
|
||||||
|
@click="onShowLimitSettings(inverter.serial)" title="Show / Set Inverter Limit">
|
||||||
|
<BIconSpeedometer style="font-size:24px;" />
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="btn-group me-2" role="group">
|
<div class="btn-group me-2" role="group">
|
||||||
<button type="button" class="btn btn-sm btn-info"
|
<button type="button" class="btn btn-sm btn-info"
|
||||||
@click="onShowDevInfo(inverter.serial)" title="Show Inverter Info">
|
@click="onShowDevInfo(inverter.serial)" title="Show Inverter Info">
|
||||||
@ -130,6 +138,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="limitSettingView" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Limit Settings</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center" v-if="limitSettingLoading">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LimitSettingsCurrent v-if="!limitSettingLoading" :limitData="limitSettingList" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" @click="onHideLimitSettings"
|
||||||
|
data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -139,6 +171,7 @@ import InverterChannelInfo from "@/components/partials/InverterChannelInfo.vue";
|
|||||||
import * as bootstrap from 'bootstrap';
|
import * as bootstrap from 'bootstrap';
|
||||||
import EventLog from '@/components/partials/EventLog.vue';
|
import EventLog from '@/components/partials/EventLog.vue';
|
||||||
import DevInfo from '@/components/partials/DevInfo.vue';
|
import DevInfo from '@/components/partials/DevInfo.vue';
|
||||||
|
import LimitSettingsCurrent from '@/components/partials/LimitSettingsCurrent.vue';
|
||||||
import VedirectView from '@/components/partials/VedirectView.vue';
|
import VedirectView from '@/components/partials/VedirectView.vue';
|
||||||
|
|
||||||
declare interface Inverter {
|
declare interface Inverter {
|
||||||
@ -154,6 +187,7 @@ export default defineComponent({
|
|||||||
InverterChannelInfo,
|
InverterChannelInfo,
|
||||||
EventLog,
|
EventLog,
|
||||||
DevInfo,
|
DevInfo,
|
||||||
|
LimitSettingsCurrent,
|
||||||
VedirectView
|
VedirectView
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -169,7 +203,10 @@ export default defineComponent({
|
|||||||
eventLogLoading: true,
|
eventLogLoading: true,
|
||||||
devInfoView: {} as bootstrap.Modal,
|
devInfoView: {} as bootstrap.Modal,
|
||||||
devInfoList: {},
|
devInfoList: {},
|
||||||
devInfoLoading: true
|
devInfoLoading: true,
|
||||||
|
limitSettingView: {} as bootstrap.Modal,
|
||||||
|
limitSettingList: {},
|
||||||
|
limitSettingLoading: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -180,6 +217,7 @@ export default defineComponent({
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.eventLogView = new bootstrap.Modal('#eventView');
|
this.eventLogView = new bootstrap.Modal('#eventView');
|
||||||
this.devInfoView = new bootstrap.Modal('#devInfoView');
|
this.devInfoView = new bootstrap.Modal('#devInfoView');
|
||||||
|
this.limitSettingView = new bootstrap.Modal('#limitSettingView');
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
@ -264,7 +302,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onShowEventlog(serial: number) {
|
onShowEventlog(serial: number) {
|
||||||
this.eventLogLoading = true;
|
this.eventLogLoading = true;
|
||||||
fetch("/api/eventlog/status")
|
fetch("/api/eventlog/status?inv=" + serial)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.eventLogList = data[serial];
|
this.eventLogList = data[serial];
|
||||||
@ -287,6 +325,20 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.devInfoView.show();
|
this.devInfoView.show();
|
||||||
},
|
},
|
||||||
|
onHideLimitSettings() {
|
||||||
|
this.limitSettingView.hide();
|
||||||
|
},
|
||||||
|
onShowLimitSettings(serial: number) {
|
||||||
|
this.limitSettingLoading = true;
|
||||||
|
fetch("/api/limit/status")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
this.limitSettingList = data[serial];
|
||||||
|
this.limitSettingLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.limitSettingView.show();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -39,6 +39,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<hr class="dropdown-divider" />
|
<hr class="dropdown-divider" />
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link @click="onClick" class="dropdown-item" to="/settings/config">Config Management</router-link>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/firmware/upgrade">Firmware Upgrade</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/firmware/upgrade">Firmware Upgrade</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -39,6 +39,11 @@
|
|||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" id="inputHostname" maxlength="32"
|
<input type="text" class="form-control" id="inputHostname" maxlength="32"
|
||||||
placeholder="Hostname" v-model="networkConfigList.hostname" />
|
placeholder="Hostname" v-model="networkConfigList.hostname" />
|
||||||
|
|
||||||
|
<div class="alert alert-secondary" role="alert">
|
||||||
|
<b>Hint:</b> The text <span class="font-monospace">%06X</span> will be replaced
|
||||||
|
with the last 6 digits of the ESP ChipID in hex format.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -49,6 +49,7 @@ export default defineComponent({
|
|||||||
ap_ssid: "",
|
ap_ssid: "",
|
||||||
ap_stationnum: 0,
|
ap_stationnum: 0,
|
||||||
// InterfaceNetworkInfo
|
// InterfaceNetworkInfo
|
||||||
|
network_hostname: "",
|
||||||
network_ip: "",
|
network_ip: "",
|
||||||
network_netmask: "",
|
network_netmask: "",
|
||||||
network_gateway: "",
|
network_gateway: "",
|
||||||
|
|||||||
@ -7,6 +7,10 @@
|
|||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover table-condensed">
|
<table class="table table-hover table-condensed">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Hostname</th>
|
||||||
|
<td>{{ network_hostname }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>IP Address</th>
|
<th>IP Address</th>
|
||||||
<td>{{ network_ip }}</td>
|
<td>{{ network_ip }}</td>
|
||||||
@ -43,6 +47,7 @@ import { defineComponent } from 'vue';
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
network_hostname: String,
|
||||||
network_ip: String,
|
network_ip: String,
|
||||||
network_netmask: String,
|
network_netmask: String,
|
||||||
network_gateway: String,
|
network_gateway: String,
|
||||||
|
|||||||
33
webapp/src/components/partials/LimitSettingsCurrent.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Current Limit</td>
|
||||||
|
<td>{{ formatNumber(limitData.limit) }}%</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
declare interface LimitData {
|
||||||
|
limit: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
limitData: { type: Object as () => LimitData, required: true },
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formatNumber() {
|
||||||
|
return (num: number) => {
|
||||||
|
return new Intl.NumberFormat(
|
||||||
|
undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }
|
||||||
|
).format(num)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -11,6 +11,7 @@ import MqttInfoView from '@/components/MqttInfoView.vue'
|
|||||||
import InverterAdminView from '@/components/InverterAdminView.vue'
|
import InverterAdminView from '@/components/InverterAdminView.vue'
|
||||||
import DtuAdminView from '@/components/DtuAdminView.vue'
|
import DtuAdminView from '@/components/DtuAdminView.vue'
|
||||||
import FirmwareUpgradeView from '@/components/FirmwareUpgradeView.vue'
|
import FirmwareUpgradeView from '@/components/FirmwareUpgradeView.vue'
|
||||||
|
import ConfigAdminView from '@/components/ConfigAdminView.vue'
|
||||||
import VedirectAdminView from '@/components/VedirectAdminView.vue'
|
import VedirectAdminView from '@/components/VedirectAdminView.vue'
|
||||||
import VedirectInfoView from '@/components/VedirectInfoView.vue'
|
import VedirectInfoView from '@/components/VedirectInfoView.vue'
|
||||||
|
|
||||||
@ -84,6 +85,11 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: '/firmware/upgrade',
|
path: '/firmware/upgrade',
|
||||||
name: 'Firmware Upgrade',
|
name: 'Firmware Upgrade',
|
||||||
component: FirmwareUpgradeView
|
component: FirmwareUpgradeView
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/settings/config',
|
||||||
|
name: 'Config Management',
|
||||||
|
component: ConfigAdminView
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
375
webapp/yarn.lock
@ -36,6 +36,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
|
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
|
||||||
integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==
|
integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==
|
||||||
|
|
||||||
|
"@babel/compat-data@^7.19.0":
|
||||||
|
version "7.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86"
|
||||||
|
integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==
|
||||||
|
|
||||||
"@babel/core@^7.12.16":
|
"@babel/core@^7.12.16":
|
||||||
version "7.18.10"
|
version "7.18.10"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8"
|
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8"
|
||||||
@ -57,21 +62,21 @@
|
|||||||
json5 "^2.2.1"
|
json5 "^2.2.1"
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
"@babel/core@^7.18.13":
|
"@babel/core@^7.19.0":
|
||||||
version "7.18.13"
|
version "7.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.13.tgz#9be8c44512751b05094a4d3ab05fc53a47ce00ac"
|
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3"
|
||||||
integrity sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==
|
integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ampproject/remapping" "^2.1.0"
|
"@ampproject/remapping" "^2.1.0"
|
||||||
"@babel/code-frame" "^7.18.6"
|
"@babel/code-frame" "^7.18.6"
|
||||||
"@babel/generator" "^7.18.13"
|
"@babel/generator" "^7.19.0"
|
||||||
"@babel/helper-compilation-targets" "^7.18.9"
|
"@babel/helper-compilation-targets" "^7.19.0"
|
||||||
"@babel/helper-module-transforms" "^7.18.9"
|
"@babel/helper-module-transforms" "^7.19.0"
|
||||||
"@babel/helpers" "^7.18.9"
|
"@babel/helpers" "^7.19.0"
|
||||||
"@babel/parser" "^7.18.13"
|
"@babel/parser" "^7.19.0"
|
||||||
"@babel/template" "^7.18.10"
|
"@babel/template" "^7.18.10"
|
||||||
"@babel/traverse" "^7.18.13"
|
"@babel/traverse" "^7.19.0"
|
||||||
"@babel/types" "^7.18.13"
|
"@babel/types" "^7.19.0"
|
||||||
convert-source-map "^1.7.0"
|
convert-source-map "^1.7.0"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
gensync "^1.0.0-beta.2"
|
gensync "^1.0.0-beta.2"
|
||||||
@ -96,12 +101,12 @@
|
|||||||
"@jridgewell/gen-mapping" "^0.3.2"
|
"@jridgewell/gen-mapping" "^0.3.2"
|
||||||
jsesc "^2.5.1"
|
jsesc "^2.5.1"
|
||||||
|
|
||||||
"@babel/generator@^7.18.13":
|
"@babel/generator@^7.19.0":
|
||||||
version "7.18.13"
|
version "7.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.13.tgz#59550cbb9ae79b8def15587bdfbaa388c4abf212"
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a"
|
||||||
integrity sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==
|
integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.18.13"
|
"@babel/types" "^7.19.0"
|
||||||
"@jridgewell/gen-mapping" "^0.3.2"
|
"@jridgewell/gen-mapping" "^0.3.2"
|
||||||
jsesc "^2.5.1"
|
jsesc "^2.5.1"
|
||||||
|
|
||||||
@ -130,6 +135,16 @@
|
|||||||
browserslist "^4.20.2"
|
browserslist "^4.20.2"
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
|
"@babel/helper-compilation-targets@^7.19.0":
|
||||||
|
version "7.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0"
|
||||||
|
integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/compat-data" "^7.19.0"
|
||||||
|
"@babel/helper-validator-option" "^7.18.6"
|
||||||
|
browserslist "^4.20.2"
|
||||||
|
semver "^6.3.0"
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin@^7.17.12", "@babel/helper-create-class-features-plugin@^7.18.0":
|
"@babel/helper-create-class-features-plugin@^7.17.12", "@babel/helper-create-class-features-plugin@^7.18.0":
|
||||||
version "7.18.0"
|
version "7.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz#fac430912606331cb075ea8d82f9a4c145a4da19"
|
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz#fac430912606331cb075ea8d82f9a4c145a4da19"
|
||||||
@ -198,6 +213,14 @@
|
|||||||
"@babel/template" "^7.18.6"
|
"@babel/template" "^7.18.6"
|
||||||
"@babel/types" "^7.18.9"
|
"@babel/types" "^7.18.9"
|
||||||
|
|
||||||
|
"@babel/helper-function-name@^7.19.0":
|
||||||
|
version "7.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c"
|
||||||
|
integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==
|
||||||
|
dependencies:
|
||||||
|
"@babel/template" "^7.18.10"
|
||||||
|
"@babel/types" "^7.19.0"
|
||||||
|
|
||||||
"@babel/helper-hoist-variables@^7.16.7":
|
"@babel/helper-hoist-variables@^7.16.7":
|
||||||
version "7.16.7"
|
version "7.16.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246"
|
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246"
|
||||||
@ -247,6 +270,20 @@
|
|||||||
"@babel/traverse" "^7.18.9"
|
"@babel/traverse" "^7.18.9"
|
||||||
"@babel/types" "^7.18.9"
|
"@babel/types" "^7.18.9"
|
||||||
|
|
||||||
|
"@babel/helper-module-transforms@^7.19.0":
|
||||||
|
version "7.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30"
|
||||||
|
integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-environment-visitor" "^7.18.9"
|
||||||
|
"@babel/helper-module-imports" "^7.18.6"
|
||||||
|
"@babel/helper-simple-access" "^7.18.6"
|
||||||
|
"@babel/helper-split-export-declaration" "^7.18.6"
|
||||||
|
"@babel/helper-validator-identifier" "^7.18.6"
|
||||||
|
"@babel/template" "^7.18.10"
|
||||||
|
"@babel/traverse" "^7.19.0"
|
||||||
|
"@babel/types" "^7.19.0"
|
||||||
|
|
||||||
"@babel/helper-optimise-call-expression@^7.16.7":
|
"@babel/helper-optimise-call-expression@^7.16.7":
|
||||||
version "7.16.7"
|
version "7.16.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2"
|
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2"
|
||||||
@ -358,6 +395,15 @@
|
|||||||
"@babel/traverse" "^7.18.9"
|
"@babel/traverse" "^7.18.9"
|
||||||
"@babel/types" "^7.18.9"
|
"@babel/types" "^7.18.9"
|
||||||
|
|
||||||
|
"@babel/helpers@^7.19.0":
|
||||||
|
version "7.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18"
|
||||||
|
integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/template" "^7.18.10"
|
||||||
|
"@babel/traverse" "^7.19.0"
|
||||||
|
"@babel/types" "^7.19.0"
|
||||||
|
|
||||||
"@babel/highlight@^7.18.6":
|
"@babel/highlight@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
|
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
|
||||||
@ -372,10 +418,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9"
|
||||||
integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==
|
integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==
|
||||||
|
|
||||||
"@babel/parser@^7.18.13":
|
"@babel/parser@^7.19.0":
|
||||||
version "7.18.13"
|
version "7.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c"
|
||||||
integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==
|
integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==
|
||||||
|
|
||||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.17.12":
|
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.17.12":
|
||||||
version "7.17.12"
|
version "7.17.12"
|
||||||
@ -1040,19 +1086,19 @@
|
|||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
|
|
||||||
"@babel/traverse@^7.18.13":
|
"@babel/traverse@^7.19.0":
|
||||||
version "7.18.13"
|
version "7.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.13.tgz#5ab59ef51a997b3f10c4587d648b9696b6cb1a68"
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed"
|
||||||
integrity sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==
|
integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.18.6"
|
"@babel/code-frame" "^7.18.6"
|
||||||
"@babel/generator" "^7.18.13"
|
"@babel/generator" "^7.19.0"
|
||||||
"@babel/helper-environment-visitor" "^7.18.9"
|
"@babel/helper-environment-visitor" "^7.18.9"
|
||||||
"@babel/helper-function-name" "^7.18.9"
|
"@babel/helper-function-name" "^7.19.0"
|
||||||
"@babel/helper-hoist-variables" "^7.18.6"
|
"@babel/helper-hoist-variables" "^7.18.6"
|
||||||
"@babel/helper-split-export-declaration" "^7.18.6"
|
"@babel/helper-split-export-declaration" "^7.18.6"
|
||||||
"@babel/parser" "^7.18.13"
|
"@babel/parser" "^7.19.0"
|
||||||
"@babel/types" "^7.18.13"
|
"@babel/types" "^7.19.0"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
|
|
||||||
@ -1065,19 +1111,19 @@
|
|||||||
"@babel/helper-validator-identifier" "^7.18.6"
|
"@babel/helper-validator-identifier" "^7.18.6"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@babel/types@^7.18.13":
|
"@babel/types@^7.19.0":
|
||||||
version "7.18.13"
|
version "7.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600"
|
||||||
integrity sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==
|
integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-string-parser" "^7.18.10"
|
"@babel/helper-string-parser" "^7.18.10"
|
||||||
"@babel/helper-validator-identifier" "^7.18.6"
|
"@babel/helper-validator-identifier" "^7.18.6"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@eslint/eslintrc@^1.3.1":
|
"@eslint/eslintrc@^1.3.2":
|
||||||
version "1.3.1"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.1.tgz#de0807bfeffc37b964a7d0400e0c348ce5a2543d"
|
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.2.tgz#58b69582f3b7271d8fa67fe5251767a5b38ea356"
|
||||||
integrity sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==
|
integrity sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "^6.12.4"
|
ajv "^6.12.4"
|
||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
@ -1270,10 +1316,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/bootstrap@^5.2.2":
|
"@types/bootstrap@^5.2.4":
|
||||||
version "5.2.3"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.3.tgz#562defe61dac6c9598843d4088de8007a36607bd"
|
resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.4.tgz#7f4f4af8e22af8247385549bd2f687088d00d2d3"
|
||||||
integrity sha512-r2SE9NYaaI7B/jJk8gqRtXzlhgFL6dlXBResJkCbQa8ept619WeiOIO4zBQxdmUFzkKNWLK5ZOyYGI3QZoaqbQ==
|
integrity sha512-jGNB81zuDHu1DPvBV7Ox3Z3eyzdWPNguYwrt0j7X90VExA8H7c6qxJh0cz5j3xp0XvSy1TYaP2pkyXCHeo8CaA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@popperjs/core" "^2.9.2"
|
"@popperjs/core" "^2.9.2"
|
||||||
|
|
||||||
@ -1372,10 +1418,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.4.tgz#fd26723a8a3f8f46729812a7f9b4fc2d1608ed39"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.4.tgz#fd26723a8a3f8f46729812a7f9b4fc2d1608ed39"
|
||||||
integrity sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg==
|
integrity sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg==
|
||||||
|
|
||||||
"@types/node@^18.7.15":
|
"@types/node@^18.7.16":
|
||||||
version "18.7.15"
|
version "18.7.16"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.15.tgz#20ae1ec80c57ee844b469f968a1cd511d4088b29"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.16.tgz#0eb3cce1e37c79619943d2fd903919fc30850601"
|
||||||
integrity sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==
|
integrity sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
@ -1466,14 +1512,14 @@
|
|||||||
"@typescript-eslint/typescript-estree" "5.32.0"
|
"@typescript-eslint/typescript-estree" "5.32.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
"@typescript-eslint/parser@^5.36.1":
|
"@typescript-eslint/parser@^5.36.2":
|
||||||
version "5.36.1"
|
version "5.36.2"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.1.tgz#931c22c7bacefd17e29734628cdec8b2acdcf1ce"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.2.tgz#3ddf323d3ac85a25295a55fcb9c7a49ab4680ddd"
|
||||||
integrity sha512-/IsgNGOkBi7CuDfUbwt1eOqUXF9WGVBW9dwEe1pi+L32XrTsZIgmDFIi2RxjzsvB/8i+MIf5JIoTEH8LOZ368A==
|
integrity sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager" "5.36.1"
|
"@typescript-eslint/scope-manager" "5.36.2"
|
||||||
"@typescript-eslint/types" "5.36.1"
|
"@typescript-eslint/types" "5.36.2"
|
||||||
"@typescript-eslint/typescript-estree" "5.36.1"
|
"@typescript-eslint/typescript-estree" "5.36.2"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@5.29.0":
|
"@typescript-eslint/scope-manager@5.29.0":
|
||||||
@ -1492,13 +1538,13 @@
|
|||||||
"@typescript-eslint/types" "5.32.0"
|
"@typescript-eslint/types" "5.32.0"
|
||||||
"@typescript-eslint/visitor-keys" "5.32.0"
|
"@typescript-eslint/visitor-keys" "5.32.0"
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@5.36.1":
|
"@typescript-eslint/scope-manager@5.36.2":
|
||||||
version "5.36.1"
|
version "5.36.2"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.1.tgz#23c49b7ddbcffbe09082e6694c2524950766513f"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz#a75eb588a3879ae659514780831370642505d1cd"
|
||||||
integrity sha512-pGC2SH3/tXdu9IH3ItoqciD3f3RRGCh7hb9zPdN2Drsr341zgd6VbhP5OHQO/reUqihNltfPpMpTNihFMarP2w==
|
integrity sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.36.1"
|
"@typescript-eslint/types" "5.36.2"
|
||||||
"@typescript-eslint/visitor-keys" "5.36.1"
|
"@typescript-eslint/visitor-keys" "5.36.2"
|
||||||
|
|
||||||
"@typescript-eslint/type-utils@5.29.0":
|
"@typescript-eslint/type-utils@5.29.0":
|
||||||
version "5.29.0"
|
version "5.29.0"
|
||||||
@ -1519,10 +1565,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.32.0.tgz#484273021eeeae87ddb288f39586ef5efeb6dcd8"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.32.0.tgz#484273021eeeae87ddb288f39586ef5efeb6dcd8"
|
||||||
integrity sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ==
|
integrity sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ==
|
||||||
|
|
||||||
"@typescript-eslint/types@5.36.1":
|
"@typescript-eslint/types@5.36.2":
|
||||||
version "5.36.1"
|
version "5.36.2"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.1.tgz#1cf0e28aed1cb3ee676917966eb23c2f8334ce2c"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.2.tgz#a5066e500ebcfcee36694186ccc57b955c05faf9"
|
||||||
integrity sha512-jd93ShpsIk1KgBTx9E+hCSEuLCUFwi9V/urhjOWnOaksGZFbTOxAT47OH2d4NLJnLhkVD+wDbB48BuaycZPLBg==
|
integrity sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@5.29.0":
|
"@typescript-eslint/typescript-estree@5.29.0":
|
||||||
version "5.29.0"
|
version "5.29.0"
|
||||||
@ -1550,13 +1596,13 @@
|
|||||||
semver "^7.3.7"
|
semver "^7.3.7"
|
||||||
tsutils "^3.21.0"
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@5.36.1":
|
"@typescript-eslint/typescript-estree@5.36.2":
|
||||||
version "5.36.1"
|
version "5.36.2"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.1.tgz#b857f38d6200f7f3f4c65cd0a5afd5ae723f2adb"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz#0c93418b36c53ba0bc34c61fe9405c4d1d8fe560"
|
||||||
integrity sha512-ih7V52zvHdiX6WcPjsOdmADhYMDN15SylWRZrT2OMy80wzKbc79n8wFW0xpWpU0x3VpBz/oDgTm2xwDAnFTl+g==
|
integrity sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.36.1"
|
"@typescript-eslint/types" "5.36.2"
|
||||||
"@typescript-eslint/visitor-keys" "5.36.1"
|
"@typescript-eslint/visitor-keys" "5.36.2"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
globby "^11.1.0"
|
globby "^11.1.0"
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
@ -1591,12 +1637,12 @@
|
|||||||
"@typescript-eslint/types" "5.32.0"
|
"@typescript-eslint/types" "5.32.0"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@5.36.1":
|
"@typescript-eslint/visitor-keys@5.36.2":
|
||||||
version "5.36.1"
|
version "5.36.2"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.1.tgz#7731175312d65738e501780f923896d200ad1615"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz#2f8f78da0a3bad3320d2ac24965791ac39dace5a"
|
||||||
integrity sha512-ojB9aRyRFzVMN3b5joSYni6FAS10BBSCAfKJhjJAV08t/a95aM6tAhz+O1jF+EtgxktuSO3wJysp2R+Def/IWQ==
|
integrity sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.36.1"
|
"@typescript-eslint/types" "5.36.2"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
"@vue/babel-helper-vue-jsx-merge-props@^1.2.1":
|
"@vue/babel-helper-vue-jsx-merge-props@^1.2.1":
|
||||||
@ -1855,47 +1901,47 @@
|
|||||||
semver "^7.3.4"
|
semver "^7.3.4"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
"@vue/compiler-core@3.2.38":
|
"@vue/compiler-core@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.38.tgz#0a2a7bffd2280ac19a96baf5301838a2cf1964d7"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.39.tgz#0d77e635f4bdb918326669155a2dc977c053943e"
|
||||||
integrity sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==
|
integrity sha512-mf/36OWXqWn0wsC40nwRRGheR/qoID+lZXbIuLnr4/AngM0ov8Xvv8GHunC0rKRIkh60bTqydlqTeBo49rlbqw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.16.4"
|
"@babel/parser" "^7.16.4"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
source-map "^0.6.1"
|
source-map "^0.6.1"
|
||||||
|
|
||||||
"@vue/compiler-dom@3.2.38":
|
"@vue/compiler-dom@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz#53d04ed0c0c62d1ef259bf82f9b28100a880b6fd"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.39.tgz#bd69d35c1a48fe2cea4ab9e96d2a3a735d146fdf"
|
||||||
integrity sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==
|
integrity sha512-HMFI25Be1C8vLEEv1hgEO1dWwG9QQ8LTTPmCkblVJY/O3OvWx6r1+zsox5mKPMGvqYEZa6l8j+xgOfUspgo7hw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-core" "3.2.38"
|
"@vue/compiler-core" "3.2.39"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
|
|
||||||
"@vue/compiler-sfc@3.2.38":
|
"@vue/compiler-sfc@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz#9e763019471a535eb1fceeaac9d4d18a83f0940f"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.39.tgz#8fe29990f672805b7c5a2ecfa5b05e681c862ea2"
|
||||||
integrity sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==
|
integrity sha512-fqAQgFs1/BxTUZkd0Vakn3teKUt//J3c420BgnYgEOoVdTwYpBTSXCMJ88GOBCylmUBbtquGPli9tVs7LzsWIA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.16.4"
|
"@babel/parser" "^7.16.4"
|
||||||
"@vue/compiler-core" "3.2.38"
|
"@vue/compiler-core" "3.2.39"
|
||||||
"@vue/compiler-dom" "3.2.38"
|
"@vue/compiler-dom" "3.2.39"
|
||||||
"@vue/compiler-ssr" "3.2.38"
|
"@vue/compiler-ssr" "3.2.39"
|
||||||
"@vue/reactivity-transform" "3.2.38"
|
"@vue/reactivity-transform" "3.2.39"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
magic-string "^0.25.7"
|
magic-string "^0.25.7"
|
||||||
postcss "^8.1.10"
|
postcss "^8.1.10"
|
||||||
source-map "^0.6.1"
|
source-map "^0.6.1"
|
||||||
|
|
||||||
"@vue/compiler-ssr@3.2.38":
|
"@vue/compiler-ssr@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz#933b23bf99e667e5078eefc6ba94cb95fd765dfe"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.39.tgz#4f3bfb535cb98b764bee45e078700e03ccc60633"
|
||||||
integrity sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==
|
integrity sha512-EoGCJ6lincKOZGW+0Ky4WOKsSmqL7hp1ZYgen8M7u/mlvvEQUaO9tKKOy7K43M9U2aA3tPv0TuYYQFrEbK2eFQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-dom" "3.2.38"
|
"@vue/compiler-dom" "3.2.39"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
|
|
||||||
"@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.3.0":
|
"@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.3.0":
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
@ -1918,62 +1964,62 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.2.1.tgz#6f2948ff002ec46df01420dfeff91de16c5b4092"
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.2.1.tgz#6f2948ff002ec46df01420dfeff91de16c5b4092"
|
||||||
integrity sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==
|
integrity sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==
|
||||||
|
|
||||||
"@vue/eslint-config-typescript@^11.0.0":
|
"@vue/eslint-config-typescript@^11.0.1":
|
||||||
version "11.0.0"
|
version "11.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.0.tgz#bac0cb2d381625b5bf568d2025acffc0fd09113e"
|
resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.1.tgz#d79b3656aecea844ec9875bc93155163f684dde7"
|
||||||
integrity sha512-txuRzxnQVmtUvvy9UyWUy9sHWXNeRPGmSPqP53hRtaiUeCTAondI9Ho9GQYI/8/eWljYOST7iA4Aa8sANBkWaA==
|
integrity sha512-0U+nL0nA7ahnGPk3rTN49x76miUwuQtQPQNWOFvAcjg6nFJkIkA8qbGNtXwsuHtwBwRtWpHhShL3zK07v+632w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/eslint-plugin" "^5.0.0"
|
"@typescript-eslint/eslint-plugin" "^5.0.0"
|
||||||
"@typescript-eslint/parser" "^5.0.0"
|
"@typescript-eslint/parser" "^5.0.0"
|
||||||
vue-eslint-parser "^9.0.0"
|
vue-eslint-parser "^9.0.0"
|
||||||
|
|
||||||
"@vue/reactivity-transform@3.2.38":
|
"@vue/reactivity-transform@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz#a856c217b2ead99eefb6fddb1d61119b2cb67984"
|
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.39.tgz#da6ae6c8fd77791b9ae21976720d116591e1c4aa"
|
||||||
integrity sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==
|
integrity sha512-HGuWu864zStiWs9wBC6JYOP1E00UjMdDWIG5W+FpUx28hV3uz9ODOKVNm/vdOy/Pvzg8+OcANxAVC85WFBbl3A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.16.4"
|
"@babel/parser" "^7.16.4"
|
||||||
"@vue/compiler-core" "3.2.38"
|
"@vue/compiler-core" "3.2.39"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
magic-string "^0.25.7"
|
magic-string "^0.25.7"
|
||||||
|
|
||||||
"@vue/reactivity@3.2.38":
|
"@vue/reactivity@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.38.tgz#d576fdcea98eefb96a1f1ad456e289263e87292e"
|
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.39.tgz#e6e3615fe2288d4232b104640ddabd0729a78c80"
|
||||||
integrity sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==
|
integrity sha512-vlaYX2a3qMhIZfrw3Mtfd+BuU+TZmvDrPMa+6lpfzS9k/LnGxkSuf0fhkP0rMGfiOHPtyKoU9OJJJFGm92beVQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
|
|
||||||
"@vue/runtime-core@3.2.38":
|
"@vue/runtime-core@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.38.tgz#d19cf591c210713f80e6a94ffbfef307c27aea06"
|
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.39.tgz#dc1faccab11b3e81197aba33fb30c9447c1d2c84"
|
||||||
integrity sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==
|
integrity sha512-xKH5XP57JW5JW+8ZG1khBbuLakINTgPuINKL01hStWLTTGFOrM49UfCFXBcFvWmSbci3gmJyLl2EAzCaZWsx8g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/reactivity" "3.2.38"
|
"@vue/reactivity" "3.2.39"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
|
|
||||||
"@vue/runtime-dom@3.2.38":
|
"@vue/runtime-dom@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.38.tgz#fec711f65c2485991289fd4798780aa506469b48"
|
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.39.tgz#4a8cb132bcef316e8151c5ed07fc7272eb064614"
|
||||||
integrity sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A==
|
integrity sha512-4G9AEJP+sLhsqf5wXcyKVWQKUhI+iWfy0hWQgea+CpaTD7BR0KdQzvoQdZhwCY6B3oleSyNLkLAQwm0ya/wNoA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/runtime-core" "3.2.38"
|
"@vue/runtime-core" "3.2.39"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
csstype "^2.6.8"
|
csstype "^2.6.8"
|
||||||
|
|
||||||
"@vue/server-renderer@3.2.38":
|
"@vue/server-renderer@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.38.tgz#01a4c0f218e90b8ad1815074208a1974ded109aa"
|
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.39.tgz#4358292d925233b0d8b54cf0513eaece8b2351c5"
|
||||||
integrity sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA==
|
integrity sha512-1yn9u2YBQWIgytFMjz4f/t0j43awKytTGVptfd3FtBk76t1pd8mxbek0G/DrnjJhd2V7mSTb5qgnxMYt8Z5iSQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-ssr" "3.2.38"
|
"@vue/compiler-ssr" "3.2.39"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
|
|
||||||
"@vue/shared@3.2.38":
|
"@vue/shared@3.2.39":
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.38.tgz#e823f0cb2e85b6bf43430c0d6811b1441c300f3c"
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.39.tgz#302df167559a1a5156da162d8cc6760cef67f8e3"
|
||||||
integrity sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==
|
integrity sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw==
|
||||||
|
|
||||||
"@vue/vue-loader-v15@npm:vue-loader@^15.9.7":
|
"@vue/vue-loader-v15@npm:vue-loader@^15.9.7":
|
||||||
version "15.9.8"
|
version "15.9.8"
|
||||||
@ -2413,10 +2459,10 @@ bootstrap-icons-vue@^1.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/bootstrap-icons-vue/-/bootstrap-icons-vue-1.8.1.tgz#ce4a0c1f6efe41dabcc1341f2cb191d307fbaf50"
|
resolved "https://registry.yarnpkg.com/bootstrap-icons-vue/-/bootstrap-icons-vue-1.8.1.tgz#ce4a0c1f6efe41dabcc1341f2cb191d307fbaf50"
|
||||||
integrity sha512-uItRULwQz0epETi9x/RBEqfjHmTAmkIIczpH1R6L9T6yyaaijk0826PzTWnWNm15tw66JT/8GNuXjB0HI5PHLw==
|
integrity sha512-uItRULwQz0epETi9x/RBEqfjHmTAmkIIczpH1R6L9T6yyaaijk0826PzTWnWNm15tw66JT/8GNuXjB0HI5PHLw==
|
||||||
|
|
||||||
bootstrap@^5.2.0:
|
bootstrap@^5.2.1:
|
||||||
version "5.2.0"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.0.tgz#838727fb60f1630db370fe57c63cbcf2962bb3d3"
|
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.1.tgz#45f97ff05cbe828bad807b014d8425f3aeb8ec3a"
|
||||||
integrity sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==
|
integrity sha512-UQi3v2NpVPEi1n35dmRRzBJFlgvWHYwyem6yHhuT6afYF+sziEt46McRbT//kVXZ7b1YUYEVGdXEH74Nx3xzGA==
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.11"
|
version "1.1.11"
|
||||||
@ -2791,10 +2837,10 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.8.3:
|
|||||||
browserslist "^4.20.3"
|
browserslist "^4.20.3"
|
||||||
semver "7.0.0"
|
semver "7.0.0"
|
||||||
|
|
||||||
core-js@^3.25.0:
|
core-js@^3.25.1:
|
||||||
version "3.25.0"
|
version "3.25.1"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.0.tgz#be71d9e0dd648ffd70c44a7ec2319d039357eceb"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.1.tgz#5818e09de0db8956e16bf10e2a7141e931b7c69c"
|
||||||
integrity sha512-CVU1xvJEfJGhyCpBrzzzU1kjCfgsGUxhEvwUV2e/cOedYWHdmluamx+knDnmhqALddMG16fZvIqvs9aijsHHaA==
|
integrity sha512-sr0FY4lnO1hkQ4gLDr24K0DGnweGO1QwSj5BpfQjpSJPdqWalja4cTps29Y/PJVG/P7FYlPDkH3hO+Tr0CvDgQ==
|
||||||
|
|
||||||
core-js@^3.8.3:
|
core-js@^3.8.3:
|
||||||
version "3.24.1"
|
version "3.24.1"
|
||||||
@ -3293,12 +3339,12 @@ eslint-webpack-plugin@^3.1.0:
|
|||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
schema-utils "^3.1.1"
|
schema-utils "^3.1.1"
|
||||||
|
|
||||||
eslint@^8.23.0:
|
eslint@^8.23.1:
|
||||||
version "8.23.0"
|
version "8.23.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.0.tgz#a184918d288820179c6041bb3ddcc99ce6eea040"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.1.tgz#cfd7b3f7fdd07db8d16b4ac0516a29c8d8dca5dc"
|
||||||
integrity sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==
|
integrity sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/eslintrc" "^1.3.1"
|
"@eslint/eslintrc" "^1.3.2"
|
||||||
"@humanwhocodes/config-array" "^0.10.4"
|
"@humanwhocodes/config-array" "^0.10.4"
|
||||||
"@humanwhocodes/gitignore-to-minimatch" "^1.0.2"
|
"@humanwhocodes/gitignore-to-minimatch" "^1.0.2"
|
||||||
"@humanwhocodes/module-importer" "^1.0.1"
|
"@humanwhocodes/module-importer" "^1.0.1"
|
||||||
@ -3317,7 +3363,6 @@ eslint@^8.23.0:
|
|||||||
fast-deep-equal "^3.1.3"
|
fast-deep-equal "^3.1.3"
|
||||||
file-entry-cache "^6.0.1"
|
file-entry-cache "^6.0.1"
|
||||||
find-up "^5.0.0"
|
find-up "^5.0.0"
|
||||||
functional-red-black-tree "^1.0.1"
|
|
||||||
glob-parent "^6.0.1"
|
glob-parent "^6.0.1"
|
||||||
globals "^13.15.0"
|
globals "^13.15.0"
|
||||||
globby "^11.1.0"
|
globby "^11.1.0"
|
||||||
@ -3326,6 +3371,7 @@ eslint@^8.23.0:
|
|||||||
import-fresh "^3.0.0"
|
import-fresh "^3.0.0"
|
||||||
imurmurhash "^0.1.4"
|
imurmurhash "^0.1.4"
|
||||||
is-glob "^4.0.0"
|
is-glob "^4.0.0"
|
||||||
|
js-sdsl "^4.1.4"
|
||||||
js-yaml "^4.1.0"
|
js-yaml "^4.1.0"
|
||||||
json-stable-stringify-without-jsonify "^1.0.1"
|
json-stable-stringify-without-jsonify "^1.0.1"
|
||||||
levn "^0.4.1"
|
levn "^0.4.1"
|
||||||
@ -4168,6 +4214,11 @@ js-message@1.0.7:
|
|||||||
resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
|
resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
|
||||||
integrity sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==
|
integrity sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==
|
||||||
|
|
||||||
|
js-sdsl@^4.1.4:
|
||||||
|
version "4.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.4.tgz#78793c90f80e8430b7d8dc94515b6c77d98a26a6"
|
||||||
|
integrity sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==
|
||||||
|
|
||||||
js-tokens@^4.0.0:
|
js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
@ -6097,10 +6148,10 @@ type-is@~1.6.18:
|
|||||||
media-typer "0.3.0"
|
media-typer "0.3.0"
|
||||||
mime-types "~2.1.24"
|
mime-types "~2.1.24"
|
||||||
|
|
||||||
typescript@^4.8.2:
|
typescript@^4.8.3:
|
||||||
version "4.8.2"
|
version "4.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.3.tgz#d59344522c4bc464a65a730ac695007fdb66dd88"
|
||||||
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
|
integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@^2.0.0:
|
unicode-canonical-property-names-ecmascript@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@ -6234,16 +6285,16 @@ vue-template-es2015-compiler@^1.9.0:
|
|||||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||||
|
|
||||||
vue@^3.2.38:
|
vue@^3.2.39:
|
||||||
version "3.2.38"
|
version "3.2.39"
|
||||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.38.tgz#cda3a414631745b194971219318a792dbbccdec0"
|
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.39.tgz#de071c56c4c32c41cbd54e55f11404295c0dd62d"
|
||||||
integrity sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q==
|
integrity sha512-tRkguhRTw9NmIPXhzk21YFBqXHT2t+6C6wPOgQ50fcFVWnPdetmRqbmySRHznrYjX2E47u0cGlKGcxKZJ38R/g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-dom" "3.2.38"
|
"@vue/compiler-dom" "3.2.39"
|
||||||
"@vue/compiler-sfc" "3.2.38"
|
"@vue/compiler-sfc" "3.2.39"
|
||||||
"@vue/runtime-dom" "3.2.38"
|
"@vue/runtime-dom" "3.2.39"
|
||||||
"@vue/server-renderer" "3.2.38"
|
"@vue/server-renderer" "3.2.39"
|
||||||
"@vue/shared" "3.2.38"
|
"@vue/shared" "3.2.39"
|
||||||
|
|
||||||
watchpack@^2.3.1:
|
watchpack@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
|
|||||||