diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 048b3375..6d6f6020 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,14 @@ name: OpenDTU Build -on: [push, pull_request] +on: + push: + paths-ignore: + - docs/** + - '**/*.md' + pull_request: + paths-ignore: + - docs/** + - '**/*.md' jobs: get_default_envs: @@ -92,12 +100,15 @@ jobs: name: opendtu-${{ matrix.environment }} path: | .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin + .pio/build/${{ matrix.environment }}/partitions.bin - uses: actions/upload-artifact@v3 if: startsWith(github.ref, 'refs/tags/') with: name: opendtu-release - path: .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin + path: | + .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin + .pio/build/${{ matrix.environment }}/partitions.bin release: name: Create Release diff --git a/.github/workflows/cpplint.yml b/.github/workflows/cpplint.yml new file mode 100644 index 00000000..5a44695e --- /dev/null +++ b/.github/workflows/cpplint.yml @@ -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 diff --git a/README.md b/README.md index 564299d0..8b9215e1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # OpenDTU_VeDirect +[![OpenDTU Build](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml) +[![cpplint](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml) + ## Background 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. @@ -55,6 +58,7 @@ Sends text raw data as difined in VE.Direct spec. * Read live data from inverter * Show inverters internal event log * 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+ * Multi-Inverter support * 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) * 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: + +![NodeMCU-ESP32](docs/nodemcu-esp32.png) + +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: + +![nrf24l01plus](docs/nrf24l01plus.png) + +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 ### Schematic ![Schematic](docs/Wiring_ESP32_Schematic.png) @@ -98,11 +135,13 @@ This can be achieved by editing the 'platformio.ini' file and add/change one or ## Flashing and starting up ### 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) -* 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) -* 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) -* Adjust the COM port in the file "platformio.ini". It occurs twice: +* Install git and enable git in vscode - [git download](https://git-scm.com/downloads/) - [Instructions](https://www.jcchouinard.com/install-git-in-vscode/) +* Clone this repository (you really have to clone it, don't just download the ZIP file. During the build process the git hash gets embedded into the firmware. If you download the ZIP file a build error will occur): Inside vscode open the command palette by pressing `CTRL` + `SHIFT` + `P`. Enter `git clone`, add the repository-URL `https://github.com/tbnobody/OpenDTU`. Next you have to choose (or create) a target directory. +* In vscode, choose File --> Open Folder and select the previously downloaded source code. (You have to select the folder which contains the "platformio.ini" file) +* 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 * 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. @@ -152,3 +191,21 @@ After the successful upload, the OpenDTU immediately restarts into the new firmw * Building the microcontroller firmware * 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) diff --git a/docs/Wiring_ESP32_Schematic.png b/docs/Wiring_ESP32_Schematic.png index e33e0c80..f2c00651 100644 Binary files a/docs/Wiring_ESP32_Schematic.png and b/docs/Wiring_ESP32_Schematic.png differ diff --git a/docs/nodemcu-esp32.png b/docs/nodemcu-esp32.png new file mode 100644 index 00000000..2a45bc56 Binary files /dev/null and b/docs/nodemcu-esp32.png differ diff --git a/docs/nrf24l01plus.png b/docs/nrf24l01plus.png new file mode 100644 index 00000000..c46643bc Binary files /dev/null and b/docs/nrf24l01plus.png differ diff --git a/docs/screenshots/01_LiveView.png b/docs/screenshots/01_LiveView.png index 1998d356..1291342a 100644 Binary files a/docs/screenshots/01_LiveView.png and b/docs/screenshots/01_LiveView.png differ diff --git a/docs/screenshots/04_MqttAdmin.png b/docs/screenshots/04_MqttAdmin.png index 980306a4..d1da0190 100644 Binary files a/docs/screenshots/04_MqttAdmin.png and b/docs/screenshots/04_MqttAdmin.png differ diff --git a/docs/screenshots/14_ConfigManagement.png b/docs/screenshots/14_ConfigManagement.png new file mode 100644 index 00000000..109e3b7a Binary files /dev/null and b/docs/screenshots/14_ConfigManagement.png differ diff --git a/include/NetworkSettings.h b/include/NetworkSettings.h index 6c6fae8d..7f699bd4 100644 --- a/include/NetworkSettings.h +++ b/include/NetworkSettings.h @@ -50,7 +50,7 @@ public: IPAddress gatewayIP(); IPAddress dnsIP(uint8_t dns_no = 0); String macAddress(); - const char* getHostname(); + static String getHostname(); bool isConnected(); network_mode NetworkMode(); @@ -62,12 +62,13 @@ private: void setStaticIp(); void setupMode(); void NetworkEvent(WiFiEvent_t event); + static uint32_t getChipId(); bool adminEnabled = true; bool forceDisconnection = false; int adminTimeoutCounter = 0; int connectTimeoutTimer = 0; int connectRedoTimer = 0; - unsigned long lastTimerCall = 0; + uint32_t lastTimerCall = 0; const byte DNS_PORT = 53; IPAddress apIp; IPAddress apNetmask; diff --git a/include/WebApi.h b/include/WebApi.h index d492783b..55dc3238 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "WebApi_config.h" #include "WebApi_devinfo.h" #include "WebApi_dtu.h" #include "WebApi_eventlog.h" #include "WebApi_firmware.h" #include "WebApi_inverter.h" +#include "WebApi_limit.h" #include "WebApi_mqtt.h" #include "WebApi_network.h" #include "WebApi_ntp.h" @@ -26,11 +28,13 @@ private: AsyncWebServer _server; AsyncEventSource _events; + WebApiConfigClass _webApiConfig; WebApiDevInfoClass _webApiDevInfo; WebApiDtuClass _webApiDtu; WebApiEventlogClass _webApiEventlog; WebApiFirmwareClass _webApiFirmware; WebApiInverterClass _webApiInverter; + WebApiLimitClass _webApiLimit; WebApiMqttClass _webApiMqtt; WebApiNetworkClass _webApiNetwork; WebApiNtpClass _webApiNtp; diff --git a/include/WebApi_config.h b/include/WebApi_config.h new file mode 100644 index 00000000..42bea04e --- /dev/null +++ b/include/WebApi_config.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +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; +}; \ No newline at end of file diff --git a/include/WebApi_limit.h b/include/WebApi_limit.h new file mode 100644 index 00000000..59ed4e71 --- /dev/null +++ b/include/WebApi_limit.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +class WebApiLimitClass { +public: + void init(AsyncWebServer* server); + void loop(); + +private: + void onLimitStatus(AsyncWebServerRequest* request); + + AsyncWebServer* _server; +}; \ No newline at end of file diff --git a/include/WebApi_mqtt.h b/include/WebApi_mqtt.h index 56e26541..99a494f7 100644 --- a/include/WebApi_mqtt.h +++ b/include/WebApi_mqtt.h @@ -14,7 +14,7 @@ private: void onMqttStatus(AsyncWebServerRequest* request); void onMqttAdminGet(AsyncWebServerRequest* request); void onMqttAdminPost(AsyncWebServerRequest* request); - String getRootCaCertInfo(char* cert); + String getRootCaCertInfo(const char* cert); AsyncWebServer* _server; }; \ No newline at end of file diff --git a/include/WebApi_ws_live.h b/include/WebApi_ws_live.h index 631aaaae..d21f376a 100644 --- a/include/WebApi_ws_live.h +++ b/include/WebApi_ws_live.h @@ -22,6 +22,6 @@ private: uint32_t _lastWsPublish = 0; uint32_t _lastInvUpdateCheck = 0; - unsigned long _lastWsCleanup = 0; + uint32_t _lastWsCleanup = 0; uint32_t _newestInverterTimestamp = 0; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index 5ec88dc2..1bfbad53 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -31,6 +31,12 @@ void HoymilesClass::loop() // Fetch event log 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) if (iv->Statistics()->getLastUpdate() > 0 && (iv->DevInfo()->getLastUpdateAll() == 0 || iv->DevInfo()->getLastUpdateSample() == 0)) { Serial.println(F("Request device info")); diff --git a/lib/Hoymiles/src/Hoymiles.h b/lib/Hoymiles/src/Hoymiles.h index 57979553..10b0c9c4 100644 --- a/lib/Hoymiles/src/Hoymiles.h +++ b/lib/Hoymiles/src/Hoymiles.h @@ -7,6 +7,8 @@ #include #include +#define HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL (10 * 60 * 1000) // 10 minutes + class HoymilesClass { public: void init(); @@ -28,7 +30,7 @@ private: std::vector> _inverters; std::unique_ptr _radio; - uint32_t _pollInterval; + uint32_t _pollInterval = 0; uint32_t _lastPoll = 0; }; diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index 0ffee373..acbf4fb1 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -9,8 +9,6 @@ #include #include -using namespace std; - // number of fragments hold in buffer #define FRAGMENT_BUFFER_SIZE 30 @@ -57,7 +55,7 @@ public: template T* enqueCommand() { - _commandQueue.push(make_shared()); + _commandQueue.push(std::make_shared()); return static_cast(_commandQueue.back().get()); } @@ -75,12 +73,12 @@ private: std::unique_ptr _hspi; std::unique_ptr _radio; 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 _txChIdx; + uint8_t _txChIdx = 0; - volatile bool _packetReceived; + volatile bool _packetReceived = false; CircularBuffer _rxBuffer; TimeoutHelper _rxTimeout; @@ -89,5 +87,5 @@ private: bool _busyFlag = false; - queue> _commandQueue; + std::queue> _commandQueue; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/AlarmDataCommand.h b/lib/Hoymiles/src/commands/AlarmDataCommand.h index a4015582..5af30ef6 100644 --- a/lib/Hoymiles/src/commands/AlarmDataCommand.h +++ b/lib/Hoymiles/src/commands/AlarmDataCommand.h @@ -4,7 +4,7 @@ class AlarmDataCommand : public MultiDataCommand { 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); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/CommandAbstract.h b/lib/Hoymiles/src/commands/CommandAbstract.h index b09177fe..dfcd5224 100644 --- a/lib/Hoymiles/src/commands/CommandAbstract.h +++ b/lib/Hoymiles/src/commands/CommandAbstract.h @@ -10,7 +10,7 @@ class InverterAbstract; class CommandAbstract { 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() {}; template diff --git a/lib/Hoymiles/src/commands/DevControlCommand.h b/lib/Hoymiles/src/commands/DevControlCommand.h index fb2e8f05..44653447 100644 --- a/lib/Hoymiles/src/commands/DevControlCommand.h +++ b/lib/Hoymiles/src/commands/DevControlCommand.h @@ -4,5 +4,5 @@ class DevControlCommand : public CommandAbstract { public: - DevControlCommand(uint64_t target_address = 0, uint64_t router_address = 0); + explicit DevControlCommand(uint64_t target_address = 0, uint64_t router_address = 0); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/DevInfoAllCommand.h b/lib/Hoymiles/src/commands/DevInfoAllCommand.h index 16a0bd9b..964ffaf0 100644 --- a/lib/Hoymiles/src/commands/DevInfoAllCommand.h +++ b/lib/Hoymiles/src/commands/DevInfoAllCommand.h @@ -4,7 +4,7 @@ class DevInfoAllCommand : public MultiDataCommand { 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); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/DevInfoSampleCommand.h b/lib/Hoymiles/src/commands/DevInfoSampleCommand.h index 5cefd5ef..4bc7aaf9 100644 --- a/lib/Hoymiles/src/commands/DevInfoSampleCommand.h +++ b/lib/Hoymiles/src/commands/DevInfoSampleCommand.h @@ -4,7 +4,7 @@ class DevInfoSampleCommand : public MultiDataCommand { 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); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.h b/lib/Hoymiles/src/commands/MultiDataCommand.h index 46129ebb..511d8c75 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.h +++ b/lib/Hoymiles/src/commands/MultiDataCommand.h @@ -6,7 +6,7 @@ class MultiDataCommand : public CommandAbstract { 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); time_t getTime(); diff --git a/lib/Hoymiles/src/commands/ParaSetCommand.h b/lib/Hoymiles/src/commands/ParaSetCommand.h index 926ec30c..caa0a521 100644 --- a/lib/Hoymiles/src/commands/ParaSetCommand.h +++ b/lib/Hoymiles/src/commands/ParaSetCommand.h @@ -4,5 +4,5 @@ class ParaSetCommand : public CommandAbstract { public: - ParaSetCommand(uint64_t target_address = 0, uint64_t router_address = 0); + explicit ParaSetCommand(uint64_t target_address = 0, uint64_t router_address = 0); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/README.md b/lib/Hoymiles/src/commands/README.md new file mode 100644 index 00000000..dc6dd5cf --- /dev/null +++ b/lib/Hoymiles/src/commands/README.md @@ -0,0 +1,13 @@ +# Class hierarchy + +* CommandAbstract + * DevControlCommand + * MultiDataCommand + * AlarmDataCommand + * DevInfoAllCommand + * DevInfoSampleCommand + * RealTimeRunDataCommand + * SystemConfigParaCommand + * ParaSetCommand + * SingleDataCommand + * RequestFrameCommand \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.h b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.h index 09a2bc48..0044ab05 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.h +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.h @@ -4,7 +4,7 @@ class RealTimeRunDataCommand : public MultiDataCommand { 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); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RequestFrameCommand.h b/lib/Hoymiles/src/commands/RequestFrameCommand.h index 4d627679..aebf9483 100644 --- a/lib/Hoymiles/src/commands/RequestFrameCommand.h +++ b/lib/Hoymiles/src/commands/RequestFrameCommand.h @@ -4,7 +4,7 @@ class RequestFrameCommand : public SingleDataCommand { 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); uint8_t getFrameNo(); diff --git a/lib/Hoymiles/src/commands/SingleDataCommand.h b/lib/Hoymiles/src/commands/SingleDataCommand.h index aba0fda9..98722518 100644 --- a/lib/Hoymiles/src/commands/SingleDataCommand.h +++ b/lib/Hoymiles/src/commands/SingleDataCommand.h @@ -4,5 +4,5 @@ class SingleDataCommand : public CommandAbstract { public: - SingleDataCommand(uint64_t target_address = 0, uint64_t router_address = 0); + explicit SingleDataCommand(uint64_t target_address = 0, uint64_t router_address = 0); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp new file mode 100644 index 00000000..664442bb --- /dev/null +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp @@ -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; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.h b/lib/Hoymiles/src/commands/SystemConfigParaCommand.h new file mode 100644 index 00000000..805f665e --- /dev/null +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.h @@ -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); +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/crc.cpp b/lib/Hoymiles/src/crc.cpp index 6f7a9913..ce3bbf90 100644 --- a/lib/Hoymiles/src/crc.cpp +++ b/lib/Hoymiles/src/crc.cpp @@ -1,6 +1,6 @@ #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; for (uint8_t i = 0; i < len; i++) { @@ -12,7 +12,7 @@ uint8_t crc8(uint8_t buf[], uint8_t len) 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; uint8_t shift = 0; @@ -29,7 +29,7 @@ uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start) 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; uint8_t idx, val = buf[(startBit >> 3)]; diff --git a/lib/Hoymiles/src/crc.h b/lib/Hoymiles/src/crc.h index eaa7299f..dd640cc3 100644 --- a/lib/Hoymiles/src/crc.h +++ b/lib/Hoymiles/src/crc.h @@ -8,6 +8,6 @@ #define CRC16_MODBUS_POLYNOM 0xA001 #define CRC16_NRF24_POLYNOM 0x1021 -uint8_t crc8(uint8_t buf[], uint8_t len); -uint16_t crc16(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); +uint8_t crc8(const uint8_t buf[], uint8_t len); +uint16_t crc16(const uint8_t buf[], uint8_t len, uint16_t start = 0xffff); +uint16_t crc16nrf24(const uint8_t buf[], uint16_t lenBits, uint16_t startBit = 0, uint16_t crcIn = 0xffff); diff --git a/lib/Hoymiles/src/inverters/HM_1CH.h b/lib/Hoymiles/src/inverters/HM_1CH.h index 95874652..1f1804f0 100644 --- a/lib/Hoymiles/src/inverters/HM_1CH.h +++ b/lib/Hoymiles/src/inverters/HM_1CH.h @@ -4,7 +4,7 @@ class HM_1CH : public HM_Abstract { public: - HM_1CH(uint64_t serial); + explicit HM_1CH(uint64_t serial); static bool isValidSerial(uint64_t serial); String typeName(); const byteAssign_t* getByteAssignment(); diff --git a/lib/Hoymiles/src/inverters/HM_2CH.h b/lib/Hoymiles/src/inverters/HM_2CH.h index e0562900..69b9c69c 100644 --- a/lib/Hoymiles/src/inverters/HM_2CH.h +++ b/lib/Hoymiles/src/inverters/HM_2CH.h @@ -4,7 +4,7 @@ class HM_2CH : public HM_Abstract { public: - HM_2CH(uint64_t serial); + explicit HM_2CH(uint64_t serial); static bool isValidSerial(uint64_t serial); String typeName(); const byteAssign_t* getByteAssignment(); diff --git a/lib/Hoymiles/src/inverters/HM_4CH.h b/lib/Hoymiles/src/inverters/HM_4CH.h index 9ae30c09..617c839e 100644 --- a/lib/Hoymiles/src/inverters/HM_4CH.h +++ b/lib/Hoymiles/src/inverters/HM_4CH.h @@ -4,7 +4,7 @@ class HM_4CH : public HM_Abstract { public: - HM_4CH(uint64_t serial); + explicit HM_4CH(uint64_t serial); static bool isValidSerial(uint64_t serial); String typeName(); const byteAssign_t* getByteAssignment(); diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.cpp b/lib/Hoymiles/src/inverters/HM_Abstract.cpp index df492f8d..0668f915 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HM_Abstract.cpp @@ -4,6 +4,7 @@ #include "commands/DevInfoAllCommand.h" #include "commands/DevInfoSampleCommand.h" #include "commands/RealTimeRunDataCommand.h" +#include "commands/SystemConfigParaCommand.h" HM_Abstract::HM_Abstract(uint64_t serial) : InverterAbstract(serial) {}; @@ -68,5 +69,22 @@ bool HM_Abstract::sendDevInfoRequest(HoymilesRadio* radio) cmdSample->setTime(now); cmdSample->setTargetAddress(serial()); + 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(); + cmd->setTime(now); + cmd->setTargetAddress(serial()); + return true; } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.h b/lib/Hoymiles/src/inverters/HM_Abstract.h index bd10be9e..36cf462a 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.h +++ b/lib/Hoymiles/src/inverters/HM_Abstract.h @@ -4,10 +4,11 @@ class HM_Abstract : public InverterAbstract { public: - HM_Abstract(uint64_t serial); + explicit HM_Abstract(uint64_t serial); bool sendStatsRequest(HoymilesRadio* radio); bool sendAlarmLogRequest(HoymilesRadio* radio); bool sendDevInfoRequest(HoymilesRadio* radio); + bool sendSystemConfigParaRequest(HoymilesRadio* radio); private: uint8_t _lastAlarmLogCnt = 0; diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index 4005479a..caa23033 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -8,6 +8,7 @@ InverterAbstract::InverterAbstract(uint64_t serial) _alarmLogParser.reset(new AlarmLogParser()); _devInfoParser.reset(new DevInfoParser()); _statisticsParser.reset(new StatisticsParser()); + _systemConfigParaParser.reset(new SystemConfigParaParser()); } void InverterAbstract::init() @@ -54,6 +55,11 @@ StatisticsParser* InverterAbstract::Statistics() return _statisticsParser.get(); } +SystemConfigParaParser* InverterAbstract::SystemConfigPara() +{ + return _systemConfigParaParser.get(); +} + void InverterAbstract::clearRxFragmentBuffer() { memset(_rxFragmentBuffer, 0, MAX_RF_FRAGMENT_COUNT * sizeof(fragment_t)); diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index e863cf0c..aba60a5b 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -1,8 +1,9 @@ #pragma once #include "../parser/AlarmLogParser.h" -#include "../parser/StatisticsParser.h" #include "../parser/DevInfoParser.h" +#include "../parser/StatisticsParser.h" +#include "../parser/SystemConfigParaParser.h" #include "HoymilesRadio.h" #include "types.h" #include @@ -24,7 +25,7 @@ class CommandAbstract; class InverterAbstract { public: - InverterAbstract(uint64_t serial); + explicit InverterAbstract(uint64_t serial); void init(); uint64_t serial(); void setName(const char* name); @@ -40,14 +41,16 @@ public: virtual bool sendStatsRequest(HoymilesRadio* radio) = 0; virtual bool sendAlarmLogRequest(HoymilesRadio* radio) = 0; virtual bool sendDevInfoRequest(HoymilesRadio* radio) = 0; + virtual bool sendSystemConfigParaRequest(HoymilesRadio* radio) = 0; AlarmLogParser* EventLog(); DevInfoParser* DevInfo(); StatisticsParser* Statistics(); + SystemConfigParaParser* SystemConfigPara(); private: serial_u _serial; - char _name[MAX_NAME_LENGTH]; + char _name[MAX_NAME_LENGTH] = ""; fragment_t _rxFragmentBuffer[MAX_RF_FRAGMENT_COUNT]; uint8_t _rxFragmentMaxPacketId = 0; uint8_t _rxFragmentLastPacketId = 0; @@ -56,4 +59,5 @@ private: std::unique_ptr _alarmLogParser; std::unique_ptr _devInfoParser; std::unique_ptr _statisticsParser; + std::unique_ptr _systemConfigParaParser; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.cpp b/lib/Hoymiles/src/parser/AlarmLogParser.cpp index 66953fd7..9fc56bb8 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.cpp +++ b/lib/Hoymiles/src/parser/AlarmLogParser.cpp @@ -272,5 +272,5 @@ int AlarmLogParser::getTimezoneOffset() ptm->tm_isdst = -1; gmt = mktime(ptm); - return (int)difftime(rawtime, gmt); + return static_cast(difftime(rawtime, gmt)); } \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index 76436be6..d1773c93 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -98,7 +98,7 @@ uint16_t DevInfoParser::getHwVersion() /* struct tm to seconds since Unix epoch */ time_t DevInfoParser::timegm(struct tm* t) { - register long year; + register uint32_t year; register time_t result; #define MONTHSPERYEAR 12 /* months per calendar year */ static const int cumdays[MONTHSPERYEAR] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; diff --git a/lib/Hoymiles/src/parser/DevInfoParser.h b/lib/Hoymiles/src/parser/DevInfoParser.h index f897d35f..9cc878d0 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.h +++ b/lib/Hoymiles/src/parser/DevInfoParser.h @@ -1,3 +1,4 @@ +#pragma once #include "Parser.h" #include @@ -30,9 +31,9 @@ private: uint32_t _lastUpdateAll = 0; uint32_t _lastUpdateSample = 0; - uint8_t _payloadDevInfoAll[DEV_INFO_SIZE]; - uint8_t _devInfoAllLength; + uint8_t _payloadDevInfoAll[DEV_INFO_SIZE] = {}; + uint8_t _devInfoAllLength = 0; - uint8_t _payloadDevInfoSample[DEV_INFO_SIZE]; - uint8_t _devInfoSampleLength; + uint8_t _payloadDevInfoSample[DEV_INFO_SIZE] = {}; + uint8_t _devInfoSampleLength = 0; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 2310a42b..a5dc334d 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -56,7 +56,7 @@ float StatisticsParser::getChannelFieldValue(uint8_t channel, uint8_t fieldId) val |= _payloadStatistic[ptr]; } while (++ptr != end); - return (float)(val) / (float)(div); + return static_cast(val) / static_cast(div); } else { // Value has to be calculated return calcFunctions[b[pos].start].func(this, b[pos].num); diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index 32424a04..5b646247 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -114,8 +114,8 @@ public: void setChannelMaxPower(uint8_t channel, uint16_t power); private: - uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE]; - uint8_t _statisticLength; + uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {}; + uint8_t _statisticLength = 0; uint16_t _chanMaxPower[CH4]; const byteAssign_t* _byteAssignment; diff --git a/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp b/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp new file mode 100644 index 00000000..364fefff --- /dev/null +++ b/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp @@ -0,0 +1,23 @@ +#include "SystemConfigParaParser.h" +#include + +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; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/SystemConfigParaParser.h b/lib/Hoymiles/src/parser/SystemConfigParaParser.h new file mode 100644 index 00000000..2def6c8d --- /dev/null +++ b/lib/Hoymiles/src/parser/SystemConfigParaParser.h @@ -0,0 +1,17 @@ +#pragma once +#include "Parser.h" +#include + +#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; +}; \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 5942243e..8dec6540 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,68 +1,91 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[platformio] -default_envs = generic - -[env] -framework = arduino -platform = espressif32@>=5 -build_flags = - -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 -lib_deps = - https://github.com/me-no-dev/ESPAsyncWebServer.git - bblanchon/ArduinoJson @ ^6.19.4 - https://github.com/bertmelis/espMqttClient.git - nrf24/RF24 @ ^1.4.5 -extra_scripts = pre:auto_firmware_version.py -board_build.partitions = partitions_custom.csv -board_build.filesystem = littlefs -monitor_filters = - time - colorize - log2file - esp32_exception_decoder -monitor_speed = 115200 -upload_protocol = esptool - -[env:generic] -board = esp32dev -monitor_port = COM4 -upload_port = COM4 - -[env:olimex_esp32_poe] -board = esp32-poe -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=5 - -DOPENDTU_ETHERNET -monitor_port = COM3 -upload_port = COM3 - -[env:d1 mini esp32] -board = wemos_d1_mini32 -build_flags = - ${env.build_flags} - -DHOYMILES_PIN_MISO=19 - -DHOYMILES_PIN_MOSI=23 - -DHOYMILES_PIN_SCLK=18 - -DHOYMILES_PIN_IRQ=16 - -DHOYMILES_PIN_CE=17 - -DHOYMILES_PIN_CS=5 - -DVICTRON_PIN_TX=21 - -DVICTRON_PIN_RX=22 -monitor_port = /dev/cu.usbserial-01E68DD0 -upload_port = /dev/cu.usbserial-01E68DD0 +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = generic + +[env] +framework = arduino +platform = espressif32@>=5 + +build_flags = + -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 + +lib_deps = + https://github.com/yubox-node-org/ESPAsyncWebServer + bblanchon/ArduinoJson @ ^6.19.4 + https://github.com/bertmelis/espMqttClient.git#v1.2.3 + nrf24/RF24 @ ^1.4.5 + +extra_scripts = + pre:auto_firmware_version.py + +board_build.partitions = partitions_custom.csv +board_build.filesystem = littlefs +monitor_filters = time, colorize, log2file, esp32_exception_decoder +monitor_speed = 115200 +upload_protocol = esptool + + +[env:generic] +board = esp32dev +monitor_port = COM4 +upload_port = COM4 + + +[env:olimex_esp32_poe] +; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware + +board = esp32-poe +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=5 + -DOPENDTU_ETHERNET + +monitor_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] +board = wemos_d1_mini32 +build_flags = + ${env.build_flags} + -DHOYMILES_PIN_MISO=19 + -DHOYMILES_PIN_MOSI=23 + -DHOYMILES_PIN_SCLK=18 + -DHOYMILES_PIN_IRQ=16 + -DHOYMILES_PIN_CE=17 + -DHOYMILES_PIN_CS=5 + -DVICTRON_PIN_TX=21 + -DVICTRON_PIN_RX=22 +monitor_port = /dev/cu.usbserial-01E68DD0 +upload_port = /dev/cu.usbserial-01E68DD0 diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 003e771a..11287bb8 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -70,7 +70,7 @@ bool ConfigurationClass::write() return false; } config.Cfg_SaveCount++; - uint8_t* bytes = (uint8_t*)&config; + uint8_t* bytes = reinterpret_cast(&config); for (unsigned int i = 0; i < sizeof(CONFIG_T); i++) { f.write(bytes[i]); } @@ -84,7 +84,7 @@ bool ConfigurationClass::read() if (!f) { return false; } - uint8_t* bytes = (uint8_t*)&config; + uint8_t* bytes = reinterpret_cast(&config); for (unsigned int i = 0; i < sizeof(CONFIG_T); i++) { bytes[i] = f.read(); } diff --git a/src/MqttHassPublishing.cpp b/src/MqttHassPublishing.cpp index 99f46871..c18829b0 100644 --- a/src/MqttHassPublishing.cpp +++ b/src/MqttHassPublishing.cpp @@ -46,7 +46,7 @@ void MqttHassPublishingClass::publishConfig() return; } - CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); // Loop all inverters for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { @@ -74,7 +74,7 @@ void MqttHassPublishingClass::publishField(std::shared_ptr inv } 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() & 0xFFFFFFFF))); diff --git a/src/MqttPublishing.cpp b/src/MqttPublishing.cpp index 9108689a..b56a913e 100644 --- a/src/MqttPublishing.cpp +++ b/src/MqttPublishing.cpp @@ -19,18 +19,21 @@ void MqttPublishingClass::loop() return; } - CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) { MqttSettings.publish("dtu/uptime", String(millis() / 1000)); MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString()); + if (NetworkSettings.NetworkMode() == network_mode::WiFi) { + MqttSettings.publish("dtu/rssi", String(WiFi.RSSI())); + } // Loop all inverters for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); 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() & 0xFFFFFFFF))); String subtopic = String(buffer); @@ -58,6 +61,11 @@ void MqttPublishingClass::loop() 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(); if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) { _lastPublishStats[i] = lastUpdate; @@ -94,7 +102,7 @@ String MqttPublishingClass::getTopic(std::shared_ptr inv, uint } 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() & 0xFFFFFFFF))); String invSerial = String(buffer); diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index 049a11f3..e49d4293 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -30,7 +30,7 @@ void MqttSettingsClass::NetworkEvent(network_event event) void MqttSettingsClass::onMqttConnect(bool sessionPresent) { 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); } @@ -68,9 +68,9 @@ void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason re void MqttSettingsClass::performConnect() { if (NetworkSettings.isConnected() && Configuration.get().Mqtt_Enabled) { - using namespace std::placeholders; + using std::placeholders::_1; Serial.println(F("Connecting to MQTT...")); - CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); willTopic = getPrefix() + config.Mqtt_LwtTopic; clientId = NetworkSettings.getApName(); if (config.Mqtt_Tls) { @@ -95,7 +95,7 @@ void MqttSettingsClass::performConnect() void MqttSettingsClass::performDisconnect() { - CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Offline); mqttClient->disconnect(); } @@ -136,7 +136,7 @@ void MqttSettingsClass::publishHass(String subtopic, String payload) void MqttSettingsClass::init() { - using namespace std::placeholders; + using std::placeholders::_1; NetworkSettings.onEvent(std::bind(&MqttSettingsClass::NetworkEvent, this, _1)); createMqttClientObject(); @@ -146,7 +146,7 @@ void MqttSettingsClass::createMqttClientObject() { if (mqttClient != nullptr) delete mqttClient; - CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); if (config.Mqtt_Tls) { mqttClient = static_cast(new espMqttClientSecure); } else { diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 1342c142..98e3a6a6 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -19,7 +19,7 @@ NetworkSettingsClass::NetworkSettingsClass() void NetworkSettingsClass::init() { - using namespace std::placeholders; + using std::placeholders::_1; WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1)); setupMode(); @@ -142,11 +142,7 @@ void NetworkSettingsClass::enableAdminMode() String NetworkSettingsClass::getApName() { - uint32_t chipId = 0; - for (int i = 0; i < 17; i += 8) { - chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; - } - return String(ACCESS_POINT_NAME + String(chipId)); + return String(ACCESS_POINT_NAME + String(getChipId())); } void NetworkSettingsClass::loop() @@ -241,32 +237,37 @@ void NetworkSettingsClass::applyConfig() void NetworkSettingsClass::setHostname() { Serial.print(F("Setting Hostname... ")); - if (strcmp(Configuration.get().WiFi_Hostname, "")) { - if (_networkMode == network_mode::WiFi) { - if (WiFi.hostname(Configuration.get().WiFi_Hostname)) { - Serial.println(F("done")); - } else { - Serial.println(F("failed")); - } + if (_networkMode == network_mode::WiFi) { + if (WiFi.hostname(getHostname())) { + Serial.println(F("done")); + } else { + Serial.println(F("failed")); } -#ifdef OPENDTU_ETHERNET - else if (_networkMode == network_mode::Ethernet) { - if (ETH.setHostname(Configuration.get().WiFi_Hostname)) { - Serial.println(F("done")); - } else { - Serial.println(F("failed")); - } - } -#endif - } else { - Serial.println(F("failed (Hostname empty)")); + + // Evil bad hack to get the hostname set up correctly + WiFi.mode(WIFI_MODE_APSTA); + WiFi.mode(WIFI_MODE_STA); + setupMode(); } +#ifdef OPENDTU_ETHERNET + else if (_networkMode == network_mode::Ethernet) { + if (ETH.setHostname(getHostname().c_str())) { + Serial.println(F("done")); + } else { + Serial.println(F("failed")); + } + } +#endif } void NetworkSettingsClass::setStaticIp() { 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... ")); WiFi.config( IPAddress(Configuration.get().WiFi_Ip), @@ -280,7 +281,9 @@ void NetworkSettingsClass::setStaticIp() #ifdef OPENDTU_ETHERNET else if (_networkMode == network_mode::Ethernet) { if (Configuration.get().WiFi_Dhcp) { + Serial.print(F("Configuring Ethernet DHCP IP... ")); ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); + Serial.println(F("done")); } else { Serial.print(F("Configuring Ethernet static IP... ")); ETH.config( @@ -375,14 +378,43 @@ String NetworkSettingsClass::macAddress() } } -const char* NetworkSettingsClass::getHostname() +String NetworkSettingsClass::getHostname() { -#ifdef OPENDTU_ETHERNET - if (_networkMode == network_mode::Ethernet) { - return ETH.getHostname(); + const CONFIG_T& config = Configuration.get(); + char preparedHostname[WIFI_MAX_HOSTNAME_STRLEN + 1]; + 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++; + } + // else do nothing - no leading hyphens and do not include hyphens for all other characters. + pC++; } -#endif - return WiFi.getHostname(); + + 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() @@ -399,4 +431,13 @@ network_mode NetworkSettingsClass::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; \ No newline at end of file diff --git a/src/WebApi.cpp b/src/WebApi.cpp index 9d5f32ba..e0df086d 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -15,15 +15,15 @@ WebApiClass::WebApiClass() void WebApiClass::init() { - using namespace std::placeholders; - _server.addHandler(&_events); + _webApiConfig.init(&_server); _webApiDevInfo.init(&_server); _webApiDtu.init(&_server); _webApiEventlog.init(&_server); _webApiFirmware.init(&_server); _webApiInverter.init(&_server); + _webApiLimit.init(&_server); _webApiMqtt.init(&_server); _webApiNetwork.init(&_server); _webApiNtp.init(&_server); @@ -38,11 +38,13 @@ void WebApiClass::init() void WebApiClass::loop() { + _webApiConfig.loop(); _webApiDevInfo.loop(); _webApiDtu.loop(); _webApiEventlog.loop(); _webApiFirmware.loop(); _webApiInverter.loop(); + _webApiLimit.loop(); _webApiMqtt.loop(); _webApiNetwork.loop(); _webApiNtp.loop(); diff --git a/src/WebApi_config.cpp b/src/WebApi_config.cpp new file mode 100644 index 00000000..7bda7133 --- /dev/null +++ b/src/WebApi_config.cpp @@ -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 + +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() == 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(); + } +} \ No newline at end of file diff --git a/src/WebApi_devinfo.cpp b/src/WebApi_devinfo.cpp index 8022ecb7..e4a75a2e 100644 --- a/src/WebApi_devinfo.cpp +++ b/src/WebApi_devinfo.cpp @@ -10,7 +10,7 @@ void WebApiDevInfoClass::init(AsyncWebServer* server) { - using namespace std::placeholders; + using std::placeholders::_1; _server = server; @@ -31,7 +31,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request) // Inverter Serial is read as HEX 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() & 0xFFFFFFFF))); diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index 743bb92e..65c68580 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.cpp @@ -10,7 +10,7 @@ void WebApiDtuClass::init(AsyncWebServer* server) { - using namespace std::placeholders; + using std::placeholders::_1; _server = server; @@ -26,11 +26,11 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(); JsonObject root = response->getRoot(); - CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); // DTU Serial is read as HEX 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 & 0xFFFFFFFF))); root[F("dtu_serial")] = buffer; @@ -102,9 +102,9 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - char* t; + // Interpret the string as a hex value and convert it to uint64_t - config.Dtu_Serial = strtoll(root[F("dtu_serial")].as().c_str(), &t, 16); + config.Dtu_Serial = strtoll(root[F("dtu_serial")].as().c_str(), NULL, 16); config.Dtu_PollInterval = root[F("dtu_pollinterval")].as(); config.Dtu_PaLevel = root[F("dtu_palevel")].as(); Configuration.write(); diff --git a/src/WebApi_eventlog.cpp b/src/WebApi_eventlog.cpp index af7637e1..b73b72ed 100644 --- a/src/WebApi_eventlog.cpp +++ b/src/WebApi_eventlog.cpp @@ -9,7 +9,7 @@ void WebApiEventlogClass::init(AsyncWebServer* server) { - using namespace std::placeholders; + using std::placeholders::_1; _server = server; @@ -22,15 +22,21 @@ void WebApiEventlogClass::loop() void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(); + AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048); JsonObject root = response->getRoot(); - for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { - auto inv = Hoymiles.getInverterByPos(i); + uint64_t serial = 0; + 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 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() & 0xFFFFFFFF))); diff --git a/src/WebApi_firmware.cpp b/src/WebApi_firmware.cpp index 2f07341b..9b33dc77 100644 --- a/src/WebApi_firmware.cpp +++ b/src/WebApi_firmware.cpp @@ -11,7 +11,12 @@ 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; diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index db2d0344..9759e644 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -12,7 +12,7 @@ void WebApiInverterClass::init(AsyncWebServer* server) { - using namespace std::placeholders; + using std::placeholders::_1; _server = server; @@ -32,7 +32,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); 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++) { if (config.Inverter[i].Serial > 0) { @@ -42,7 +42,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) // Inverter Serial is read as HEX 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 & 0xFFFFFFFF))); obj[F("serial")] = buffer; @@ -126,9 +126,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) return; } - char* t; // Interpret the string as a hex value and convert it to uint64_t - inverter->Serial = strtoll(root[F("serial")].as().c_str(), &t, 16); + inverter->Serial = strtoll(root[F("serial")].as().c_str(), NULL, 16); strncpy(inverter->Name, root[F("name")].as().c_str(), INV_MAX_NAME_STRLEN); Configuration.write(); @@ -224,8 +223,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root[F("id")].as()]; - char* t; - uint64_t new_serial = strtoll(root[F("serial")].as().c_str(), &t, 16); + uint64_t new_serial = strtoll(root[F("serial")].as().c_str(), NULL, 16); uint64_t old_serial = inverter.Serial; // Interpret the string as a hex value and convert it to uint64_t diff --git a/src/WebApi_limit.cpp b/src/WebApi_limit.cpp new file mode 100644 index 00000000..8d4de0ed --- /dev/null +++ b/src/WebApi_limit.cpp @@ -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); +} \ No newline at end of file diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index 8fa36d9f..33db93c3 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.cpp @@ -12,7 +12,7 @@ void WebApiMqttClass::init(AsyncWebServer* server) { - using namespace std::placeholders; + using std::placeholders::_1; _server = server; @@ -29,7 +29,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); 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_hostname")] = config.Mqtt_Hostname; @@ -56,7 +56,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); 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_hostname")] = config.Mqtt_Hostname; @@ -240,21 +240,21 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) config.Mqtt_Enabled = root[F("mqtt_enabled")].as(); config.Mqtt_Retain = root[F("mqtt_retain")].as(); config.Mqtt_Tls = root[F("mqtt_tls")].as(); - strcpy(config.Mqtt_RootCaCert, root[F("mqtt_root_ca_cert")].as().c_str()); + strlcpy(config.Mqtt_RootCaCert, root[F("mqtt_root_ca_cert")].as().c_str(), sizeof(config.Mqtt_RootCaCert)); config.Mqtt_Port = root[F("mqtt_port")].as(); - strcpy(config.Mqtt_Hostname, root[F("mqtt_hostname")].as().c_str()); - strcpy(config.Mqtt_Username, root[F("mqtt_username")].as().c_str()); - strcpy(config.Mqtt_Password, root[F("mqtt_password")].as().c_str()); - strcpy(config.Mqtt_Topic, root[F("mqtt_topic")].as().c_str()); - strcpy(config.Mqtt_LwtTopic, root[F("mqtt_lwt_topic")].as().c_str()); - strcpy(config.Mqtt_LwtValue_Online, root[F("mqtt_lwt_online")].as().c_str()); - strcpy(config.Mqtt_LwtValue_Offline, root[F("mqtt_lwt_offline")].as().c_str()); + strlcpy(config.Mqtt_Hostname, root[F("mqtt_hostname")].as().c_str(), sizeof(config.Mqtt_Hostname)); + strlcpy(config.Mqtt_Username, root[F("mqtt_username")].as().c_str(), sizeof(config.Mqtt_Username)); + strlcpy(config.Mqtt_Password, root[F("mqtt_password")].as().c_str(), sizeof(config.Mqtt_Password)); + strlcpy(config.Mqtt_Topic, root[F("mqtt_topic")].as().c_str(), sizeof(config.Mqtt_Topic)); + strlcpy(config.Mqtt_LwtTopic, root[F("mqtt_lwt_topic")].as().c_str(), sizeof(config.Mqtt_LwtTopic)); + strlcpy(config.Mqtt_LwtValue_Online, root[F("mqtt_lwt_online")].as().c_str(), sizeof(config.Mqtt_LwtValue_Online)); + strlcpy(config.Mqtt_LwtValue_Offline, root[F("mqtt_lwt_offline")].as().c_str(), sizeof(config.Mqtt_LwtValue_Offline)); config.Mqtt_PublishInterval = root[F("mqtt_publish_interval")].as(); config.Mqtt_Hass_Enabled = root[F("mqtt_hass_enabled")].as(); config.Mqtt_Hass_Expire = root[F("mqtt_hass_expire")].as(); config.Mqtt_Hass_Retain = root[F("mqtt_hass_retain")].as(); config.Mqtt_Hass_IndividualPanels = root[F("mqtt_hass_individualpanels")].as(); - strcpy(config.Mqtt_Hass_Topic, root[F("mqtt_hass_topic")].as().c_str()); + strlcpy(config.Mqtt_Hass_Topic, root[F("mqtt_hass_topic")].as().c_str(), sizeof(config.Mqtt_Hass_Topic)); Configuration.write(); retMsg[F("type")] = F("success"); @@ -267,18 +267,18 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) MqttHassPublishing.forceUpdate(); } -String WebApiMqttClass::getRootCaCertInfo(char* cert) +String WebApiMqttClass::getRootCaCertInfo(const char* cert) { char rootCaCertInfo[1024] = ""; 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); int ret = mbedtls_x509_crt_parse(&global_cacert, const_cast((unsigned char*)cert), 1 + strlen(cert)); 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); return ""; } diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp index 3254c4b4..7c950604 100644 --- a/src/WebApi_network.cpp +++ b/src/WebApi_network.cpp @@ -11,7 +11,7 @@ void WebApiNetworkClass::init(AsyncWebServer* server) { - using namespace std::placeholders; + using std::placeholders::_1; _server = server; @@ -32,6 +32,7 @@ void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request) root[F("sta_status")] = ((WiFi.getMode() & WIFI_STA) != 0); root[F("sta_ssid")] = WiFi.SSID(); root[F("sta_rssi")] = WiFi.RSSI(); + root[F("network_hostname")] = NetworkSettings.getHostname(); root[F("network_ip")] = NetworkSettings.localIP().toString(); root[F("network_netmask")] = NetworkSettings.subnetMask().toString(); root[F("network_gateway")] = NetworkSettings.gatewayIP().toString(); @@ -53,7 +54,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(); JsonObject root = response->getRoot(); - CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); root[F("hostname")] = config.WiFi_Hostname; root[F("dhcp")] = config.WiFi_Dhcp; @@ -184,9 +185,9 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) config.WiFi_Dns2[1] = dns2[1]; config.WiFi_Dns2[2] = dns2[2]; config.WiFi_Dns2[3] = dns2[3]; - strcpy(config.WiFi_Ssid, root[F("ssid")].as().c_str()); - strcpy(config.WiFi_Password, root[F("password")].as().c_str()); - strcpy(config.WiFi_Hostname, root[F("hostname")].as().c_str()); + strlcpy(config.WiFi_Ssid, root[F("ssid")].as().c_str(), sizeof(config.WiFi_Ssid)); + strlcpy(config.WiFi_Password, root[F("password")].as().c_str(), sizeof(config.WiFi_Password)); + strlcpy(config.WiFi_Hostname, root[F("hostname")].as().c_str(), sizeof(config.WiFi_Hostname)); if (root[F("dhcp")].as()) { config.WiFi_Dhcp = true; } else { diff --git a/src/WebApi_ntp.cpp b/src/WebApi_ntp.cpp index 0f230822..49451489 100644 --- a/src/WebApi_ntp.cpp +++ b/src/WebApi_ntp.cpp @@ -11,7 +11,7 @@ void WebApiNtpClass::init(AsyncWebServer* server) { - using namespace std::placeholders; + using std::placeholders::_1; _server = server; @@ -28,7 +28,7 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(); 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_timezone")] = config.Ntp_Timezone; @@ -52,7 +52,7 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(); 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_timezone")] = config.Ntp_Timezone; @@ -123,9 +123,9 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - strcpy(config.Ntp_Server, root[F("ntp_server")].as().c_str()); - strcpy(config.Ntp_Timezone, root[F("ntp_timezone")].as().c_str()); - strcpy(config.Ntp_TimezoneDescr, root[F("ntp_timezone_descr")].as().c_str()); + strlcpy(config.Ntp_Server, root[F("ntp_server")].as().c_str(), sizeof(config.Ntp_Server)); + strlcpy(config.Ntp_Timezone, root[F("ntp_timezone")].as().c_str(), sizeof(config.Ntp_Timezone)); + strlcpy(config.Ntp_TimezoneDescr, root[F("ntp_timezone_descr")].as().c_str(), sizeof(config.Ntp_TimezoneDescr)); Configuration.write(); retMsg[F("type")] = F("success"); diff --git a/src/WebApi_sysstatus.cpp b/src/WebApi_sysstatus.cpp index 61acaf93..13864348 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.cpp @@ -16,7 +16,7 @@ void WebApiSysstatusClass::init(AsyncWebServer* server) { - using namespace std::placeholders; + using std::placeholders::_1; _server = server; @@ -58,7 +58,7 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request) root[F("cfgsavecount")] = Configuration.get().Cfg_SaveCount; 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("git_hash")] = AUTO_GIT_HASH; diff --git a/src/WebApi_webapp.cpp b/src/WebApi_webapp.cpp index 9580f833..4976f78e 100644 --- a/src/WebApi_webapp.cpp +++ b/src/WebApi_webapp.cpp @@ -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) { - using namespace std::placeholders; - _server = server; _server->on("/", HTTP_GET, [](AsyncWebServerRequest* request) { diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 2c38b87c..e8848ae2 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -13,7 +13,12 @@ WebApiWsLiveClass::WebApiWsLiveClass() 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->on("/api/livedata/status", HTTP_GET, std::bind(&WebApiWsLiveClass::onLivedataStatus, this, _1)); @@ -56,10 +61,9 @@ void WebApiWsLiveClass::loop() JsonVariant var = root; generateJsonResponse(var); - size_t len = measureJson(root); - AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len); // creates a buffer (len + 1) for you. + String buffer; if (buffer) { - serializeJson(root, (char*)buffer->get(), len + 1); + serializeJson(root, buffer); _ws.textAll(buffer); } @@ -74,7 +78,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root) auto inv = Hoymiles.getInverterByPos(i); 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() & 0xFFFFFFFF))); @@ -135,11 +139,11 @@ void WebApiWsLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketC { if (type == WS_EVT_CONNECT) { 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); } else if (type == WS_EVT_DISCONNECT) { 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); } } diff --git a/src/main.cpp b/src/main.cpp index 83efcccb..de13940a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -81,7 +81,7 @@ void setup() // Initialize inverter communication Serial.print(F("Initialize Hoymiles interface... ")); - CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); Hoymiles.init(); Serial.println(F(" Setting radio PA level... ")); diff --git a/webapp/package.json b/webapp/package.json index 3fae0df6..b86a43c7 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -9,30 +9,30 @@ }, "dependencies": { "@popperjs/core": "^2.11.6", - "bootstrap": "^5.2.0", + "bootstrap": "^5.2.1", "bootstrap-icons-vue": "^1.8.1", - "core-js": "^3.25.0", + "core-js": "^3.25.1", "spark-md5": "^3.0.2", - "vue": "^3.2.38", + "vue": "^3.2.39", "vue-class-component": "^8.0.0-0", "vue-router": "^4.1.5" }, "devDependencies": { - "@babel/core": "^7.18.13", + "@babel/core": "^7.19.0", "@babel/eslint-parser": "^7.18.9", - "@types/bootstrap": "^5.2.2", - "@types/node": "^18.7.15", + "@types/bootstrap": "^5.2.4", + "@types/node": "^18.7.16", "@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-eslint": "~5.0.8", "@vue/cli-plugin-router": "^5.0.6", "@vue/cli-plugin-typescript": "^5.0.8", "@vue/cli-service": "~5.0.8", - "@vue/eslint-config-typescript": "^11.0.0", - "eslint": "^8.23.0", + "@vue/eslint-config-typescript": "^11.0.1", + "eslint": "^8.23.1", "eslint-plugin-vue": "^9.4.0", - "typescript": "^4.8.2", + "typescript": "^4.8.3", "vue-cli-plugin-compression": "~2.0.0" }, "eslintConfig": { diff --git a/webapp/src/components/AboutView.vue b/webapp/src/components/AboutView.vue index 6c2824db..7196cf52 100644 --- a/webapp/src/components/AboutView.vue +++ b/webapp/src/components/AboutView.vue @@ -1,10 +1,101 @@ \ No newline at end of file diff --git a/webapp/src/components/ConfigAdminView.vue b/webapp/src/components/ConfigAdminView.vue new file mode 100644 index 00000000..04d15caa --- /dev/null +++ b/webapp/src/components/ConfigAdminView.vue @@ -0,0 +1,219 @@ + + + \ No newline at end of file diff --git a/webapp/src/components/HomeView.vue b/webapp/src/components/HomeView.vue index 657db6fb..ad627a6e 100644 --- a/webapp/src/components/HomeView.vue +++ b/webapp/src/components/HomeView.vue @@ -39,6 +39,14 @@ {{ inverter.data_age }} seconds) + @@ -139,6 +171,7 @@ import InverterChannelInfo from "@/components/partials/InverterChannelInfo.vue"; import * as bootstrap from 'bootstrap'; import EventLog from '@/components/partials/EventLog.vue'; import DevInfo from '@/components/partials/DevInfo.vue'; +import LimitSettingsCurrent from '@/components/partials/LimitSettingsCurrent.vue'; import VedirectView from '@/components/partials/VedirectView.vue'; declare interface Inverter { @@ -154,6 +187,7 @@ export default defineComponent({ InverterChannelInfo, EventLog, DevInfo, + LimitSettingsCurrent, VedirectView }, data() { @@ -169,7 +203,10 @@ export default defineComponent({ eventLogLoading: true, devInfoView: {} as bootstrap.Modal, devInfoList: {}, - devInfoLoading: true + devInfoLoading: true, + limitSettingView: {} as bootstrap.Modal, + limitSettingList: {}, + limitSettingLoading: true, }; }, created() { @@ -180,6 +217,7 @@ export default defineComponent({ mounted() { this.eventLogView = new bootstrap.Modal('#eventView'); this.devInfoView = new bootstrap.Modal('#devInfoView'); + this.limitSettingView = new bootstrap.Modal('#limitSettingView'); }, unmounted() { this.closeSocket(); @@ -264,7 +302,7 @@ export default defineComponent({ }, onShowEventlog(serial: number) { this.eventLogLoading = true; - fetch("/api/eventlog/status") + fetch("/api/eventlog/status?inv=" + serial) .then((response) => response.json()) .then((data) => { this.eventLogList = data[serial]; @@ -287,6 +325,20 @@ export default defineComponent({ 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(); + }, }, }); \ No newline at end of file diff --git a/webapp/src/components/NavBar.vue b/webapp/src/components/NavBar.vue index ea945c9c..c476be7a 100644 --- a/webapp/src/components/NavBar.vue +++ b/webapp/src/components/NavBar.vue @@ -39,6 +39,9 @@
  • +
  • + Config Management +
  • Firmware Upgrade
  • diff --git a/webapp/src/components/NetworkAdminView.vue b/webapp/src/components/NetworkAdminView.vue index 738b006b..a4891b5f 100644 --- a/webapp/src/components/NetworkAdminView.vue +++ b/webapp/src/components/NetworkAdminView.vue @@ -39,6 +39,11 @@
    + +
    diff --git a/webapp/src/components/NetworkInfoView.vue b/webapp/src/components/NetworkInfoView.vue index 3f424628..4417df65 100644 --- a/webapp/src/components/NetworkInfoView.vue +++ b/webapp/src/components/NetworkInfoView.vue @@ -49,6 +49,7 @@ export default defineComponent({ ap_ssid: "", ap_stationnum: 0, // InterfaceNetworkInfo + network_hostname: "", network_ip: "", network_netmask: "", network_gateway: "", diff --git a/webapp/src/components/partials/InterfaceNetworkInfo.vue b/webapp/src/components/partials/InterfaceNetworkInfo.vue index dfb6aa70..c640da57 100644 --- a/webapp/src/components/partials/InterfaceNetworkInfo.vue +++ b/webapp/src/components/partials/InterfaceNetworkInfo.vue @@ -7,6 +7,10 @@
    + + + + @@ -43,6 +47,7 @@ import { defineComponent } from 'vue'; export default defineComponent({ props: { + network_hostname: String, network_ip: String, network_netmask: String, network_gateway: String, diff --git a/webapp/src/components/partials/LimitSettingsCurrent.vue b/webapp/src/components/partials/LimitSettingsCurrent.vue new file mode 100644 index 00000000..fd9c99cd --- /dev/null +++ b/webapp/src/components/partials/LimitSettingsCurrent.vue @@ -0,0 +1,33 @@ + + + \ No newline at end of file diff --git a/webapp/src/router/index.ts b/webapp/src/router/index.ts index f91efcc7..bbc47e9e 100644 --- a/webapp/src/router/index.ts +++ b/webapp/src/router/index.ts @@ -11,6 +11,7 @@ import MqttInfoView from '@/components/MqttInfoView.vue' import InverterAdminView from '@/components/InverterAdminView.vue' import DtuAdminView from '@/components/DtuAdminView.vue' import FirmwareUpgradeView from '@/components/FirmwareUpgradeView.vue' +import ConfigAdminView from '@/components/ConfigAdminView.vue' import VedirectAdminView from '@/components/VedirectAdminView.vue' import VedirectInfoView from '@/components/VedirectInfoView.vue' @@ -84,6 +85,11 @@ const routes: Array = [ path: '/firmware/upgrade', name: 'Firmware Upgrade', component: FirmwareUpgradeView + }, + { + path: '/settings/config', + name: 'Config Management', + component: ConfigAdminView } ]; diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 4a1fd1cf..4758030d 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -36,6 +36,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" 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": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8" @@ -57,21 +62,21 @@ json5 "^2.2.1" semver "^6.3.0" -"@babel/core@^7.18.13": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.13.tgz#9be8c44512751b05094a4d3ab05fc53a47ce00ac" - integrity sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A== +"@babel/core@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" + integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.13" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-module-transforms" "^7.18.9" - "@babel/helpers" "^7.18.9" - "@babel/parser" "^7.18.13" + "@babel/generator" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.0" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.18.13" - "@babel/types" "^7.18.13" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -96,12 +101,12 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.18.13": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.13.tgz#59550cbb9ae79b8def15587bdfbaa388c4abf212" - integrity sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ== +"@babel/generator@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" + integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== dependencies: - "@babel/types" "^7.18.13" + "@babel/types" "^7.19.0" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -130,6 +135,16 @@ browserslist "^4.20.2" 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": 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" @@ -198,6 +213,14 @@ "@babel/template" "^7.18.6" "@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": version "7.16.7" 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/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": version "7.16.7" 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/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": version "7.18.6" 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" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/parser@^7.18.13": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4" - integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg== +"@babel/parser@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" + integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.17.12": version "7.17.12" @@ -1040,19 +1086,19 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.13": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.13.tgz#5ab59ef51a997b3f10c4587d648b9696b6cb1a68" - integrity sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA== +"@babel/traverse@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" + integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== dependencies: "@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-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.18.13" - "@babel/types" "^7.18.13" + "@babel/parser" "^7.19.0" + "@babel/types" "^7.19.0" debug "^4.1.0" globals "^11.1.0" @@ -1065,19 +1111,19 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.18.13": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a" - integrity sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ== +"@babel/types@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== dependencies: "@babel/helper-string-parser" "^7.18.10" "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@eslint/eslintrc@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.1.tgz#de0807bfeffc37b964a7d0400e0c348ce5a2543d" - integrity sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ== +"@eslint/eslintrc@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.2.tgz#58b69582f3b7271d8fa67fe5251767a5b38ea356" + integrity sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -1270,10 +1316,10 @@ dependencies: "@types/node" "*" -"@types/bootstrap@^5.2.2": - version "5.2.3" - resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.3.tgz#562defe61dac6c9598843d4088de8007a36607bd" - integrity sha512-r2SE9NYaaI7B/jJk8gqRtXzlhgFL6dlXBResJkCbQa8ept619WeiOIO4zBQxdmUFzkKNWLK5ZOyYGI3QZoaqbQ== +"@types/bootstrap@^5.2.4": + version "5.2.4" + resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.4.tgz#7f4f4af8e22af8247385549bd2f687088d00d2d3" + integrity sha512-jGNB81zuDHu1DPvBV7Ox3Z3eyzdWPNguYwrt0j7X90VExA8H7c6qxJh0cz5j3xp0XvSy1TYaP2pkyXCHeo8CaA== dependencies: "@popperjs/core" "^2.9.2" @@ -1372,10 +1418,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.4.tgz#fd26723a8a3f8f46729812a7f9b4fc2d1608ed39" integrity sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg== -"@types/node@^18.7.15": - version "18.7.15" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.15.tgz#20ae1ec80c57ee844b469f968a1cd511d4088b29" - integrity sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ== +"@types/node@^18.7.16": + version "18.7.16" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.16.tgz#0eb3cce1e37c79619943d2fd903919fc30850601" + integrity sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -1466,14 +1512,14 @@ "@typescript-eslint/typescript-estree" "5.32.0" debug "^4.3.4" -"@typescript-eslint/parser@^5.36.1": - version "5.36.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.1.tgz#931c22c7bacefd17e29734628cdec8b2acdcf1ce" - integrity sha512-/IsgNGOkBi7CuDfUbwt1eOqUXF9WGVBW9dwEe1pi+L32XrTsZIgmDFIi2RxjzsvB/8i+MIf5JIoTEH8LOZ368A== +"@typescript-eslint/parser@^5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.2.tgz#3ddf323d3ac85a25295a55fcb9c7a49ab4680ddd" + integrity sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA== dependencies: - "@typescript-eslint/scope-manager" "5.36.1" - "@typescript-eslint/types" "5.36.1" - "@typescript-eslint/typescript-estree" "5.36.1" + "@typescript-eslint/scope-manager" "5.36.2" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/typescript-estree" "5.36.2" debug "^4.3.4" "@typescript-eslint/scope-manager@5.29.0": @@ -1492,13 +1538,13 @@ "@typescript-eslint/types" "5.32.0" "@typescript-eslint/visitor-keys" "5.32.0" -"@typescript-eslint/scope-manager@5.36.1": - version "5.36.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.1.tgz#23c49b7ddbcffbe09082e6694c2524950766513f" - integrity sha512-pGC2SH3/tXdu9IH3ItoqciD3f3RRGCh7hb9zPdN2Drsr341zgd6VbhP5OHQO/reUqihNltfPpMpTNihFMarP2w== +"@typescript-eslint/scope-manager@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz#a75eb588a3879ae659514780831370642505d1cd" + integrity sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw== dependencies: - "@typescript-eslint/types" "5.36.1" - "@typescript-eslint/visitor-keys" "5.36.1" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/visitor-keys" "5.36.2" "@typescript-eslint/type-utils@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" integrity sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ== -"@typescript-eslint/types@5.36.1": - version "5.36.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.1.tgz#1cf0e28aed1cb3ee676917966eb23c2f8334ce2c" - integrity sha512-jd93ShpsIk1KgBTx9E+hCSEuLCUFwi9V/urhjOWnOaksGZFbTOxAT47OH2d4NLJnLhkVD+wDbB48BuaycZPLBg== +"@typescript-eslint/types@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.2.tgz#a5066e500ebcfcee36694186ccc57b955c05faf9" + integrity sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ== "@typescript-eslint/typescript-estree@5.29.0": version "5.29.0" @@ -1550,13 +1596,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.36.1": - version "5.36.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.1.tgz#b857f38d6200f7f3f4c65cd0a5afd5ae723f2adb" - integrity sha512-ih7V52zvHdiX6WcPjsOdmADhYMDN15SylWRZrT2OMy80wzKbc79n8wFW0xpWpU0x3VpBz/oDgTm2xwDAnFTl+g== +"@typescript-eslint/typescript-estree@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz#0c93418b36c53ba0bc34c61fe9405c4d1d8fe560" + integrity sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w== dependencies: - "@typescript-eslint/types" "5.36.1" - "@typescript-eslint/visitor-keys" "5.36.1" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/visitor-keys" "5.36.2" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -1591,12 +1637,12 @@ "@typescript-eslint/types" "5.32.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@5.36.1": - version "5.36.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.1.tgz#7731175312d65738e501780f923896d200ad1615" - integrity sha512-ojB9aRyRFzVMN3b5joSYni6FAS10BBSCAfKJhjJAV08t/a95aM6tAhz+O1jF+EtgxktuSO3wJysp2R+Def/IWQ== +"@typescript-eslint/visitor-keys@5.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz#2f8f78da0a3bad3320d2ac24965791ac39dace5a" + integrity sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A== dependencies: - "@typescript-eslint/types" "5.36.1" + "@typescript-eslint/types" "5.36.2" eslint-visitor-keys "^3.3.0" "@vue/babel-helper-vue-jsx-merge-props@^1.2.1": @@ -1855,47 +1901,47 @@ semver "^7.3.4" strip-ansi "^6.0.0" -"@vue/compiler-core@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.38.tgz#0a2a7bffd2280ac19a96baf5301838a2cf1964d7" - integrity sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q== +"@vue/compiler-core@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.39.tgz#0d77e635f4bdb918326669155a2dc977c053943e" + integrity sha512-mf/36OWXqWn0wsC40nwRRGheR/qoID+lZXbIuLnr4/AngM0ov8Xvv8GHunC0rKRIkh60bTqydlqTeBo49rlbqw== dependencies: "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.38" + "@vue/shared" "3.2.39" estree-walker "^2.0.2" source-map "^0.6.1" -"@vue/compiler-dom@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz#53d04ed0c0c62d1ef259bf82f9b28100a880b6fd" - integrity sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g== +"@vue/compiler-dom@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.39.tgz#bd69d35c1a48fe2cea4ab9e96d2a3a735d146fdf" + integrity sha512-HMFI25Be1C8vLEEv1hgEO1dWwG9QQ8LTTPmCkblVJY/O3OvWx6r1+zsox5mKPMGvqYEZa6l8j+xgOfUspgo7hw== dependencies: - "@vue/compiler-core" "3.2.38" - "@vue/shared" "3.2.38" + "@vue/compiler-core" "3.2.39" + "@vue/shared" "3.2.39" -"@vue/compiler-sfc@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz#9e763019471a535eb1fceeaac9d4d18a83f0940f" - integrity sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg== +"@vue/compiler-sfc@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.39.tgz#8fe29990f672805b7c5a2ecfa5b05e681c862ea2" + integrity sha512-fqAQgFs1/BxTUZkd0Vakn3teKUt//J3c420BgnYgEOoVdTwYpBTSXCMJ88GOBCylmUBbtquGPli9tVs7LzsWIA== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.38" - "@vue/compiler-dom" "3.2.38" - "@vue/compiler-ssr" "3.2.38" - "@vue/reactivity-transform" "3.2.38" - "@vue/shared" "3.2.38" + "@vue/compiler-core" "3.2.39" + "@vue/compiler-dom" "3.2.39" + "@vue/compiler-ssr" "3.2.39" + "@vue/reactivity-transform" "3.2.39" + "@vue/shared" "3.2.39" estree-walker "^2.0.2" magic-string "^0.25.7" postcss "^8.1.10" source-map "^0.6.1" -"@vue/compiler-ssr@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz#933b23bf99e667e5078eefc6ba94cb95fd765dfe" - integrity sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ== +"@vue/compiler-ssr@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.39.tgz#4f3bfb535cb98b764bee45e078700e03ccc60633" + integrity sha512-EoGCJ6lincKOZGW+0Ky4WOKsSmqL7hp1ZYgen8M7u/mlvvEQUaO9tKKOy7K43M9U2aA3tPv0TuYYQFrEbK2eFQ== dependencies: - "@vue/compiler-dom" "3.2.38" - "@vue/shared" "3.2.38" + "@vue/compiler-dom" "3.2.39" + "@vue/shared" "3.2.39" "@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^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" integrity sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ== -"@vue/eslint-config-typescript@^11.0.0": - version "11.0.0" - resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.0.tgz#bac0cb2d381625b5bf568d2025acffc0fd09113e" - integrity sha512-txuRzxnQVmtUvvy9UyWUy9sHWXNeRPGmSPqP53hRtaiUeCTAondI9Ho9GQYI/8/eWljYOST7iA4Aa8sANBkWaA== +"@vue/eslint-config-typescript@^11.0.1": + version "11.0.1" + resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.1.tgz#d79b3656aecea844ec9875bc93155163f684dde7" + integrity sha512-0U+nL0nA7ahnGPk3rTN49x76miUwuQtQPQNWOFvAcjg6nFJkIkA8qbGNtXwsuHtwBwRtWpHhShL3zK07v+632w== dependencies: "@typescript-eslint/eslint-plugin" "^5.0.0" "@typescript-eslint/parser" "^5.0.0" vue-eslint-parser "^9.0.0" -"@vue/reactivity-transform@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz#a856c217b2ead99eefb6fddb1d61119b2cb67984" - integrity sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA== +"@vue/reactivity-transform@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.39.tgz#da6ae6c8fd77791b9ae21976720d116591e1c4aa" + integrity sha512-HGuWu864zStiWs9wBC6JYOP1E00UjMdDWIG5W+FpUx28hV3uz9ODOKVNm/vdOy/Pvzg8+OcANxAVC85WFBbl3A== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.38" - "@vue/shared" "3.2.38" + "@vue/compiler-core" "3.2.39" + "@vue/shared" "3.2.39" estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.38.tgz#d576fdcea98eefb96a1f1ad456e289263e87292e" - integrity sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw== +"@vue/reactivity@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.39.tgz#e6e3615fe2288d4232b104640ddabd0729a78c80" + integrity sha512-vlaYX2a3qMhIZfrw3Mtfd+BuU+TZmvDrPMa+6lpfzS9k/LnGxkSuf0fhkP0rMGfiOHPtyKoU9OJJJFGm92beVQ== dependencies: - "@vue/shared" "3.2.38" + "@vue/shared" "3.2.39" -"@vue/runtime-core@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.38.tgz#d19cf591c210713f80e6a94ffbfef307c27aea06" - integrity sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg== +"@vue/runtime-core@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.39.tgz#dc1faccab11b3e81197aba33fb30c9447c1d2c84" + integrity sha512-xKH5XP57JW5JW+8ZG1khBbuLakINTgPuINKL01hStWLTTGFOrM49UfCFXBcFvWmSbci3gmJyLl2EAzCaZWsx8g== dependencies: - "@vue/reactivity" "3.2.38" - "@vue/shared" "3.2.38" + "@vue/reactivity" "3.2.39" + "@vue/shared" "3.2.39" -"@vue/runtime-dom@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.38.tgz#fec711f65c2485991289fd4798780aa506469b48" - integrity sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A== +"@vue/runtime-dom@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.39.tgz#4a8cb132bcef316e8151c5ed07fc7272eb064614" + integrity sha512-4G9AEJP+sLhsqf5wXcyKVWQKUhI+iWfy0hWQgea+CpaTD7BR0KdQzvoQdZhwCY6B3oleSyNLkLAQwm0ya/wNoA== dependencies: - "@vue/runtime-core" "3.2.38" - "@vue/shared" "3.2.38" + "@vue/runtime-core" "3.2.39" + "@vue/shared" "3.2.39" csstype "^2.6.8" -"@vue/server-renderer@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.38.tgz#01a4c0f218e90b8ad1815074208a1974ded109aa" - integrity sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA== +"@vue/server-renderer@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.39.tgz#4358292d925233b0d8b54cf0513eaece8b2351c5" + integrity sha512-1yn9u2YBQWIgytFMjz4f/t0j43awKytTGVptfd3FtBk76t1pd8mxbek0G/DrnjJhd2V7mSTb5qgnxMYt8Z5iSQ== dependencies: - "@vue/compiler-ssr" "3.2.38" - "@vue/shared" "3.2.38" + "@vue/compiler-ssr" "3.2.39" + "@vue/shared" "3.2.39" -"@vue/shared@3.2.38": - version "3.2.38" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.38.tgz#e823f0cb2e85b6bf43430c0d6811b1441c300f3c" - integrity sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg== +"@vue/shared@3.2.39": + version "3.2.39" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.39.tgz#302df167559a1a5156da162d8cc6760cef67f8e3" + integrity sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw== "@vue/vue-loader-v15@npm:vue-loader@^15.9.7": 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" integrity sha512-uItRULwQz0epETi9x/RBEqfjHmTAmkIIczpH1R6L9T6yyaaijk0826PzTWnWNm15tw66JT/8GNuXjB0HI5PHLw== -bootstrap@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.0.tgz#838727fb60f1630db370fe57c63cbcf2962bb3d3" - integrity sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A== +bootstrap@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.1.tgz#45f97ff05cbe828bad807b014d8425f3aeb8ec3a" + integrity sha512-UQi3v2NpVPEi1n35dmRRzBJFlgvWHYwyem6yHhuT6afYF+sziEt46McRbT//kVXZ7b1YUYEVGdXEH74Nx3xzGA== brace-expansion@^1.1.7: 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" semver "7.0.0" -core-js@^3.25.0: - version "3.25.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.0.tgz#be71d9e0dd648ffd70c44a7ec2319d039357eceb" - integrity sha512-CVU1xvJEfJGhyCpBrzzzU1kjCfgsGUxhEvwUV2e/cOedYWHdmluamx+knDnmhqALddMG16fZvIqvs9aijsHHaA== +core-js@^3.25.1: + version "3.25.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.1.tgz#5818e09de0db8956e16bf10e2a7141e931b7c69c" + integrity sha512-sr0FY4lnO1hkQ4gLDr24K0DGnweGO1QwSj5BpfQjpSJPdqWalja4cTps29Y/PJVG/P7FYlPDkH3hO+Tr0CvDgQ== core-js@^3.8.3: version "3.24.1" @@ -3293,12 +3339,12 @@ eslint-webpack-plugin@^3.1.0: normalize-path "^3.0.0" schema-utils "^3.1.1" -eslint@^8.23.0: - version "8.23.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.0.tgz#a184918d288820179c6041bb3ddcc99ce6eea040" - integrity sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA== +eslint@^8.23.1: + version "8.23.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.1.tgz#cfd7b3f7fdd07db8d16b4ac0516a29c8d8dca5dc" + integrity sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg== dependencies: - "@eslint/eslintrc" "^1.3.1" + "@eslint/eslintrc" "^1.3.2" "@humanwhocodes/config-array" "^0.10.4" "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" "@humanwhocodes/module-importer" "^1.0.1" @@ -3317,7 +3363,6 @@ eslint@^8.23.0: fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" find-up "^5.0.0" - functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.15.0" globby "^11.1.0" @@ -3326,6 +3371,7 @@ eslint@^8.23.0: import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" + js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.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" 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: version "4.0.0" 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" mime-types "~2.1.24" -typescript@^4.8.2: - version "4.8.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790" - integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw== +typescript@^4.8.3: + version "4.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.3.tgz#d59344522c4bc464a65a730ac695007fdb66dd88" + integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig== unicode-canonical-property-names-ecmascript@^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" integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== -vue@^3.2.38: - version "3.2.38" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.38.tgz#cda3a414631745b194971219318a792dbbccdec0" - integrity sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q== +vue@^3.2.39: + version "3.2.39" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.39.tgz#de071c56c4c32c41cbd54e55f11404295c0dd62d" + integrity sha512-tRkguhRTw9NmIPXhzk21YFBqXHT2t+6C6wPOgQ50fcFVWnPdetmRqbmySRHznrYjX2E47u0cGlKGcxKZJ38R/g== dependencies: - "@vue/compiler-dom" "3.2.38" - "@vue/compiler-sfc" "3.2.38" - "@vue/runtime-dom" "3.2.38" - "@vue/server-renderer" "3.2.38" - "@vue/shared" "3.2.38" + "@vue/compiler-dom" "3.2.39" + "@vue/compiler-sfc" "3.2.39" + "@vue/runtime-dom" "3.2.39" + "@vue/server-renderer" "3.2.39" + "@vue/shared" "3.2.39" watchpack@^2.3.1: version "2.3.1" diff --git a/webapp_dist/js/app.js.gz b/webapp_dist/js/app.js.gz index ed0ddc28..41b8af5e 100644 Binary files a/webapp_dist/js/app.js.gz and b/webapp_dist/js/app.js.gz differ
    Hostname{{ network_hostname }}
    IP Address {{ network_ip }}