diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fa430ea0..d557ccc65 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: - name: Get default environments id: envs run: | - echo "environments=$(pio project config --json-output | jq -cr '.[0][1][0][1]')" >> $GITHUB_OUTPUT + echo "environments=$(pio project config --json-output | jq -cr '.[1][1][0][1]|split(",")')" >> $GITHUB_OUTPUT outputs: environments: ${{ steps.envs.outputs.environments }} @@ -98,17 +98,15 @@ jobs: - name: Rename Firmware run: mv .pio/build/${{ matrix.environment }}/firmware.bin .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin - - name: Copy boot_app0.bin - run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin .pio/build/${{ matrix.environment }}/boot_app0.bin + - name: Rename Factory Firmware + run: mv .pio/build/${{ matrix.environment }}/firmware.factory.bin .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.factory.bin - uses: actions/upload-artifact@v3 with: name: opendtu-${{ matrix.environment }} path: | .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.bin - .pio/build/${{ matrix.environment }}/partitions.bin - .pio/build/${{ matrix.environment }}/bootloader.bin - .pio/build/${{ matrix.environment }}/boot_app0.bin + .pio/build/${{ matrix.environment }}/opendtu-${{ matrix.environment }}.factory.bin release: name: Create Release @@ -121,7 +119,7 @@ jobs: - name: Build Changelog id: github_release - uses: mikepenz/release-changelog-builder-action@v3.7.0 + uses: mikepenz/release-changelog-builder-action@v3 with: failOnError: true commitMode: true @@ -133,12 +131,10 @@ jobs: with: path: artifacts/ - - name: Create ZIPs + - name: Move all files to the same location run: | ls -R - sudo apt install zip cd artifacts - for i in */; do zip -r "${i%/}.zip" "$i"; done for i in */; do cp ${i}opendtu-*.bin ./; done - name: Create release diff --git a/README.md b/README.md index 5dbc8a241..005fd28ff 100644 --- a/README.md +++ b/README.md @@ -210,14 +210,7 @@ It is recommended to make all changes only in the 'platformio_override.ini', th ### using the pre-compiled .bin files -The pre-compiled binary files can be found here on the [github page behind "Releases"](https://github.com/tbnobody/OpenDTU/releases) (look at the right column). For a first installation on an ESP32, download `opendtu-generic.zip`, unpack and use a ESP32 flash tool of your choice (see next chapter) to flash the `.bin` files to the right addresses: - -| Address | File | -| ---------| ---------------------- | -| 0x1000 | bootloader.bin | -| 0x8000 | partitions.bin | -| 0xe000 | boot_app0.bin | -| 0x10000 | opendtu-*.bin | +The pre-compiled binary files can be found here on the [github page behind "Releases"](https://github.com/tbnobody/OpenDTU/releases) (look at the right column). For a first installation on an ESP32, download `opendtu-generic.factory.bin` and use a ESP32 flash tool of your choice to flash the `.bin` file to the address `0x0`. (The previous method with different .bin files is no more necessary.) For further updates download `opendtu-generic.bin` and use the over-the-air firmware update in OpenDTU's web interface. @@ -226,10 +219,7 @@ For further updates download `opendtu-generic.bin` and use the over-the-air firm ```bash esptool.py --port /dev/ttyUSB0 --chip esp32 --before default_reset --after hard_reset \ write_flash --flash_mode dout --flash_freq 40m --flash_size detect \ - 0x1000 bootloader.bin \ - 0x8000 partitions.bin \ - 0xe000 boot_app0.bin \ - 0x10000 opendtu-generic.bin + 0x0 opendtu-generic.factory.bin ``` #### Flash with Espressif Flash Download Tool (Windows) diff --git a/docs/DeviceProfiles/nodemcu_esp32.json b/docs/DeviceProfiles/nodemcu_esp32.json index a42af3719..d7f6a6141 100644 --- a/docs/DeviceProfiles/nodemcu_esp32.json +++ b/docs/DeviceProfiles/nodemcu_esp32.json @@ -1,6 +1,6 @@ [ { - "name": "Generic NodeMCU 32", + "name": "NRF24", "nrf24": { "miso": 19, "mosi": 23, @@ -10,17 +10,11 @@ "cs": 5 }, "eth": { - "enabled": false, - "phy_addr": -1, - "power": -1, - "mdc": -1, - "mdio": -1, - "type": 0, - "clk_mode": 0 + "enabled": false } }, { - "name": "Generic NodeMCU 32 with CMT2300A", + "name": "CMT2300A", "nrf24": { "miso": -1, "mosi": -1, @@ -38,17 +32,11 @@ "gpio3": 16 }, "eth": { - "enabled": false, - "phy_addr": -1, - "power": -1, - "mdc": -1, - "mdio": -1, - "type": 0, - "clk_mode": 0 + "enabled": false } }, { - "name": "Generic NodeMCU 32 with SSD1306", + "name": "NRF24 with SSD1306", "nrf24": { "miso": 19, "mosi": 23, @@ -58,13 +46,7 @@ "cs": 5 }, "eth": { - "enabled": false, - "phy_addr": -1, - "power": -1, - "mdc": -1, - "mdio": -1, - "type": 0, - "clk_mode": 0 + "enabled": false }, "display": { "type": 2, @@ -73,7 +55,7 @@ } }, { - "name": "Generic NodeMCU 32 with SH1106", + "name": "NRF24 with SH1106", "nrf24": { "miso": 19, "mosi": 23, @@ -83,13 +65,7 @@ "cs": 5 }, "eth": { - "enabled": false, - "phy_addr": -1, - "power": -1, - "mdc": -1, - "mdio": -1, - "type": 0, - "clk_mode": 0 + "enabled": false }, "display": { "type": 3, @@ -98,7 +74,61 @@ } }, { - "name": "Generic NodeMCU 32 with NRF + CMT Module", + "name": "CMT2300A with SSD1306", + "nrf24": { + "miso": -1, + "mosi": -1, + "clk": -1, + "irq": -1, + "en": -1, + "cs": -1 + }, + "cmt": { + "clk": 18, + "cs": 4, + "fcs": 5, + "sdio": 23, + "gpio2": 19, + "gpio3": 16 + }, + "eth": { + "enabled": false + }, + "display": { + "type": 2, + "data": 21, + "clk": 22 + } + }, + { + "name": "CMT2300A with SH1106", + "nrf24": { + "miso": -1, + "mosi": -1, + "clk": -1, + "irq": -1, + "en": -1, + "cs": -1 + }, + "cmt": { + "clk": 18, + "cs": 4, + "fcs": 5, + "sdio": 23, + "gpio2": 19, + "gpio3": 16 + }, + "eth": { + "enabled": false + }, + "display": { + "type": 3, + "data": 21, + "clk": 22 + } + }, + { + "name": "NRF24 + CMT2300A", "nrf24": { "miso": 19, "mosi": 23, diff --git a/docs/DeviceProfiles/wemos-lolin32-oled.json b/docs/DeviceProfiles/wemos-lolin32-oled.json new file mode 100644 index 000000000..cbfa6eaa1 --- /dev/null +++ b/docs/DeviceProfiles/wemos-lolin32-oled.json @@ -0,0 +1,21 @@ +[ + { + "name": "Wemos Lolin32 OLED", + "nrf24": { + "miso": 2, + "mosi": 14, + "clk": 12, + "irq": 0, + "en": 15, + "cs": 13 + }, + "eth": { + "enabled": false + }, + "display": { + "type": 2, + "data": 5, + "clk": 4 + } + } +] diff --git a/docs/UpgradePartition.md b/docs/UpgradePartition.md index 429e5f581..f2cd8bf9a 100644 --- a/docs/UpgradePartition.md +++ b/docs/UpgradePartition.md @@ -12,13 +12,10 @@ There are several possibilities to update the partition table: - Any kind of flash interface - If you like to use any kind of flash interface like `esptool.py`, Espressif Flash Download Tool, ESP_Flasher or esptool-js you have to make sure to upload **ALL** provided .bin files. It is important to enter the correct target addresses. + If you like to use any kind of flash interface like `esptool.py`, Espressif Flash Download Tool, ESP_Flasher or esptool-js you have to make sure to upload the provided .factory.bin file. It is important to enter the correct target address. | Address | File | | ---------| ---------------------- | - | 0x1000 | bootloader.bin | - | 0x8000 | partitions.bin | - | 0xe000 | boot_app0.bin | - | 0x10000 | opendtu-*.bin | + | 0x0 | opendtu-*.factory.bin | After upgrading the ESP32 will open the intergrated access point (AP) again. Just connect to it using the default password ("openDTU42"). If you are connected, just visit and enter the "Configuration Management". Recover the previously backuped config files. diff --git a/docs/esp32_flash_download_tool.png b/docs/esp32_flash_download_tool.png index 3f970e63f..7114d4c97 100644 Binary files a/docs/esp32_flash_download_tool.png and b/docs/esp32_flash_download_tool.png differ diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index 8faf1727e..fc1ca409e 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -51,6 +51,69 @@ void HoymilesRadio::sendLastPacketAgain() sendEsbPacket(cmd); } +void HoymilesRadio::handleReceivedPackage() +{ + if (_busyFlag && _rxTimeout.occured()) { + Hoymiles.getMessageOutput()->println("RX Period End"); + std::shared_ptr inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress()); + + if (nullptr != inv) { + CommandAbstract* cmd = _commandQueue.front().get(); + uint8_t verifyResult = inv->verifyAllFragments(cmd); + if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) { + Hoymiles.getMessageOutput()->println("Nothing received, resend whole request"); + sendLastPacketAgain(); + + } else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) { + Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded"); + _commandQueue.pop(); + _busyFlag = false; + + } else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) { + Hoymiles.getMessageOutput()->println("Retransmit timeout"); + _commandQueue.pop(); + _busyFlag = false; + + } else if (verifyResult == FRAGMENT_HANDLE_ERROR) { + Hoymiles.getMessageOutput()->println("Packet handling error"); + _commandQueue.pop(); + _busyFlag = false; + + } else if (verifyResult > 0) { + // Perform Retransmit + Hoymiles.getMessageOutput()->print("Request retransmit: "); + Hoymiles.getMessageOutput()->println(verifyResult); + sendRetransmitPacket(verifyResult); + + } else { + // Successful received all packages + Hoymiles.getMessageOutput()->println("Success"); + _commandQueue.pop(); + _busyFlag = false; + } + } else { + // If inverter was not found, assume the command is invalid + Hoymiles.getMessageOutput()->println("RX: Invalid inverter found"); + _commandQueue.pop(); + _busyFlag = false; + } + } else if (!_busyFlag) { + // Currently in idle mode --> send packet if one is in the queue + if (!_commandQueue.empty()) { + CommandAbstract* cmd = _commandQueue.front().get(); + + auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress()); + if (nullptr != inv) { + inv->clearRxFragmentBuffer(); + sendEsbPacket(cmd); + } else { + Hoymiles.getMessageOutput()->println("TX: Invalid inverter found"); + _commandQueue.pop(); + } + } + } +} + void HoymilesRadio::dumpBuf(const uint8_t buf[], uint8_t len, bool appendNewline) { for (uint8_t i = 0; i < len; i++) { diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index 2ee2ad25f..214772938 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "TimeoutHelper.h" #include "commands/CommandAbstract.h" #include "types.h" #include @@ -30,9 +31,12 @@ class HoymilesRadio { virtual void sendEsbPacket(CommandAbstract* cmd) = 0; void sendRetransmitPacket(uint8_t fragment_id); void sendLastPacketAgain(); + void handleReceivedPackage(); serial_u _dtuSerial; std::queue> _commandQueue; bool _isInitialized = false; bool _busyFlag = false; + + TimeoutHelper _rxTimeout; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp index 4c3285f59..b2044649b 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp @@ -151,65 +151,7 @@ void HoymilesRadio_CMT::loop() } } - if (_busyFlag && _rxTimeout.occured()) { - Hoymiles.getMessageOutput()->println("RX Period End"); - std::shared_ptr inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress()); - - if (nullptr != inv) { - CommandAbstract* cmd = _commandQueue.front().get(); - uint8_t verifyResult = inv->verifyAllFragments(cmd); - if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) { - Hoymiles.getMessageOutput()->println("Nothing received, resend whole request"); - sendLastPacketAgain(); - - } else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) { - Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded"); - _commandQueue.pop(); - _busyFlag = false; - - } else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) { - Hoymiles.getMessageOutput()->println("Retransmit timeout"); - _commandQueue.pop(); - _busyFlag = false; - - } else if (verifyResult == FRAGMENT_HANDLE_ERROR) { - Hoymiles.getMessageOutput()->println("Packet handling error"); - _commandQueue.pop(); - _busyFlag = false; - - } else if (verifyResult > 0) { - // Perform Retransmit - Hoymiles.getMessageOutput()->print("Request retransmit: "); - Hoymiles.getMessageOutput()->println(verifyResult); - sendRetransmitPacket(verifyResult); - - } else { - // Successful received all packages - Hoymiles.getMessageOutput()->println("Success"); - _commandQueue.pop(); - _busyFlag = false; - } - } else { - // If inverter was not found, assume the command is invalid - Hoymiles.getMessageOutput()->println("RX: Invalid inverter found"); - _commandQueue.pop(); - _busyFlag = false; - } - } else if (!_busyFlag) { - // Currently in idle mode --> send packet if one is in the queue - if (!_commandQueue.empty()) { - CommandAbstract* cmd = _commandQueue.front().get(); - - auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress()); - if (nullptr != inv) { - inv->clearRxFragmentBuffer(); - sendEsbPacket(cmd); - } else { - Hoymiles.getMessageOutput()->println("TX: Invalid inverter found"); - _commandQueue.pop(); - } - } - } + handleReceivedPackage(); } void HoymilesRadio_CMT::setPALevel(int8_t paLevel) diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.h b/lib/Hoymiles/src/HoymilesRadio_CMT.h index b1cfa7c4c..66314b3d4 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.h +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.h @@ -2,7 +2,6 @@ #pragma once #include "HoymilesRadio.h" -#include "TimeoutHelper.h" #include "commands/CommandAbstract.h" #include "types.h" #include @@ -48,7 +47,6 @@ class HoymilesRadio_CMT : public HoymilesRadio { bool _gpio3_configured = false; std::queue _rxBuffer; - TimeoutHelper _rxTimeout; TimeoutHelper _txTimeout; uint32_t _inverterTargetFrequency = HOYMILES_CMT_WORK_FREQ; diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp index 88b7dbb63..ecb9501e1 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp @@ -55,6 +55,7 @@ void HoymilesRadio_NRF::loop() memset(f.fragment, 0xcc, MAX_RF_PAYLOAD_SIZE); f.len = _radio->getDynamicPayloadSize(); f.channel = _radio->getChannel(); + f.rssi = _radio->testRPD() ? -30 : -80; if (f.len > MAX_RF_PAYLOAD_SIZE) f.len = MAX_RF_PAYLOAD_SIZE; _radio->read(f.fragment, f.len); @@ -76,7 +77,9 @@ void HoymilesRadio_NRF::loop() if (nullptr != inv) { // Save packet in inverter rx buffer Hoymiles.getMessageOutput()->printf("RX Channel: %d --> ", f.channel); - dumpBuf(f.fragment, f.len); + dumpBuf(f.fragment, f.len, false); + Hoymiles.getMessageOutput()->printf("| %d dBm\r\n", f.rssi); + inv->addRxFragment(f.fragment, f.len); } else { Hoymiles.getMessageOutput()->println("Inverter Not found!"); @@ -91,65 +94,7 @@ void HoymilesRadio_NRF::loop() } } - if (_busyFlag && _rxTimeout.occured()) { - Hoymiles.getMessageOutput()->println("RX Period End"); - std::shared_ptr inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress()); - - if (nullptr != inv) { - CommandAbstract* cmd = _commandQueue.front().get(); - uint8_t verifyResult = inv->verifyAllFragments(cmd); - if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) { - Hoymiles.getMessageOutput()->println("Nothing received, resend whole request"); - sendLastPacketAgain(); - - } else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) { - Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded"); - _commandQueue.pop(); - _busyFlag = false; - - } else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) { - Hoymiles.getMessageOutput()->println("Retransmit timeout"); - _commandQueue.pop(); - _busyFlag = false; - - } else if (verifyResult == FRAGMENT_HANDLE_ERROR) { - Hoymiles.getMessageOutput()->println("Packet handling error"); - _commandQueue.pop(); - _busyFlag = false; - - } else if (verifyResult > 0) { - // Perform Retransmit - Hoymiles.getMessageOutput()->print("Request retransmit: "); - Hoymiles.getMessageOutput()->println(verifyResult); - sendRetransmitPacket(verifyResult); - - } else { - // Successful received all packages - Hoymiles.getMessageOutput()->println("Success"); - _commandQueue.pop(); - _busyFlag = false; - } - } else { - // If inverter was not found, assume the command is invalid - Hoymiles.getMessageOutput()->println("RX: Invalid inverter found"); - _commandQueue.pop(); - _busyFlag = false; - } - } else if (!_busyFlag) { - // Currently in idle mode --> send packet if one is in the queue - if (!_commandQueue.empty()) { - CommandAbstract* cmd = _commandQueue.front().get(); - - auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress()); - if (nullptr != inv) { - inv->clearRxFragmentBuffer(); - sendEsbPacket(cmd); - } else { - Hoymiles.getMessageOutput()->println("TX: Invalid inverter found"); - _commandQueue.pop(); - } - } - } + handleReceivedPackage(); } void HoymilesRadio_NRF::setPALevel(rf24_pa_dbm_e paLevel) diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.h b/lib/Hoymiles/src/HoymilesRadio_NRF.h index 88c0d2f90..8530a0e34 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.h +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.h @@ -2,7 +2,6 @@ #pragma once #include "HoymilesRadio.h" -#include "TimeoutHelper.h" #include "commands/CommandAbstract.h" #include #include @@ -44,5 +43,4 @@ class HoymilesRadio_NRF : public HoymilesRadio { volatile bool _packetReceived = false; std::queue _rxBuffer; - TimeoutHelper _rxTimeout; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.cpp b/lib/Hoymiles/src/commands/MultiDataCommand.cpp index b25c9027d..39a0d4c64 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.cpp +++ b/lib/Hoymiles/src/commands/MultiDataCommand.cpp @@ -91,4 +91,13 @@ void MultiDataCommand::udpateCRC() uint16_t crc = crc16(&_payload[10], 14); // From data_type till password _payload[24] = (uint8_t)(crc >> 8); _payload[25] = (uint8_t)(crc); -} \ No newline at end of file +} + +uint8_t MultiDataCommand::getTotalFragmentSize(fragment_t fragment[], uint8_t max_fragment_id) +{ + uint8_t fragmentSize = 0; + for (uint8_t i = 0; i < max_fragment_id; i++) { + fragmentSize += fragment[i].len; + } + return fragmentSize; +} diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.h b/lib/Hoymiles/src/commands/MultiDataCommand.h index ff835d7b9..4d2adfde4 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.h +++ b/lib/Hoymiles/src/commands/MultiDataCommand.h @@ -20,6 +20,7 @@ class MultiDataCommand : public CommandAbstract { void setDataType(uint8_t data_type); uint8_t getDataType(); void udpateCRC(); + static uint8_t getTotalFragmentSize(fragment_t fragment[], uint8_t max_fragment_id); RequestFrameCommand _cmdRequestFrame; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index 6a7db92a9..646e363fe 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2022 Thomas Basler and others */ #include "RealTimeRunDataCommand.h" +#include "Hoymiles.h" #include "inverters/InverterAbstract.h" RealTimeRunDataCommand::RealTimeRunDataCommand(uint64_t target_address, uint64_t router_address, time_t time) @@ -25,6 +26,18 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment return false; } + // Check if at least all required bytes are received + // In case of low power in the inverter it occours that some incomplete fragments + // with a valid CRC are received. + if (getTotalFragmentSize(fragment, max_fragment_id) < inverter->Statistics()->getMaxByteCount()) { + Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d min. expected size: %d\r\n", + getCommandName().c_str(), + getTotalFragmentSize(fragment, max_fragment_id), + inverter->Statistics()->getMaxByteCount()); + + return false; + } + // Move all fragments into target buffer uint8_t offs = 0; inverter->Statistics()->clearBuffer(); diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index 0c1a10f54..d5994dcab 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -29,6 +29,7 @@ const devInfo_t devInfo[] = { { { 0x10, 0x10, 0x10, 0x15 }, static_cast(300 * 0.7), "HM-300" }, // HM-300 factory limitted to 70% { { 0x10, 0x20, 0x21, ALL }, 350, "HMS-350" }, // 00 + { { 0x10, 0x20, 0x41, ALL }, 400, "HMS-400" }, // 00 { { 0x10, 0x10, 0x51, ALL }, 450, "HMS-450" }, // 01 { { 0x10, 0x10, 0x71, ALL }, 500, "HMS-500" }, // 02 { { 0x10, 0x21, 0x11, ALL }, 600, "HMS-600" }, // 01 diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 0575a5fbb..c0e0b8ba5 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -34,6 +34,25 @@ void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, uin _byteAssignmentSize = size; } +uint8_t StatisticsParser::getMaxByteCount() +{ + static uint8_t maxByteCount = 0; + + // Use already calculated value + if (maxByteCount > 0) { + return maxByteCount; + } + + for (uint8_t i = 0; i < _byteAssignmentSize; i++) { + if (_byteAssignment[i].div == CMD_CALC) { + continue; + } + maxByteCount = max(maxByteCount, _byteAssignment[i].start + _byteAssignment[i].num); + } + + return maxByteCount; +} + void StatisticsParser::clearBuffer() { memset(_payloadStatistic, 0, STATISTIC_PACKET_SIZE); diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index f4bf7ae02..9f71045a3 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -109,6 +109,9 @@ class StatisticsParser : public Parser { void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size); + // Returns 1 based amount of expected bytes of statistic data + uint8_t getMaxByteCount(); + const byteAssign_t* getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); fieldSettings_t* getSettingByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); diff --git a/platformio.ini b/platformio.ini index 5a5522984..fbab96831 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,16 +9,20 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = generic +default_envs = generic_esp32 extra_configs = platformio_override.ini [env] +; Make sure to NOT add any spaces in the custom_ci_action property +; (also the position in the file is important) +custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb + framework = arduino -platform = espressif32@6.3.0 +platform = espressif32@6.3.1 build_flags = - -DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz + -DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/favicon.png:webapp_dist/js/app.js.gz -DPIOENV=\"$PIOENV\" -Wall -Wextra -Werror -std=c++17 @@ -30,8 +34,8 @@ lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer bblanchon/ArduinoJson @ ^6.21.2 https://github.com/bertmelis/espMqttClient.git#v1.4.3 - nrf24/RF24 @ ^1.4.5 - olikraus/U8g2 @ ^2.34.17 + nrf24/RF24 @ ^1.4.7 + olikraus/U8g2 @ ^2.34.22 buelowp/sunset @ ^1.1.7 https://github.com/ArekKubacki/modbus-esp8266#4.1.2 @@ -51,15 +55,9 @@ upload_protocol = esptool ; upload_port = COM4 -[env:generic] +[env:generic_esp32] board = esp32dev build_flags = ${env.build_flags} - -DHOYMILES_PIN_MISO=19 - -DHOYMILES_PIN_MOSI=23 - -DHOYMILES_PIN_SCLK=18 - -DHOYMILES_PIN_IRQ=16 - -DHOYMILES_PIN_CE=4 - -DHOYMILES_PIN_CS=5 [env:generic_esp32c3] @@ -67,10 +65,28 @@ board = esp32dev board_build.mcu = esp32c3 custom_patches = esp32c3 build_flags = ${env.build_flags} - -DHOYMILES_PIN_MISO=9 - -DHOYMILES_PIN_MOSI=10 - -DHOYMILES_PIN_SCLK=8 - -DHOYMILES_PIN_IRQ=3 + + +[env:generic_esp32s3] +board = esp32-s3-devkitc-1 +build_flags = ${env.build_flags} + + +[env:generic_esp32s3_usb] +board = esp32-s3-devkitc-1 +upload_protocol = esp-builtin +build_flags = ${env.build_flags} + -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=1 + + +[env:generic] +board = esp32dev +build_flags = ${env.build_flags} + -DHOYMILES_PIN_MISO=19 + -DHOYMILES_PIN_MOSI=23 + -DHOYMILES_PIN_SCLK=18 + -DHOYMILES_PIN_IRQ=16 -DHOYMILES_PIN_CE=4 -DHOYMILES_PIN_CS=5 diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index 52a37b5df..459b68228 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -28,10 +28,11 @@ void InverterSettingsClass::init() // Initialize inverter communication MessageOutput.print("Initialize Hoymiles interface... "); - if (PinMapping.isValidNrf24Config() || PinMapping.isValidCmt2300Config()) { - Hoymiles.setMessageOutput(&MessageOutput); - Hoymiles.init(); + Hoymiles.setMessageOutput(&MessageOutput); + Hoymiles.init(); + + if (PinMapping.isValidNrf24Config() || PinMapping.isValidCmt2300Config()) { if (PinMapping.isValidNrf24Config()) { SPIClass* spiClass = new SPIClass(SPI_NRF); spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs); diff --git a/src/MqttHandleDtu.cpp b/src/MqttHandleDtu.cpp index dfca23406..ee5ad417f 100644 --- a/src/MqttHandleDtu.cpp +++ b/src/MqttHandleDtu.cpp @@ -28,6 +28,7 @@ void MqttHandleDtuClass::loop() MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname()); if (NetworkSettings.NetworkMode() == network_mode::WiFi) { MqttSettings.publish("dtu/rssi", String(WiFi.RSSI())); + MqttSettings.publish("dtu/bssid", String(WiFi.BSSIDstr())); } _lastPublish = millis(); diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index ed620dc83..a42803cdd 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.cpp @@ -129,7 +129,6 @@ void MqttHandleInverterClass::publishField(std::shared_ptr inv String value = String( inv->Statistics()->getChannelFieldValue(type, channel, fieldId), static_cast(inv->Statistics()->getChannelFieldDigits(type, channel, fieldId))); - value.trim(); MqttSettings.publish(topic, value); } diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index da0363df7..d5b29faf5 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -159,7 +159,11 @@ void MqttSettingsClass::publish(const String& subtopic, const String& payload) { String topic = getPrefix(); topic += subtopic; - mqttClient->publish(topic.c_str(), 0, Configuration.get().Mqtt_Retain, payload.c_str()); + + String value = payload; + value.trim(); + + mqttClient->publish(topic.c_str(), 0, Configuration.get().Mqtt_Retain, value.c_str()); } void MqttSettingsClass::publishGeneric(const String& topic, const String& payload, bool retain, uint8_t qos) diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index 9cbcd8764..b9532d665 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -38,6 +38,30 @@ #define LED1 -1 #endif +#ifndef HOYMILES_PIN_SCLK +#define HOYMILES_PIN_SCLK -1 +#endif + +#ifndef HOYMILES_PIN_CS +#define HOYMILES_PIN_CS -1 +#endif + +#ifndef HOYMILES_PIN_CE +#define HOYMILES_PIN_CE -1 +#endif + +#ifndef HOYMILES_PIN_IRQ +#define HOYMILES_PIN_IRQ -1 +#endif + +#ifndef HOYMILES_PIN_MISO +#define HOYMILES_PIN_MISO -1 +#endif + +#ifndef HOYMILES_PIN_MOSI +#define HOYMILES_PIN_MOSI -1 +#endif + #ifndef CMT_CLK #define CMT_CLK -1 #endif diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp index 06d8261eb..1226917fa 100644 --- a/src/WebApi_network.cpp +++ b/src/WebApi_network.cpp @@ -36,6 +36,7 @@ void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request) root["sta_status"] = ((WiFi.getMode() & WIFI_STA) != 0); root["sta_ssid"] = WiFi.SSID(); + root["sta_bssid"] = WiFi.BSSIDstr(); root["sta_rssi"] = WiFi.RSSI(); root["network_hostname"] = NetworkSettings.getHostname(); root["network_ip"] = NetworkSettings.localIP().toString(); diff --git a/src/WebApi_ntp.cpp b/src/WebApi_ntp.cpp index 26524c2af..67b30851d 100644 --- a/src/WebApi_ntp.cpp +++ b/src/WebApi_ntp.cpp @@ -55,14 +55,14 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request) if (SunPosition.sunriseTime(&timeinfo)) { strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo); } else { - strcpy(timeStringBuff, "--"); + snprintf(timeStringBuff, sizeof(timeStringBuff), "--"); } root["sun_risetime"] = timeStringBuff; if (SunPosition.sunsetTime(&timeinfo)) { strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo); } else { - strcpy(timeStringBuff, "--"); + snprintf(timeStringBuff, sizeof(timeStringBuff), "--"); } root["sun_settime"] = timeStringBuff; diff --git a/src/WebApi_prometheus.cpp b/src/WebApi_prometheus.cpp index d921dac65..28b003315 100644 --- a/src/WebApi_prometheus.cpp +++ b/src/WebApi_prometheus.cpp @@ -53,6 +53,10 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques stream->print("# TYPE wifi_rssi gauge\n"); stream->printf("wifi_rssi %d\n", WiFi.RSSI()); + stream->print("# HELP wifi_station WiFi Station info\n"); + stream->print("# TYPE wifi_station gauge\n"); + stream->printf("wifi_station{bssid=\"%s\"} 1\n", WiFi.BSSIDstr().c_str()); + for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); diff --git a/src/WebApi_webapp.cpp b/src/WebApi_webapp.cpp index 4976f78e2..fd42da5c1 100644 --- a/src/WebApi_webapp.cpp +++ b/src/WebApi_webapp.cpp @@ -6,11 +6,13 @@ extern const uint8_t file_index_html_start[] asm("_binary_webapp_dist_index_html_gz_start"); extern const uint8_t file_favicon_ico_start[] asm("_binary_webapp_dist_favicon_ico_start"); +extern const uint8_t file_favicon_png_start[] asm("_binary_webapp_dist_favicon_png_start"); extern const uint8_t file_zones_json_start[] asm("_binary_webapp_dist_zones_json_gz_start"); extern const uint8_t file_app_js_start[] asm("_binary_webapp_dist_js_app_js_gz_start"); extern const uint8_t file_index_html_end[] asm("_binary_webapp_dist_index_html_gz_end"); extern const uint8_t file_favicon_ico_end[] asm("_binary_webapp_dist_favicon_ico_end"); +extern const uint8_t file_favicon_png_end[] asm("_binary_webapp_dist_favicon_png_end"); extern const uint8_t file_zones_json_end[] asm("_binary_webapp_dist_zones_json_gz_end"); extern const uint8_t file_app_js_end[] asm("_binary_webapp_dist_js_app_js_gz_end"); @@ -41,6 +43,11 @@ void WebApiWebappClass::init(AsyncWebServer* server) request->send(response); }); + _server->on("/favicon.png", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse_P(200, "image/png", file_favicon_png_start, file_favicon_png_end - file_favicon_png_start); + request->send(response); + }); + _server->on("/zones.json", HTTP_GET, [](AsyncWebServerRequest* request) { AsyncWebServerResponse* response = request->beginResponse_P(200, "application/json", file_zones_json_start, file_zones_json_end - file_zones_json_start); response->addHeader("Content-Encoding", "gzip"); diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 870c06188..f18b58d02 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -80,8 +80,10 @@ void WebApiWsLiveClass::loop() _ws.textAll(buffer); } - } catch (std::bad_alloc& bad_alloc) { + } catch (const std::bad_alloc& bad_alloc) { MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); + } catch (const std::exception& exc) { + MessageOutput.printf("Unknown exception in /api/livedata/status. Reason: \"%s\".\r\n", exc.what()); } _lastWsPublish = millis(); @@ -239,9 +241,11 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request) response->setLength(); request->send(response); - } catch (std::bad_alloc& bad_alloc) { + } catch (const std::bad_alloc& bad_alloc) { MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); - + WebApi.sendTooManyRequests(request); + } catch (const std::exception& exc) { + MessageOutput.printf("Unknown exception in /api/livedata/status. Reason: \"%s\".\r\n", exc.what()); WebApi.sendTooManyRequests(request); } } diff --git a/webapp/index.html b/webapp/index.html index 36236f188..39a94a039 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -3,6 +3,8 @@ + + OpenDTU diff --git a/webapp/package.json b/webapp/package.json index 7ae39f84f..23660a816 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -23,24 +23,24 @@ }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^0.11.0", - "@rushstack/eslint-patch": "^1.3.0", + "@rushstack/eslint-patch": "^1.3.2", "@tsconfig/node18": "^2.0.1", "@types/bootstrap": "^5.2.6", - "@types/node": "^20.2.5", + "@types/node": "^20.3.2", "@types/sortablejs": "^1.15.1", "@types/spark-md5": "^3.0.2", "@vitejs/plugin-vue": "^4.2.3", "@vue/eslint-config-typescript": "^11.0.3", "@vue/tsconfig": "^0.4.0", - "eslint": "^8.41.0", - "eslint-plugin-vue": "^9.14.1", + "eslint": "^8.43.0", + "eslint-plugin-vue": "^9.15.1", "npm-run-all": "^4.1.5", - "sass": "^1.62.1", - "terser": "^5.17.7", + "sass": "^1.63.6", + "terser": "^5.18.2", "typescript": "^5.1.3", "vite": "^4.3.9", "vite-plugin-compression": "^0.5.1", "vite-plugin-css-injected-by-js": "^3.1.1", - "vue-tsc": "^1.6.5" + "vue-tsc": "^1.8.2" } } diff --git a/webapp/public/favicon.png b/webapp/public/favicon.png new file mode 100644 index 000000000..3378b6613 Binary files /dev/null and b/webapp/public/favicon.png differ diff --git a/webapp/src/components/WifiStationInfo.vue b/webapp/src/components/WifiStationInfo.vue index e1bd0dcdc..12769a8e4 100644 --- a/webapp/src/components/WifiStationInfo.vue +++ b/webapp/src/components/WifiStationInfo.vue @@ -13,6 +13,10 @@ {{ $t('wifistationinfo.Ssid') }} {{ networkStatus.sta_ssid }} + + {{ $t('wifistationinfo.Bssid') }} + {{ networkStatus.sta_bssid }} + {{ $t('wifistationinfo.Quality') }} {{ $n(getRSSIasQuality(networkStatus.sta_rssi), 'percent') }} diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 0d454bae3..a014f09df 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -208,6 +208,7 @@ "Enabled": "aktiv", "Disabled": "nicht aktiv", "Ssid": "SSID", + "Bssid": "BSSID", "Quality": "Qualität", "Rssi": "RSSI" }, diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 7a05b4db0..4532861be 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -208,6 +208,7 @@ "Enabled": "enabled", "Disabled": "disabled", "Ssid": "SSID", + "Bssid": "BSSID", "Quality": "Quality", "Rssi": "RSSI" }, diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index daafad70e..9e3807e99 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -27,8 +27,8 @@ "Reload": "Reload" }, "localeswitcher": { - "Dark": "Dark", - "Light": "Light", + "Dark": "Sombre", + "Light": "Clair", "Auto": "Auto" }, "apiresponse": { @@ -190,13 +190,13 @@ }, "radioinfo": { "RadioInformation": "Informations sur la radio", - "Status": "{module} Status", - "ChipStatus": "{module} sÉtat de la puce", + "Status": "{module} Statut", + "ChipStatus": "{module} État de la puce", "ChipType": "{module} Type de puce", "Connected": "connectée", "NotConnected": "non connectée", - "Configured": "configured", - "NotConfigured": "not configured", + "Configured": "configurée", + "NotConfigured": "non configurée", "Unknown": "Inconnue" }, "networkinfo": { @@ -208,6 +208,7 @@ "Enabled": "activé", "Disabled": "désactivé", "Ssid": "SSID", + "Bssid": "BSSID", "Quality": "Qualité", "Rssi": "RSSI" }, @@ -244,12 +245,12 @@ "Synced": "synchronisée", "NotSynced": "pas synchronisée", "LocalTime": "Heure locale", - "Sunrise": "Sunrise", - "Sunset": "Sunset", + "Sunrise": "Lever du soleil", + "Sunset": "Coucher du soleil", "NotAvailable": "Not Available", "Mode": "Mode", - "Day": "Day", - "Night": "Night" + "Day": "Jour", + "Night": "Nuit" }, "mqttinfo": { "MqttInformation": "MQTT Information", @@ -447,7 +448,7 @@ "InverterSerial": "Numéro de série de l'onduleur", "InverterName": "Nom de l'onduleur :", "InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.", - "InverterStatus": "Receive / Send", + "InverterStatus": "Recevoir / Envoyer", "PollEnable": "Interroger les données de l'onduleur", "PollEnableNight": "Interroger les données de l'onduleur la nuit", "CommandEnable": "Envoyer des commandes", @@ -537,10 +538,10 @@ "ScreensaverHint": "Déplacez un peu l'écran à chaque mise à jour pour éviter le phénomène de brûlure. (Utile surtout pour les écrans OLED)", "Contrast": "Contraste ({contrast}):", "Rotation": "Rotation:", - "rot0": "No rotation", - "rot90": "90 degree rotation", - "rot180": "180 degree rotation", - "rot270": "270 degree rotation", + "rot0": "Pas de rotation", + "rot90": "Rotation de 90 degrés", + "rot180": "Rotation de 180 degrés", + "rot270": "Rotation de 270 degrés", "DisplayLanguage": "Langue d'affichage", "en": "Anglais", "de": "Allemand", diff --git a/webapp/src/types/NetworkStatus.ts b/webapp/src/types/NetworkStatus.ts index c10c4a0f6..90038c9d6 100644 --- a/webapp/src/types/NetworkStatus.ts +++ b/webapp/src/types/NetworkStatus.ts @@ -2,6 +2,7 @@ export interface NetworkStatus { // WifiStationInfo sta_status: boolean; sta_ssid: string; + sta_bssid: string; sta_rssi: number; // WifiApInfo ap_status: boolean; diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 7a5d7d5c9..330d42667 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -156,15 +156,15 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.41.0.tgz#080321c3b68253522f7646b55b577dd99d2950b3" - integrity sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA== +"@eslint/js@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0" + integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg== -"@humanwhocodes/config-array@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== +"@humanwhocodes/config-array@^0.11.10": + version "0.11.10" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -345,10 +345,10 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rushstack/eslint-patch@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.0.tgz#f5635b36fc0dad96ef1e542a302cd914230188c0" - integrity sha512-IthPJsJR85GhOkp3Hvp8zFOPK5ynKn6STyHa/WZpioK7E1aYDiBzpqQPrngc14DszIUkIrdd3k9Iu0XSzlP/1w== +"@rushstack/eslint-patch@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz#31b9c510d8cada9683549e1dbb4284cca5001faf" + integrity sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw== "@tsconfig/node18@^2.0.1": version "2.0.1" @@ -372,10 +372,10 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/node@^20.2.5": - version "20.2.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb" - integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ== +"@types/node@^20.3.2": + version "20.3.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" + integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== "@types/semver@^7.3.12": version "7.3.13" @@ -481,49 +481,26 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz#ee0b6dfcc62fe65364e6395bf38fa2ba10bb44b6" integrity sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw== -"@volar/language-core@1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.4.1.tgz#66b5758252e35c4e5e71197ca7fa0344d306442c" - integrity sha512-EIY+Swv+TjsWpxOxujjMf1ZXqOjg9MT2VMXZ+1dKva0wD8W0L6EtptFFcCJdBbcKmGMFkr57Qzz9VNMWhs3jXQ== +"@volar/language-core@1.7.9": + version "1.7.9" + resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.7.9.tgz#e9ca92fcbffa88136394c692454036548d97ea5a" + integrity sha512-U6GMPDNqfGFqVRv4npUN2hEDW4/6EwL4YHd6qggapcvTzRrYAodTTbOTZs4PDzmw7NSZ2Cdrmd54SjzCCMXbZw== dependencies: - "@volar/source-map" "1.4.1" + "@volar/source-map" "1.7.9" -"@volar/source-map@1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.4.1.tgz#e3b561775c742508e5e1f28609a4787c98056715" - integrity sha512-bZ46ad72dsbzuOWPUtJjBXkzSQzzSejuR3CT81+GvTEI2E994D8JPXzM3tl98zyCNnjgs4OkRyliImL1dvJ5BA== +"@volar/source-map@1.7.9": + version "1.7.9" + resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.7.9.tgz#a9588fce0a989f320bcd1ab37e31948a6a57f5cc" + integrity sha512-bLizh8HIAzbq7OdxfyoG18dXJJF9FNXBcaiRj7eqg2Bq+DkgkYHabaY+xobgaXeKFOp93Tg1KfMM7qyR2KXHmQ== dependencies: - muggle-string "^0.2.2" + muggle-string "^0.3.1" -"@volar/typescript@1.4.1-patch.2": - version "1.4.1-patch.2" - resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.4.1-patch.2.tgz#89f4bd199ca81a832d86d1449b01f49f2b72137c" - integrity sha512-lPFYaGt8OdMEzNGJJChF40uYqMO4Z/7Q9fHPQC/NRVtht43KotSXLrkPandVVMf9aPbiJ059eAT+fwHGX16k4w== +"@volar/typescript@1.7.9": + version "1.7.9" + resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.7.9.tgz#28f3597a391a90036c9e90c770654cd45201f572" + integrity sha512-cXGg7lgvdjpRjYfz52cXKo6ExBi8k3pWeBe6Gckf4+9zmTfEwEFfKWMj0H/IyUYO+S2rjyE9jytdsu1Imk+Azw== dependencies: - "@volar/language-core" "1.4.1" - -"@volar/vue-language-core@1.6.5": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@volar/vue-language-core/-/vue-language-core-1.6.5.tgz#db42520f1a29737c7e40fbb3e6aead8def85ba75" - integrity sha512-IF2b6hW4QAxfsLd5mePmLgtkXzNi+YnH6ltCd80gb7+cbdpFMjM1I+w+nSg2kfBTyfu+W8useCZvW89kPTBpzg== - dependencies: - "@volar/language-core" "1.4.1" - "@volar/source-map" "1.4.1" - "@vue/compiler-dom" "^3.3.0" - "@vue/compiler-sfc" "^3.3.0" - "@vue/reactivity" "^3.3.0" - "@vue/shared" "^3.3.0" - minimatch "^9.0.0" - muggle-string "^0.2.2" - vue-template-compiler "^2.7.14" - -"@volar/vue-typescript@1.6.5": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@volar/vue-typescript/-/vue-typescript-1.6.5.tgz#6ca9bfefa5dc64ff97fcdbc74249e5e7da44e533" - integrity sha512-er9rVClS4PHztMUmtPMDTl+7c7JyrxweKSAEe/o/Noeq2bQx6v3/jZHVHBe8ZNUti5ubJL/+Tg8L3bzmlalV8A== - dependencies: - "@volar/typescript" "1.4.1-patch.2" - "@volar/vue-language-core" "1.6.5" + "@volar/language-core" "1.7.9" "@vue/compiler-core@3.2.47": version "3.2.47" @@ -563,14 +540,6 @@ "@vue/compiler-core" "3.2.47" "@vue/shared" "3.2.47" -"@vue/compiler-dom@3.3.2", "@vue/compiler-dom@^3.3.0": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.2.tgz#2012ef4879375a4ca4ee68012a9256398b848af2" - integrity sha512-6gS3auANuKXLw0XH6QxkWqyPYPunziS2xb6VRenM3JY7gVfZcJvkCBHkb5RuNY1FCbBO3lkIi0CdXUCW1c7SXw== - dependencies: - "@vue/compiler-core" "3.3.2" - "@vue/shared" "3.3.2" - "@vue/compiler-dom@3.3.4": version "3.3.4" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz#f56e09b5f4d7dc350f981784de9713d823341151" @@ -579,6 +548,14 @@ "@vue/compiler-core" "3.3.4" "@vue/shared" "3.3.4" +"@vue/compiler-dom@^3.3.0": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.2.tgz#2012ef4879375a4ca4ee68012a9256398b848af2" + integrity sha512-6gS3auANuKXLw0XH6QxkWqyPYPunziS2xb6VRenM3JY7gVfZcJvkCBHkb5RuNY1FCbBO3lkIi0CdXUCW1c7SXw== + dependencies: + "@vue/compiler-core" "3.3.2" + "@vue/shared" "3.3.2" + "@vue/compiler-sfc@3.3.4": version "3.3.4" resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz#b19d942c71938893535b46226d602720593001df" @@ -611,22 +588,6 @@ postcss "^8.1.10" source-map "^0.6.1" -"@vue/compiler-sfc@^3.3.0": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.2.tgz#d6467acba8446655bcee7e751441232e5ddebcbf" - integrity sha512-jG4jQy28H4BqzEKsQqqW65BZgmo3vzdLHTBjF+35RwtDdlFE+Fk1VWJYUnDMMqkFBo6Ye1ltSKVOMPgkzYj7SQ== - dependencies: - "@babel/parser" "^7.20.15" - "@vue/compiler-core" "3.3.2" - "@vue/compiler-dom" "3.3.2" - "@vue/compiler-ssr" "3.3.2" - "@vue/reactivity-transform" "3.3.2" - "@vue/shared" "3.3.2" - estree-walker "^2.0.2" - magic-string "^0.30.0" - postcss "^8.1.10" - source-map-js "^1.0.2" - "@vue/compiler-ssr@3.2.47": version "3.2.47" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee" @@ -635,14 +596,6 @@ "@vue/compiler-dom" "3.2.47" "@vue/shared" "3.2.47" -"@vue/compiler-ssr@3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.2.tgz#75ac4ccafa2d78c91d2e257ad243c86409493cc4" - integrity sha512-K8OfY5FQtZaSOJHHe8xhEfIfLrefL/Y9frv4k4NsyQL3+0lRKxr9QuJhfdBDjkl7Fhz8CzKh63mULvmOfx3l2w== - dependencies: - "@vue/compiler-dom" "3.3.2" - "@vue/shared" "3.3.2" - "@vue/compiler-ssr@3.3.4": version "3.3.4" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz#9d1379abffa4f2b0cd844174ceec4a9721138777" @@ -670,6 +623,20 @@ "@typescript-eslint/parser" "^5.59.1" vue-eslint-parser "^9.1.1" +"@vue/language-core@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.2.tgz#560c1df639394a5df0043f2d7989dc2fba5bf4ca" + integrity sha512-QJujhmp89TRoWwzjn2sPMezG97+mNyaCTfznGHWNCE3LBsillZCBqAO7M7cxO8ee1V3r+qHjWytkoh3M4YkRJw== + dependencies: + "@volar/language-core" "1.7.9" + "@volar/source-map" "1.7.9" + "@vue/compiler-dom" "^3.3.0" + "@vue/reactivity" "^3.3.0" + "@vue/shared" "^3.3.0" + minimatch "^9.0.0" + muggle-string "^0.3.1" + vue-template-compiler "^2.7.14" + "@vue/reactivity-transform@3.2.47": version "3.2.47" resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e" @@ -681,17 +648,6 @@ estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity-transform@3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.2.tgz#e1991d52d7ecefb65b214d8a3385a9dbe2cca74c" - integrity sha512-iu2WaQvlJHdnONrsyv4ibIEnSsuKF+aHFngGj/y1lwpHQtalpVhKg9wsKMoiKXS9zPNjG9mNKzJS9vudvjzvyg== - dependencies: - "@babel/parser" "^7.20.15" - "@vue/compiler-core" "3.3.2" - "@vue/shared" "3.3.2" - estree-walker "^2.0.2" - magic-string "^0.30.0" - "@vue/reactivity-transform@3.3.4": version "3.3.4" resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929" @@ -762,6 +718,14 @@ resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.4.0.tgz#f01e2f6089b5098136fb084a0dd0cdd4533b72b0" integrity sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg== +"@vue/typescript@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vue/typescript/-/typescript-1.8.2.tgz#d81e2c16197e8fcf032fe766d79952cb9a629af7" + integrity sha512-5q2gpCBIfGlygfJupyjAQbc82r5J6qQuhupPeX3NlHvDK+mR6m5n4jVGknSacsp+7gtgs4RKYDq+tysoto+npA== + dependencies: + "@volar/typescript" "1.7.9" + "@vue/language-core" "1.8.2" + acorn-jsx@^5.2.0, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1125,10 +1089,10 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-plugin-vue@^9.14.1: - version "9.14.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz#3b0c9857642dac547c7564031cfb09d283eafdd4" - integrity sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw== +eslint-plugin-vue@^9.15.1: + version "9.15.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz#3c09e0edab444b5d4d9239a12a645a0e2e2ea5be" + integrity sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A== dependencies: "@eslint-community/eslint-utils" "^4.3.0" natural-compare "^1.4.0" @@ -1184,16 +1148,16 @@ eslint-visitor-keys@^3.4.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@^8.41.0: - version "8.41.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.41.0.tgz#3062ca73363b4714b16dbc1e60f035e6134b6f1c" - integrity sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q== +eslint@^8.43.0: + version "8.43.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.43.0.tgz#3e8c6066a57097adfd9d390b8fc93075f257a094" + integrity sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" "@eslint/eslintrc" "^2.0.3" - "@eslint/js" "8.41.0" - "@humanwhocodes/config-array" "^0.11.8" + "@eslint/js" "8.43.0" + "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" @@ -1882,10 +1846,10 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -muggle-string@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.2.2.tgz#786aa53fea1652c61c6a59e1f839292b262bc72a" - integrity sha512-YVE1mIJ4VpUMqZObFndk9CJu6DBJR/GB13p3tXuNbwD4XExaI5EOuRl6BHeIDxIqXZVxSfAC+y6U1Z/IxCfKUg== +muggle-string@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.3.1.tgz#e524312eb1728c63dd0b2ac49e3282e6ed85963a" + integrity sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg== nanoid@^3.3.4: version "3.3.4" @@ -2220,10 +2184,10 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -sass@^1.62.1: - version "1.62.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.62.1.tgz#caa8d6bf098935bc92fc73fa169fb3790cacd029" - integrity sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A== +sass@^1.63.6: + version "1.63.6" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.6.tgz#481610e612902e0c31c46b46cf2dad66943283ea" + integrity sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -2418,10 +2382,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -terser@^5.17.7: - version "5.17.7" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.7.tgz#2a8b134826fe179b711969fd9d9a0c2479b2a8c3" - integrity sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ== +terser@^5.18.2: + version "5.18.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.2.tgz#ff3072a0faf21ffd38f99acc9a0ddf7b5f07b948" + integrity sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -2602,13 +2566,13 @@ vue-template-compiler@^2.7.14: de-indent "^1.0.2" he "^1.2.0" -vue-tsc@^1.6.5: - version "1.6.5" - resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.6.5.tgz#cd18804b12087c300b6c9ee2a1da41a63f11103e" - integrity sha512-Wtw3J7CC+JM2OR56huRd5iKlvFWpvDiU+fO1+rqyu4V2nMTotShz4zbOZpW5g9fUOcjnyZYfBo5q5q+D/q27JA== +vue-tsc@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.2.tgz#dd9b0edfed10b590a82f964969e12d7733a282c4" + integrity sha512-iLS+z7jzxEAZRGLo4bYWfzZeBNVA71uXKmT2+5bQSsOJBtGLniR45sVpR+X7sa0m3J8iMEIU5+phLB7FnMLS0A== dependencies: - "@volar/vue-language-core" "1.6.5" - "@volar/vue-typescript" "1.6.5" + "@vue/language-core" "1.8.2" + "@vue/typescript" "1.8.2" semver "^7.3.8" vue@^3.3.4: diff --git a/webapp_dist/favicon.png b/webapp_dist/favicon.png new file mode 100644 index 000000000..3378b6613 Binary files /dev/null and b/webapp_dist/favicon.png differ diff --git a/webapp_dist/index.html.gz b/webapp_dist/index.html.gz index d0a8c5248..cc2cb3815 100644 Binary files a/webapp_dist/index.html.gz and b/webapp_dist/index.html.gz differ diff --git a/webapp_dist/js/app.js.gz b/webapp_dist/js/app.js.gz index 47414dc30..d5965f1ed 100644 Binary files a/webapp_dist/js/app.js.gz and b/webapp_dist/js/app.js.gz differ