Merge remote-tracking branch 'tbnobody/OpenDTU/master'
This commit is contained in:
commit
9a7a0d293e
@ -66,7 +66,7 @@ Sends text raw data as difined in VE.Direct spec.
|
|||||||
**TSUN compatibility remark:**
|
**TSUN compatibility remark:**
|
||||||
Compatibility with OpenDTU seems to be related to serial numbers. Current findings indicate that TSUN inverters with a serial number starting with "11" are supported, whereby inverters with a serial number starting with "10" are not.
|
Compatibility with OpenDTU seems to be related to serial numbers. Current findings indicate that TSUN inverters with a serial number starting with "11" are supported, whereby inverters with a serial number starting with "10" are not.
|
||||||
Firmware version seems to play not a significant role and cannot be read from the stickers. For completeness, the following firmware version have been reported to work with OpenDTU:
|
Firmware version seems to play not a significant role and cannot be read from the stickers. For completeness, the following firmware version have been reported to work with OpenDTU:
|
||||||
* v1.0.10 TSOL-M800 (DE)
|
* v1.0.8, v1.0.10 TSOL-M800 (DE)
|
||||||
* v1.0.12 TSOL-M1600
|
* v1.0.12 TSOL-M1600
|
||||||
|
|
||||||
## Features for end users
|
## Features for end users
|
||||||
@ -86,7 +86,7 @@ Firmware version seems to play not a significant role and cannot be read from th
|
|||||||
* Ve.Direct interface (via web-interface, REST-api, or MQTT)
|
* Ve.Direct interface (via web-interface, REST-api, or MQTT)
|
||||||
* Ethernet support
|
* Ethernet support
|
||||||
* Prometheus API endpoint (/api/prometheus/metrics)
|
* Prometheus API endpoint (/api/prometheus/metrics)
|
||||||
* English and german web interface
|
* English, german and french web interface
|
||||||
|
|
||||||
## Features for developers
|
## Features for developers
|
||||||
* The microcontroller part
|
* The microcontroller part
|
||||||
@ -151,6 +151,7 @@ This can be achieved by copying one of the [env:....] sections from 'platformio.
|
|||||||
-DVICTRON_PIN_RX=22
|
-DVICTRON_PIN_RX=22
|
||||||
```
|
```
|
||||||
It is recommended to make all changes only in the 'platformio_override.ini', this is your personal copy.
|
It is recommended to make all changes only in the 'platformio_override.ini', this is your personal copy.
|
||||||
|
You can also change the pins by creating a custom [device profile](docs/DeviceProfiles.md).
|
||||||
|
|
||||||
## Flashing and starting up
|
## Flashing and starting up
|
||||||
### with Visual Studio Code
|
### with Visual Studio Code
|
||||||
@ -216,6 +217,9 @@ esptool.py --port /dev/ttyUSB0 --chip esp32 --before default_reset --after hard_
|
|||||||
#### Flash with ESP_Flasher (Windows)
|
#### Flash with ESP_Flasher (Windows)
|
||||||
Users report that [ESP_Flasher](https://github.com/Jason2866/ESP_Flasher/releases/) is suitable for flashing OpenDTU on Windows.
|
Users report that [ESP_Flasher](https://github.com/Jason2866/ESP_Flasher/releases/) is suitable for flashing OpenDTU on Windows.
|
||||||
|
|
||||||
|
#### Flash with [ESP_Flasher](https://espressif.github.io/esptool-js/) - web version
|
||||||
|
It is also possible to flash it via the web tools which might be more convenient and is platformindependent.
|
||||||
|
|
||||||
## First configuration
|
## First configuration
|
||||||
* After the initial flashing of the microcontroller, an Access Point called "OpenDTU-*" is opened. The default password is "openDTU42".
|
* After the initial flashing of the microcontroller, an Access Point called "OpenDTU-*" is opened. The default password is "openDTU42".
|
||||||
* Use a web browser to open the address [http://192.168.4.1](http://192.168.4.1)
|
* Use a web browser to open the address [http://192.168.4.1](http://192.168.4.1)
|
||||||
|
|||||||
56
docs/DeviceProfiles.md
Normal file
56
docs/DeviceProfiles.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Device Profiles
|
||||||
|
|
||||||
|
It is possible to change hardware settings like pin assignments or ethernet support using a json file. The json file can be uploaded using the configuration management in the web interface. Just select "Pin Mapping (pin_mapping.json)" in the recovery section.
|
||||||
|
|
||||||
|
When the file is uploaded the ESP performs a reboot. This is required as the pin settings could have changed within the file. By default all the pin assignments are used as compiled into the firmware.
|
||||||
|
|
||||||
|
To change the device profile, navigate to the "Device Manager" and selected the appropriate profile. You can see the current (Active) and the new (Selected) in assignment in the table below the combobox.
|
||||||
|
|
||||||
|
## Structure of the json file
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Generic NodeMCU 38 pin",
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 19,
|
||||||
|
"mosi": 23,
|
||||||
|
"clk": 18,
|
||||||
|
"irq": 16,
|
||||||
|
"en": 4,
|
||||||
|
"cs": 5
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": false,
|
||||||
|
"phy_addr": -1,
|
||||||
|
"power": -1,
|
||||||
|
"mdc": -1,
|
||||||
|
"mdio": -1,
|
||||||
|
"type": -1,
|
||||||
|
"clk_mode": -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Olimex ESP32-POE",
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 15,
|
||||||
|
"mosi": 2,
|
||||||
|
"clk": 14,
|
||||||
|
"irq": 13,
|
||||||
|
"en": 16,
|
||||||
|
"cs": 5
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": true,
|
||||||
|
"phy_addr": 0,
|
||||||
|
"power": 12,
|
||||||
|
"mdc": 23,
|
||||||
|
"mdio": 18,
|
||||||
|
"type": 0,
|
||||||
|
"clk_mode": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The json file can contain multiple profiles. Each profile requires a name and different parameters. If one parameter is not set, the default value, as compiled into the firmware is used. The example above shows all the currently supported values. Others may follow. A sample file with a lot of boards can be found [here](pin_mapping.json). This means you can just flash the generic bin file and upload the pin_mapping.json file. Then you select your board and everything works hopyfully as expected.
|
||||||
@ -4,4 +4,5 @@ More detailed descriptions for some topics can be found here.
|
|||||||
|
|
||||||
## [MQTT Topic Documentation](MQTT_Topics.md)
|
## [MQTT Topic Documentation](MQTT_Topics.md)
|
||||||
## [Web API Documentation](Web-API.md)
|
## [Web API Documentation](Web-API.md)
|
||||||
|
## [Device Profile Documentation](DeviceProfiles.md)
|
||||||
## [Builds](builds/README.md)
|
## [Builds](builds/README.md)
|
||||||
122
docs/pin_mapping.json
Normal file
122
docs/pin_mapping.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Generic NodeMCU 38 pin",
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 19,
|
||||||
|
"mosi": 23,
|
||||||
|
"clk": 18,
|
||||||
|
"irq": 16,
|
||||||
|
"en": 4,
|
||||||
|
"cs": 5
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": false,
|
||||||
|
"phy_addr": -1,
|
||||||
|
"power": -1,
|
||||||
|
"mdc": -1,
|
||||||
|
"mdio": -1,
|
||||||
|
"type": -1,
|
||||||
|
"clk_mode": -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Olimex ESP32-POE",
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 15,
|
||||||
|
"mosi": 2,
|
||||||
|
"clk": 14,
|
||||||
|
"irq": 13,
|
||||||
|
"en": 16,
|
||||||
|
"cs": 5
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": true,
|
||||||
|
"phy_addr": 0,
|
||||||
|
"power": 12,
|
||||||
|
"mdc": 23,
|
||||||
|
"mdio": 18,
|
||||||
|
"type": 0,
|
||||||
|
"clk_mode": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Olimex ESP32-EVB",
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 15,
|
||||||
|
"mosi": 2,
|
||||||
|
"clk": 14,
|
||||||
|
"irq": 13,
|
||||||
|
"en": 16,
|
||||||
|
"cs": 17
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": true,
|
||||||
|
"phy_addr": 0,
|
||||||
|
"power": 12,
|
||||||
|
"mdc": 23,
|
||||||
|
"mdio": 18,
|
||||||
|
"type": 0,
|
||||||
|
"clk_mode": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generic NodeMCU 30 pin",
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 19,
|
||||||
|
"mosi": 23,
|
||||||
|
"clk": 18,
|
||||||
|
"irq": 16,
|
||||||
|
"en": 17,
|
||||||
|
"cs": 5
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": false,
|
||||||
|
"phy_addr": -1,
|
||||||
|
"power": -1,
|
||||||
|
"mdc": -1,
|
||||||
|
"mdio": -1,
|
||||||
|
"type": -1,
|
||||||
|
"clk_mode": -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WT32-ETH01",
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 4,
|
||||||
|
"mosi": 2,
|
||||||
|
"clk": 32,
|
||||||
|
"irq": 33,
|
||||||
|
"en": 14,
|
||||||
|
"cs": 15
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": true,
|
||||||
|
"phy_addr": 1,
|
||||||
|
"power": 16,
|
||||||
|
"mdc": 23,
|
||||||
|
"mdio": 18,
|
||||||
|
"type": 0,
|
||||||
|
"clk_mode": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LILYGO TTGO T-Internet-POE",
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 2,
|
||||||
|
"mosi": 15,
|
||||||
|
"clk": 14,
|
||||||
|
"irq": 34,
|
||||||
|
"en": 12,
|
||||||
|
"cs": 4
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": true,
|
||||||
|
"phy_addr": 0,
|
||||||
|
"power": -1,
|
||||||
|
"mdc": 23,
|
||||||
|
"mdio": 18,
|
||||||
|
"type": 0,
|
||||||
|
"clk_mode": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -19,7 +19,7 @@
|
|||||||
#define MQTT_MAX_PASSWORD_STRLEN 64
|
#define MQTT_MAX_PASSWORD_STRLEN 64
|
||||||
#define MQTT_MAX_TOPIC_STRLEN 32
|
#define MQTT_MAX_TOPIC_STRLEN 32
|
||||||
#define MQTT_MAX_LWTVALUE_STRLEN 20
|
#define MQTT_MAX_LWTVALUE_STRLEN 20
|
||||||
#define MQTT_MAX_ROOT_CA_CERT_STRLEN 2048
|
#define MQTT_MAX_ROOT_CA_CERT_STRLEN 2560
|
||||||
|
|
||||||
#define INV_MAX_NAME_STRLEN 31
|
#define INV_MAX_NAME_STRLEN 31
|
||||||
#define INV_MAX_COUNT 10
|
#define INV_MAX_COUNT 10
|
||||||
@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#define CHAN_MAX_NAME_STRLEN 31
|
#define CHAN_MAX_NAME_STRLEN 31
|
||||||
|
|
||||||
|
#define DEV_MAX_MAPPING_NAME_STRLEN 31
|
||||||
|
|
||||||
#define JSON_BUFFER_SIZE 6144
|
#define JSON_BUFFER_SIZE 6144
|
||||||
|
|
||||||
struct CHANNEL_CONFIG_T {
|
struct CHANNEL_CONFIG_T {
|
||||||
@ -92,6 +94,8 @@ struct CONFIG_T {
|
|||||||
|
|
||||||
char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
|
char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
|
||||||
bool Security_AllowReadonly;
|
bool Security_AllowReadonly;
|
||||||
|
|
||||||
|
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConfigurationClass {
|
class ConfigurationClass {
|
||||||
|
|||||||
42
include/PinMapping.h
Normal file
42
include/PinMapping.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <ETH.h>
|
||||||
|
|
||||||
|
#define PINMAPPING_FILENAME "/pin_mapping.json"
|
||||||
|
|
||||||
|
#define MAPPING_NAME_STRLEN 31
|
||||||
|
|
||||||
|
struct PinMapping_t {
|
||||||
|
char name[MAPPING_NAME_STRLEN + 1];
|
||||||
|
int8_t nrf24_miso;
|
||||||
|
int8_t nrf24_mosi;
|
||||||
|
int8_t nrf24_clk;
|
||||||
|
int8_t nrf24_irq;
|
||||||
|
int8_t nrf24_en;
|
||||||
|
int8_t nrf24_cs;
|
||||||
|
int8_t eth_phy_addr;
|
||||||
|
bool eth_enabled;
|
||||||
|
int eth_power;
|
||||||
|
int eth_mdc;
|
||||||
|
int eth_mdio;
|
||||||
|
eth_phy_type_t eth_type;
|
||||||
|
eth_clock_mode_t eth_clk_mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PinMappingClass {
|
||||||
|
public:
|
||||||
|
PinMappingClass();
|
||||||
|
bool init(const String& deviceMapping);
|
||||||
|
PinMapping_t& get();
|
||||||
|
|
||||||
|
bool isValidNrf24Config();
|
||||||
|
bool isValidEthConfig();
|
||||||
|
|
||||||
|
private:
|
||||||
|
PinMapping_t _pinMapping;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern PinMappingClass PinMapping;
|
||||||
@ -6,6 +6,7 @@
|
|||||||
#include "WebApi_dtu.h"
|
#include "WebApi_dtu.h"
|
||||||
#include "WebApi_eventlog.h"
|
#include "WebApi_eventlog.h"
|
||||||
#include "WebApi_firmware.h"
|
#include "WebApi_firmware.h"
|
||||||
|
#include "WebApi_device.h"
|
||||||
#include "WebApi_inverter.h"
|
#include "WebApi_inverter.h"
|
||||||
#include "WebApi_limit.h"
|
#include "WebApi_limit.h"
|
||||||
#include "WebApi_maintenance.h"
|
#include "WebApi_maintenance.h"
|
||||||
@ -37,6 +38,7 @@ private:
|
|||||||
AsyncEventSource _events;
|
AsyncEventSource _events;
|
||||||
|
|
||||||
WebApiConfigClass _webApiConfig;
|
WebApiConfigClass _webApiConfig;
|
||||||
|
WebApiDeviceClass _webApiDevice;
|
||||||
WebApiDevInfoClass _webApiDevInfo;
|
WebApiDevInfoClass _webApiDevInfo;
|
||||||
WebApiDtuClass _webApiDtu;
|
WebApiDtuClass _webApiDtu;
|
||||||
WebApiEventlogClass _webApiEventlog;
|
WebApiEventlogClass _webApiEventlog;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
void onConfigGet(AsyncWebServerRequest* request);
|
void onConfigGet(AsyncWebServerRequest* request);
|
||||||
void onConfigDelete(AsyncWebServerRequest* request);
|
void onConfigDelete(AsyncWebServerRequest* request);
|
||||||
|
void onConfigListGet(AsyncWebServerRequest* request);
|
||||||
void onConfigUploadFinish(AsyncWebServerRequest* request);
|
void onConfigUploadFinish(AsyncWebServerRequest* request);
|
||||||
void onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final);
|
void onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final);
|
||||||
|
|
||||||
|
|||||||
16
include/WebApi_device.h
Normal file
16
include/WebApi_device.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
class WebApiDeviceClass {
|
||||||
|
public:
|
||||||
|
void init(AsyncWebServer* server);
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onDeviceAdminGet(AsyncWebServerRequest* request);
|
||||||
|
void onDeviceAdminPost(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
|
AsyncWebServer* _server;
|
||||||
|
};
|
||||||
@ -81,4 +81,7 @@ enum WebApiError {
|
|||||||
PowerBase = 11000,
|
PowerBase = 11000,
|
||||||
PowerSerialZero,
|
PowerSerialZero,
|
||||||
PowerInvalidInverter,
|
PowerInvalidInverter,
|
||||||
|
|
||||||
|
HardwareBase = 12000,
|
||||||
|
HardwarePinMappingLength,
|
||||||
};
|
};
|
||||||
@ -79,6 +79,8 @@
|
|||||||
#define MQTT_HASS_TOPIC "homeassistant/"
|
#define MQTT_HASS_TOPIC "homeassistant/"
|
||||||
#define MQTT_HASS_INDIVIDUALPANELS false
|
#define MQTT_HASS_INDIVIDUALPANELS false
|
||||||
|
|
||||||
|
#define DEV_PINMAPPING ""
|
||||||
|
|
||||||
#define VEDIRECT_ENABLED false
|
#define VEDIRECT_ENABLED false
|
||||||
#define VEDIRECT_UPDATESONLY true
|
#define VEDIRECT_UPDATESONLY true
|
||||||
#define VEDIRECT_POLL_INTERVAL 5
|
#define VEDIRECT_POLL_INTERVAL 5
|
||||||
@ -108,12 +108,12 @@ void InverterAbstract::clearRxFragmentBuffer()
|
|||||||
void InverterAbstract::addRxFragment(uint8_t fragment[], uint8_t len)
|
void InverterAbstract::addRxFragment(uint8_t fragment[], uint8_t len)
|
||||||
{
|
{
|
||||||
if (len < 11) {
|
if (len < 11) {
|
||||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too short\n", __FILE__, __LINE__);
|
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too short\r\n", __FILE__, __LINE__);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len - 11 > MAX_RF_PAYLOAD_SIZE) {
|
if (len - 11 > MAX_RF_PAYLOAD_SIZE) {
|
||||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too large\n", __FILE__, __LINE__);
|
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too large\r\n", __FILE__, __LINE__);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ void AlarmLogParser::clearBuffer()
|
|||||||
void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||||
{
|
{
|
||||||
if (offset + len > ALARM_LOG_PAYLOAD_SIZE) {
|
if (offset + len > ALARM_LOG_PAYLOAD_SIZE) {
|
||||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer (%d > %d)\n", __FILE__, __LINE__, offset + len, ALARM_LOG_PAYLOAD_SIZE);
|
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer (%d > %d)\r\n", __FILE__, __LINE__, offset + len, ALARM_LOG_PAYLOAD_SIZE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(&_payloadAlarmLog[offset], payload, len);
|
memcpy(&_payloadAlarmLog[offset], payload, len);
|
||||||
|
|||||||
@ -37,7 +37,7 @@ void DevInfoParser::clearBufferAll()
|
|||||||
void DevInfoParser::appendFragmentAll(uint8_t offset, uint8_t* payload, uint8_t len)
|
void DevInfoParser::appendFragmentAll(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||||
{
|
{
|
||||||
if (offset + len > DEV_INFO_SIZE) {
|
if (offset + len > DEV_INFO_SIZE) {
|
||||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) dev info all packet too large for buffer\n", __FILE__, __LINE__);
|
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) dev info all packet too large for buffer\r\n", __FILE__, __LINE__);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(&_payloadDevInfoAll[offset], payload, len);
|
memcpy(&_payloadDevInfoAll[offset], payload, len);
|
||||||
@ -53,7 +53,7 @@ void DevInfoParser::clearBufferSimple()
|
|||||||
void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len)
|
void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||||
{
|
{
|
||||||
if (offset + len > DEV_INFO_SIZE) {
|
if (offset + len > DEV_INFO_SIZE) {
|
||||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) dev info Simple packet too large for buffer\n", __FILE__, __LINE__);
|
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) dev info Simple packet too large for buffer\r\n", __FILE__, __LINE__);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(&_payloadDevInfoSimple[offset], payload, len);
|
memcpy(&_payloadDevInfoSimple[offset], payload, len);
|
||||||
|
|||||||
@ -43,7 +43,7 @@ void StatisticsParser::clearBuffer()
|
|||||||
void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||||
{
|
{
|
||||||
if (offset + len > STATISTIC_PACKET_SIZE) {
|
if (offset + len > STATISTIC_PACKET_SIZE) {
|
||||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer\n", __FILE__, __LINE__);
|
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer\r\n", __FILE__, __LINE__);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(&_payloadStatistic[offset], payload, len);
|
memcpy(&_payloadStatistic[offset], payload, len);
|
||||||
|
|||||||
@ -15,7 +15,7 @@ void SystemConfigParaParser::clearBuffer()
|
|||||||
void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
|
||||||
{
|
{
|
||||||
if (offset + len > (SYSTEM_CONFIG_PARA_SIZE)) {
|
if (offset + len > (SYSTEM_CONFIG_PARA_SIZE)) {
|
||||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer\n", __FILE__, __LINE__);
|
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer\r\n", __FILE__, __LINE__);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(&_payload[offset], payload, len);
|
memcpy(&_payload[offset], payload, len);
|
||||||
|
|||||||
@ -34,15 +34,19 @@ String ResetReasonClass::get_reset_reason_verbose(uint8_t cpu_id)
|
|||||||
case 3:
|
case 3:
|
||||||
reason_str = F("Software reset digital core");
|
reason_str = F("Software reset digital core");
|
||||||
break;
|
break;
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||||
case 4:
|
case 4:
|
||||||
reason_str = F("Legacy watch dog reset digital core");
|
reason_str = F("Legacy watch dog reset digital core");
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case 5:
|
case 5:
|
||||||
reason_str = F("Deep Sleep reset digital core");
|
reason_str = F("Deep Sleep reset digital core");
|
||||||
break;
|
break;
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||||
case 6:
|
case 6:
|
||||||
reason_str = F("Reset by SLC module, reset digital core");
|
reason_str = F("Reset by SLC module, reset digital core");
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case 7:
|
case 7:
|
||||||
reason_str = F("Timer Group0 Watch dog reset digital core");
|
reason_str = F("Timer Group0 Watch dog reset digital core");
|
||||||
break;
|
break;
|
||||||
@ -64,9 +68,11 @@ String ResetReasonClass::get_reset_reason_verbose(uint8_t cpu_id)
|
|||||||
case 13:
|
case 13:
|
||||||
reason_str = F("RTC Watch dog Reset CPU");
|
reason_str = F("RTC Watch dog Reset CPU");
|
||||||
break;
|
break;
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||||
case 14:
|
case 14:
|
||||||
reason_str = F("for APP CPU, reseted by PRO CPU");
|
reason_str = F("for APP CPU, reseted by PRO CPU");
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case 15:
|
case 15:
|
||||||
reason_str = F("Reset when the vdd voltage is not stable");
|
reason_str = F("Reset when the vdd voltage is not stable");
|
||||||
break;
|
break;
|
||||||
@ -94,15 +100,19 @@ String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id)
|
|||||||
case 3:
|
case 3:
|
||||||
reason_str = F("SW_RESET");
|
reason_str = F("SW_RESET");
|
||||||
break;
|
break;
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||||
case 4:
|
case 4:
|
||||||
reason_str = F("OWDT_RESET");
|
reason_str = F("OWDT_RESET");
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case 5:
|
case 5:
|
||||||
reason_str = F("DEEPSLEEP_RESET");
|
reason_str = F("DEEPSLEEP_RESET");
|
||||||
break;
|
break;
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||||
case 6:
|
case 6:
|
||||||
reason_str = F("SDIO_RESET");
|
reason_str = F("SDIO_RESET");
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case 7:
|
case 7:
|
||||||
reason_str = F("TG0WDT_SYS_RESET");
|
reason_str = F("TG0WDT_SYS_RESET");
|
||||||
break;
|
break;
|
||||||
@ -124,9 +134,11 @@ String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id)
|
|||||||
case 13:
|
case 13:
|
||||||
reason_str = F("RTCWDT_CPU_RESET");
|
reason_str = F("RTCWDT_CPU_RESET");
|
||||||
break;
|
break;
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||||
case 14:
|
case 14:
|
||||||
reason_str = F("EXT_CPU_RESET");
|
reason_str = F("EXT_CPU_RESET");
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case 15:
|
case 15:
|
||||||
reason_str = F("RTCWDT_BROWN_OUT_RESET");
|
reason_str = F("RTCWDT_BROWN_OUT_RESET");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -15,16 +15,15 @@ extra_configs =
|
|||||||
|
|
||||||
[env]
|
[env]
|
||||||
framework = arduino
|
framework = arduino
|
||||||
platform = espressif32@>=5.3.0
|
platform = espressif32@>=6.0.0
|
||||||
|
|
||||||
build_flags =
|
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
|
-DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz
|
||||||
-Wall -Wextra -Werror
|
-Wall -Wextra -Werror
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/yubox-node-org/ESPAsyncWebServer
|
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||||
bblanchon/ArduinoJson @ ^6.19.4
|
bblanchon/ArduinoJson @ ^6.20.0
|
||||||
https://github.com/bertmelis/espMqttClient.git#v1.3.3
|
https://github.com/bertmelis/espMqttClient.git#v1.3.3
|
||||||
nrf24/RF24 @ ^1.4.5
|
nrf24/RF24 @ ^1.4.5
|
||||||
|
|
||||||
|
|||||||
@ -80,6 +80,9 @@ bool ConfigurationClass::write()
|
|||||||
security["password"] = config.Security_Password;
|
security["password"] = config.Security_Password;
|
||||||
security["allow_readonly"] = config.Security_AllowReadonly;
|
security["allow_readonly"] = config.Security_AllowReadonly;
|
||||||
|
|
||||||
|
JsonObject device = doc.createNestedObject("device");
|
||||||
|
device["pinmapping"] = config.Dev_PinMapping;
|
||||||
|
|
||||||
JsonArray inverters = doc.createNestedArray("inverters");
|
JsonArray inverters = doc.createNestedArray("inverters");
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
JsonObject inv = inverters.createNestedObject();
|
JsonObject inv = inverters.createNestedObject();
|
||||||
@ -206,6 +209,9 @@ bool ConfigurationClass::read()
|
|||||||
strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password));
|
strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password));
|
||||||
config.Security_AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY;
|
config.Security_AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY;
|
||||||
|
|
||||||
|
JsonObject device = doc["device"];
|
||||||
|
strlcpy(config.Dev_PinMapping, device["pinmapping"] | DEV_PINMAPPING, sizeof(config.Dev_PinMapping));
|
||||||
|
|
||||||
JsonArray inverters = doc["inverters"];
|
JsonArray inverters = doc["inverters"];
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
JsonObject inv = inverters[i].as<JsonObject>();
|
JsonObject inv = inverters[i].as<JsonObject>();
|
||||||
|
|||||||
@ -186,17 +186,17 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
||||||
// Set inverter limit relative persistent
|
// Set inverter limit relative persistent
|
||||||
MessageOutput.printf("Limit Persistent: %d %%\n", payload_val);
|
MessageOutput.printf("Limit Persistent: %d %%\r\n", payload_val);
|
||||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativPersistent);
|
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativPersistent);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
||||||
// Set inverter limit absolute persistent
|
// Set inverter limit absolute persistent
|
||||||
MessageOutput.printf("Limit Persistent: %d W\n", payload_val);
|
MessageOutput.printf("Limit Persistent: %d W\r\n", payload_val);
|
||||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutPersistent);
|
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutPersistent);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
||||||
// Set inverter limit relative non persistent
|
// Set inverter limit relative non persistent
|
||||||
MessageOutput.printf("Limit Non-Persistent: %d %%\n", payload_val);
|
MessageOutput.printf("Limit Non-Persistent: %d %%\r\n", payload_val);
|
||||||
if (!properties.retain) {
|
if (!properties.retain) {
|
||||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativNonPersistent);
|
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::RelativNonPersistent);
|
||||||
} else {
|
} else {
|
||||||
@ -205,7 +205,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
||||||
// Set inverter limit absolute non persistent
|
// Set inverter limit absolute non persistent
|
||||||
MessageOutput.printf("Limit Non-Persistent: %d W\n", payload_val);
|
MessageOutput.printf("Limit Non-Persistent: %d W\r\n", payload_val);
|
||||||
if (!properties.retain) {
|
if (!properties.retain) {
|
||||||
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutNonPersistent);
|
inv->sendActivePowerControlRequest(Hoymiles.getRadio(), payload_val, PowerLimitControlType::AbsolutNonPersistent);
|
||||||
} else {
|
} else {
|
||||||
@ -214,12 +214,12 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
||||||
// Turn inverter on or off
|
// Turn inverter on or off
|
||||||
MessageOutput.printf("Set inverter power to: %d\n", payload_val);
|
MessageOutput.printf("Set inverter power to: %d\r\n", payload_val);
|
||||||
inv->sendPowerControlRequest(Hoymiles.getRadio(), payload_val > 0);
|
inv->sendPowerControlRequest(Hoymiles.getRadio(), payload_val > 0);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
||||||
// Restart inverter
|
// Restart inverter
|
||||||
MessageOutput.printf("Restart inverter\n");
|
MessageOutput.printf("Restart inverter\r\n");
|
||||||
if (!properties.retain && payload_val == 1) {
|
if (!properties.retain && payload_val == 1) {
|
||||||
inv->sendRestartControlRequest(Hoymiles.getRadio());
|
inv->sendRestartControlRequest(Hoymiles.getRadio());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -5,11 +5,10 @@
|
|||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
|
#include "PinMapping.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
#include <ETH.h>
|
#include <ETH.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
NetworkSettingsClass::NetworkSettingsClass()
|
NetworkSettingsClass::NetworkSettingsClass()
|
||||||
: apIp(192, 168, 4, 1)
|
: apIp(192, 168, 4, 1)
|
||||||
@ -29,7 +28,6 @@ void NetworkSettingsClass::init()
|
|||||||
void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
||||||
{
|
{
|
||||||
switch (event) {
|
switch (event) {
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
case ARDUINO_EVENT_ETH_START:
|
case ARDUINO_EVENT_ETH_START:
|
||||||
MessageOutput.println(F("ETH start"));
|
MessageOutput.println(F("ETH start"));
|
||||||
if (_networkMode == network_mode::Ethernet) {
|
if (_networkMode == network_mode::Ethernet) {
|
||||||
@ -48,7 +46,7 @@ void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
|||||||
raiseEvent(network_event::NETWORK_CONNECTED);
|
raiseEvent(network_event::NETWORK_CONNECTED);
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||||
MessageOutput.printf("ETH got IP: %s\n", ETH.localIP().toString().c_str());
|
MessageOutput.printf("ETH got IP: %s\r\n", ETH.localIP().toString().c_str());
|
||||||
if (_networkMode == network_mode::Ethernet) {
|
if (_networkMode == network_mode::Ethernet) {
|
||||||
raiseEvent(network_event::NETWORK_GOT_IP);
|
raiseEvent(network_event::NETWORK_GOT_IP);
|
||||||
}
|
}
|
||||||
@ -60,7 +58,6 @@ void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
|||||||
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
|
||||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||||
MessageOutput.println(F("WiFi connected"));
|
MessageOutput.println(F("WiFi connected"));
|
||||||
if (_networkMode == network_mode::WiFi) {
|
if (_networkMode == network_mode::WiFi) {
|
||||||
@ -76,7 +73,7 @@ void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||||
MessageOutput.printf("WiFi got ip: %s\n", WiFi.localIP().toString().c_str());
|
MessageOutput.printf("WiFi got ip: %s\r\n", WiFi.localIP().toString().c_str());
|
||||||
if (_networkMode == network_mode::WiFi) {
|
if (_networkMode == network_mode::WiFi) {
|
||||||
raiseEvent(network_event::NETWORK_GOT_IP);
|
raiseEvent(network_event::NETWORK_GOT_IP);
|
||||||
}
|
}
|
||||||
@ -129,9 +126,11 @@ void NetworkSettingsClass::setupMode()
|
|||||||
WiFi.mode(WIFI_MODE_NULL);
|
WiFi.mode(WIFI_MODE_NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
ETH.begin();
|
if (PinMapping.isValidEthConfig()) {
|
||||||
#endif
|
PinMapping_t& pin = PinMapping.get();
|
||||||
|
ETH.begin(pin.eth_phy_addr, pin.eth_power, pin.eth_mdc, pin.eth_mdio, pin.eth_type, pin.eth_clk_mode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkSettingsClass::enableAdminMode()
|
void NetworkSettingsClass::enableAdminMode()
|
||||||
@ -148,7 +147,6 @@ String NetworkSettingsClass::getApName()
|
|||||||
|
|
||||||
void NetworkSettingsClass::loop()
|
void NetworkSettingsClass::loop()
|
||||||
{
|
{
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
if (_ethConnected) {
|
if (_ethConnected) {
|
||||||
if (_networkMode != network_mode::Ethernet) {
|
if (_networkMode != network_mode::Ethernet) {
|
||||||
// Do stuff when switching to Ethernet mode
|
// Do stuff when switching to Ethernet mode
|
||||||
@ -159,7 +157,6 @@ void NetworkSettingsClass::loop()
|
|||||||
setHostname();
|
setHostname();
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
#endif
|
|
||||||
if (_networkMode != network_mode::WiFi) {
|
if (_networkMode != network_mode::WiFi) {
|
||||||
// Do stuff when switching to Ethernet mode
|
// Do stuff when switching to Ethernet mode
|
||||||
MessageOutput.println(F("Switch to WiFi mode"));
|
MessageOutput.println(F("Switch to WiFi mode"));
|
||||||
@ -250,7 +247,6 @@ void NetworkSettingsClass::setHostname()
|
|||||||
WiFi.mode(WIFI_MODE_STA);
|
WiFi.mode(WIFI_MODE_STA);
|
||||||
setupMode();
|
setupMode();
|
||||||
}
|
}
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
else if (_networkMode == network_mode::Ethernet) {
|
else if (_networkMode == network_mode::Ethernet) {
|
||||||
if (ETH.setHostname(getHostname().c_str())) {
|
if (ETH.setHostname(getHostname().c_str())) {
|
||||||
MessageOutput.println(F("done"));
|
MessageOutput.println(F("done"));
|
||||||
@ -258,7 +254,6 @@ void NetworkSettingsClass::setHostname()
|
|||||||
MessageOutput.println(F("failed"));
|
MessageOutput.println(F("failed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkSettingsClass::setStaticIp()
|
void NetworkSettingsClass::setStaticIp()
|
||||||
@ -279,7 +274,6 @@ void NetworkSettingsClass::setStaticIp()
|
|||||||
MessageOutput.println(F("done"));
|
MessageOutput.println(F("done"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
else if (_networkMode == network_mode::Ethernet) {
|
else if (_networkMode == network_mode::Ethernet) {
|
||||||
if (Configuration.get().WiFi_Dhcp) {
|
if (Configuration.get().WiFi_Dhcp) {
|
||||||
MessageOutput.print(F("Configuring Ethernet DHCP IP... "));
|
MessageOutput.print(F("Configuring Ethernet DHCP IP... "));
|
||||||
@ -296,17 +290,14 @@ void NetworkSettingsClass::setStaticIp()
|
|||||||
MessageOutput.println(F("done"));
|
MessageOutput.println(F("done"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IPAddress NetworkSettingsClass::localIP()
|
IPAddress NetworkSettingsClass::localIP()
|
||||||
{
|
{
|
||||||
switch (_networkMode) {
|
switch (_networkMode) {
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
case network_mode::Ethernet:
|
case network_mode::Ethernet:
|
||||||
return ETH.localIP();
|
return ETH.localIP();
|
||||||
break;
|
break;
|
||||||
#endif
|
|
||||||
case network_mode::WiFi:
|
case network_mode::WiFi:
|
||||||
return WiFi.localIP();
|
return WiFi.localIP();
|
||||||
break;
|
break;
|
||||||
@ -318,11 +309,9 @@ IPAddress NetworkSettingsClass::localIP()
|
|||||||
IPAddress NetworkSettingsClass::subnetMask()
|
IPAddress NetworkSettingsClass::subnetMask()
|
||||||
{
|
{
|
||||||
switch (_networkMode) {
|
switch (_networkMode) {
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
case network_mode::Ethernet:
|
case network_mode::Ethernet:
|
||||||
return ETH.subnetMask();
|
return ETH.subnetMask();
|
||||||
break;
|
break;
|
||||||
#endif
|
|
||||||
case network_mode::WiFi:
|
case network_mode::WiFi:
|
||||||
return WiFi.subnetMask();
|
return WiFi.subnetMask();
|
||||||
break;
|
break;
|
||||||
@ -334,11 +323,9 @@ IPAddress NetworkSettingsClass::subnetMask()
|
|||||||
IPAddress NetworkSettingsClass::gatewayIP()
|
IPAddress NetworkSettingsClass::gatewayIP()
|
||||||
{
|
{
|
||||||
switch (_networkMode) {
|
switch (_networkMode) {
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
case network_mode::Ethernet:
|
case network_mode::Ethernet:
|
||||||
return ETH.gatewayIP();
|
return ETH.gatewayIP();
|
||||||
break;
|
break;
|
||||||
#endif
|
|
||||||
case network_mode::WiFi:
|
case network_mode::WiFi:
|
||||||
return WiFi.gatewayIP();
|
return WiFi.gatewayIP();
|
||||||
break;
|
break;
|
||||||
@ -350,11 +337,9 @@ IPAddress NetworkSettingsClass::gatewayIP()
|
|||||||
IPAddress NetworkSettingsClass::dnsIP(uint8_t dns_no)
|
IPAddress NetworkSettingsClass::dnsIP(uint8_t dns_no)
|
||||||
{
|
{
|
||||||
switch (_networkMode) {
|
switch (_networkMode) {
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
case network_mode::Ethernet:
|
case network_mode::Ethernet:
|
||||||
return ETH.dnsIP(dns_no);
|
return ETH.dnsIP(dns_no);
|
||||||
break;
|
break;
|
||||||
#endif
|
|
||||||
case network_mode::WiFi:
|
case network_mode::WiFi:
|
||||||
return WiFi.dnsIP(dns_no);
|
return WiFi.dnsIP(dns_no);
|
||||||
break;
|
break;
|
||||||
@ -366,11 +351,9 @@ IPAddress NetworkSettingsClass::dnsIP(uint8_t dns_no)
|
|||||||
String NetworkSettingsClass::macAddress()
|
String NetworkSettingsClass::macAddress()
|
||||||
{
|
{
|
||||||
switch (_networkMode) {
|
switch (_networkMode) {
|
||||||
#ifdef OPENDTU_ETHERNET
|
|
||||||
case network_mode::Ethernet:
|
case network_mode::Ethernet:
|
||||||
return ETH.macAddress();
|
return ETH.macAddress();
|
||||||
break;
|
break;
|
||||||
#endif
|
|
||||||
case network_mode::WiFi:
|
case network_mode::WiFi:
|
||||||
return WiFi.macAddress();
|
return WiFi.macAddress();
|
||||||
break;
|
break;
|
||||||
@ -420,11 +403,7 @@ String NetworkSettingsClass::getHostname()
|
|||||||
|
|
||||||
bool NetworkSettingsClass::isConnected()
|
bool NetworkSettingsClass::isConnected()
|
||||||
{
|
{
|
||||||
#ifndef OPENDTU_ETHERNET
|
|
||||||
return WiFi.localIP()[0] != 0;
|
|
||||||
#else
|
|
||||||
return WiFi.localIP()[0] != 0 || ETH.localIP()[0] != 0;
|
return WiFi.localIP()[0] != 0 || ETH.localIP()[0] != 0;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
network_mode NetworkSettingsClass::NetworkMode()
|
network_mode NetworkSettingsClass::NetworkMode()
|
||||||
|
|||||||
103
src/PinMapping.cpp
Normal file
103
src/PinMapping.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 - 2023 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "PinMapping.h"
|
||||||
|
#include "MessageOutput.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define JSON_BUFFER_SIZE 6144
|
||||||
|
|
||||||
|
PinMappingClass PinMapping;
|
||||||
|
|
||||||
|
PinMappingClass::PinMappingClass()
|
||||||
|
{
|
||||||
|
memset(&_pinMapping, 0x0, sizeof(_pinMapping));
|
||||||
|
_pinMapping.nrf24_clk = HOYMILES_PIN_SCLK;
|
||||||
|
_pinMapping.nrf24_cs = HOYMILES_PIN_CS;
|
||||||
|
_pinMapping.nrf24_en = HOYMILES_PIN_CE;
|
||||||
|
_pinMapping.nrf24_irq = HOYMILES_PIN_IRQ;
|
||||||
|
_pinMapping.nrf24_miso = HOYMILES_PIN_MISO;
|
||||||
|
_pinMapping.nrf24_mosi = HOYMILES_PIN_MOSI;
|
||||||
|
|
||||||
|
#ifdef OPENDTU_ETHERNET
|
||||||
|
_pinMapping.eth_enabled = true;
|
||||||
|
#else
|
||||||
|
_pinMapping.eth_enabled = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_pinMapping.eth_phy_addr = ETH_PHY_ADDR;
|
||||||
|
_pinMapping.eth_power = ETH_PHY_POWER;
|
||||||
|
_pinMapping.eth_mdc = ETH_PHY_MDC;
|
||||||
|
_pinMapping.eth_mdio = ETH_PHY_MDIO;
|
||||||
|
_pinMapping.eth_type = ETH_PHY_TYPE;
|
||||||
|
_pinMapping.eth_clk_mode = ETH_CLK_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PinMapping_t& PinMappingClass::get()
|
||||||
|
{
|
||||||
|
return _pinMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PinMappingClass::init(const String& deviceMapping)
|
||||||
|
{
|
||||||
|
File f = LittleFS.open(PINMAPPING_FILENAME, "r", false);
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
||||||
|
// Deserialize the JSON document
|
||||||
|
DeserializationError error = deserializeJson(doc, f);
|
||||||
|
if (error) {
|
||||||
|
MessageOutput.println(F("Failed to read file, using default configuration"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 1; i <= doc.size(); i++) {
|
||||||
|
String devName = doc[i]["name"] | "";
|
||||||
|
if (devName == deviceMapping) {
|
||||||
|
strlcpy(_pinMapping.name, devName.c_str(), sizeof(_pinMapping.name));
|
||||||
|
_pinMapping.nrf24_clk = doc[i]["nrf24"]["clk"] | HOYMILES_PIN_SCLK;
|
||||||
|
_pinMapping.nrf24_cs = doc[i]["nrf24"]["cs"] | HOYMILES_PIN_CS;
|
||||||
|
_pinMapping.nrf24_en = doc[i]["nrf24"]["en"] | HOYMILES_PIN_CE;
|
||||||
|
_pinMapping.nrf24_irq = doc[i]["nrf24"]["irq"] | HOYMILES_PIN_IRQ;
|
||||||
|
_pinMapping.nrf24_miso = doc[i]["nrf24"]["miso"] | HOYMILES_PIN_MISO;
|
||||||
|
_pinMapping.nrf24_mosi = doc[i]["nrf24"]["mosi"] | HOYMILES_PIN_MOSI;
|
||||||
|
|
||||||
|
#ifdef OPENDTU_ETHERNET
|
||||||
|
_pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | true;
|
||||||
|
#else
|
||||||
|
_pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_pinMapping.eth_phy_addr = doc[i]["eth"]["phy_addr"] | ETH_PHY_ADDR;
|
||||||
|
_pinMapping.eth_power = doc[i]["eth"]["power"] | ETH_PHY_POWER;
|
||||||
|
_pinMapping.eth_mdc = doc[i]["eth"]["mdc"] | ETH_PHY_MDC;
|
||||||
|
_pinMapping.eth_mdio = doc[i]["eth"]["mdio"] | ETH_PHY_MDIO;
|
||||||
|
_pinMapping.eth_type = doc[i]["eth"]["type"] | ETH_PHY_TYPE;
|
||||||
|
_pinMapping.eth_clk_mode = doc[i]["eth"]["clk_mode"] | ETH_CLK_MODE;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PinMappingClass::isValidNrf24Config()
|
||||||
|
{
|
||||||
|
return _pinMapping.nrf24_clk > 0
|
||||||
|
&& _pinMapping.nrf24_cs > 0
|
||||||
|
&& _pinMapping.nrf24_en > 0
|
||||||
|
&& _pinMapping.nrf24_irq > 0
|
||||||
|
&& _pinMapping.nrf24_miso > 0
|
||||||
|
&& _pinMapping.nrf24_mosi > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PinMappingClass::isValidEthConfig()
|
||||||
|
{
|
||||||
|
return _pinMapping.eth_enabled;
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ void WebApiClass::init()
|
|||||||
_server.addHandler(&_events);
|
_server.addHandler(&_events);
|
||||||
|
|
||||||
_webApiConfig.init(&_server);
|
_webApiConfig.init(&_server);
|
||||||
|
_webApiDevice.init(&_server);
|
||||||
_webApiDevInfo.init(&_server);
|
_webApiDevInfo.init(&_server);
|
||||||
_webApiDtu.init(&_server);
|
_webApiDtu.init(&_server);
|
||||||
_webApiEventlog.init(&_server);
|
_webApiEventlog.init(&_server);
|
||||||
@ -44,6 +45,7 @@ void WebApiClass::init()
|
|||||||
void WebApiClass::loop()
|
void WebApiClass::loop()
|
||||||
{
|
{
|
||||||
_webApiConfig.loop();
|
_webApiConfig.loop();
|
||||||
|
_webApiDevice.loop();
|
||||||
_webApiDevInfo.loop();
|
_webApiDevInfo.loop();
|
||||||
_webApiDtu.loop();
|
_webApiDtu.loop();
|
||||||
_webApiEventlog.loop();
|
_webApiEventlog.loop();
|
||||||
|
|||||||
@ -22,6 +22,7 @@ void WebApiConfigClass::init(AsyncWebServer* server)
|
|||||||
|
|
||||||
_server->on("/api/config/get", HTTP_GET, std::bind(&WebApiConfigClass::onConfigGet, this, _1));
|
_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/delete", HTTP_POST, std::bind(&WebApiConfigClass::onConfigDelete, this, _1));
|
||||||
|
_server->on("/api/config/list", HTTP_GET, std::bind(&WebApiConfigClass::onConfigListGet, this, _1));
|
||||||
_server->on("/api/config/upload", HTTP_POST,
|
_server->on("/api/config/upload", HTTP_POST,
|
||||||
std::bind(&WebApiConfigClass::onConfigUploadFinish, this, _1),
|
std::bind(&WebApiConfigClass::onConfigUploadFinish, this, _1),
|
||||||
std::bind(&WebApiConfigClass::onConfigUpload, this, _1, _2, _3, _4, _5, _6));
|
std::bind(&WebApiConfigClass::onConfigUpload, this, _1, _2, _3, _4, _5, _6));
|
||||||
@ -37,7 +38,17 @@ void WebApiConfigClass::onConfigGet(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request->send(LittleFS, CONFIG_FILENAME, String(), true);
|
String requestFile = CONFIG_FILENAME;
|
||||||
|
if (request->hasParam("file")) {
|
||||||
|
String name = "/" + request->getParam("file")->value();
|
||||||
|
if (LittleFS.exists(name)) {
|
||||||
|
requestFile = name;
|
||||||
|
} else {
|
||||||
|
request->send(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request->send(LittleFS, requestFile, String(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
||||||
@ -106,6 +117,33 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
|||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
JsonArray data = root.createNestedArray(F("configs"));
|
||||||
|
|
||||||
|
File rootfs = LittleFS.open("/");
|
||||||
|
File file = rootfs.openNextFile();
|
||||||
|
while (file) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
JsonObject obj = data.createNestedObject();
|
||||||
|
obj["name"] = String(file.name());
|
||||||
|
|
||||||
|
file = rootfs.openNextFile();
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request)
|
void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentials(request)) {
|
if (!WebApi.checkCredentials(request)) {
|
||||||
@ -133,7 +171,12 @@ void WebApiConfigClass::onConfigUpload(AsyncWebServerRequest* request, String fi
|
|||||||
|
|
||||||
if (!index) {
|
if (!index) {
|
||||||
// open the file on first call and store the file handle in the request object
|
// open the file on first call and store the file handle in the request object
|
||||||
request->_tempFile = LittleFS.open(CONFIG_FILENAME, "w");
|
if (!request->hasParam("file")) {
|
||||||
|
request->send(500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String name = "/" + request->getParam("file")->value();
|
||||||
|
request->_tempFile = LittleFS.open(name, "w");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len) {
|
if (len) {
|
||||||
|
|||||||
133
src/WebApi_device.cpp
Normal file
133
src/WebApi_device.cpp
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "WebApi_device.h"
|
||||||
|
#include "Configuration.h"
|
||||||
|
#include "PinMapping.h"
|
||||||
|
#include "WebApi.h"
|
||||||
|
#include "WebApi_errors.h"
|
||||||
|
#include "helper.h"
|
||||||
|
#include <AsyncJson.h>
|
||||||
|
|
||||||
|
void WebApiDeviceClass::init(AsyncWebServer* server)
|
||||||
|
{
|
||||||
|
using std::placeholders::_1;
|
||||||
|
|
||||||
|
_server = server;
|
||||||
|
|
||||||
|
_server->on("/api/device/config", HTTP_GET, std::bind(&WebApiDeviceClass::onDeviceAdminGet, this, _1));
|
||||||
|
_server->on("/api/device/config", HTTP_POST, std::bind(&WebApiDeviceClass::onDeviceAdminPost, this, _1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiDeviceClass::loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
const PinMapping_t& pin = PinMapping.get();
|
||||||
|
|
||||||
|
JsonObject curPin = root.createNestedObject("curPin");
|
||||||
|
curPin[F("name")] = config.Dev_PinMapping;
|
||||||
|
|
||||||
|
JsonObject nrfObj = curPin.createNestedObject("nrf24");
|
||||||
|
nrfObj[F("clk")] = pin.nrf24_clk;
|
||||||
|
nrfObj[F("cs")] = pin.nrf24_cs;
|
||||||
|
nrfObj[F("en")] = pin.nrf24_en;
|
||||||
|
nrfObj[F("irq")] = pin.nrf24_irq;
|
||||||
|
nrfObj[F("miso")] = pin.nrf24_miso;
|
||||||
|
nrfObj[F("mosi")] = pin.nrf24_mosi;
|
||||||
|
|
||||||
|
JsonObject ethObj = curPin.createNestedObject("eth");
|
||||||
|
ethObj[F("enabled")] = pin.eth_enabled;
|
||||||
|
ethObj[F("phy_addr")] = pin.eth_phy_addr;
|
||||||
|
ethObj[F("power")] = pin.eth_power;
|
||||||
|
ethObj[F("mdc")] = pin.eth_mdc;
|
||||||
|
ethObj[F("mdio")] = pin.eth_mdio;
|
||||||
|
ethObj[F("type")] = pin.eth_type;
|
||||||
|
ethObj[F("clk_mode")] = pin.eth_clk_mode;
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||||
|
JsonObject retMsg = response->getRoot();
|
||||||
|
retMsg[F("type")] = F("warning");
|
||||||
|
|
||||||
|
if (!request->hasParam("data", true)) {
|
||||||
|
retMsg[F("message")] = F("No values found!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
|
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
||||||
|
retMsg[F("message")] = F("Data too large!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicJsonDocument root(MQTT_JSON_DOC_SIZE);
|
||||||
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
retMsg[F("message")] = F("Failed to parse data!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(root.containsKey("curPin"))) {
|
||||||
|
retMsg[F("message")] = F("Values are missing!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root[F("curPin")][F("name")].as<String>().length() == 0 || root[F("curPin")][F("name")].as<String>().length() > DEV_MAX_MAPPING_NAME_STRLEN) {
|
||||||
|
retMsg[F("message")] = F("Pin mapping must between 1 and " STR(DEV_MAX_MAPPING_NAME_STRLEN) " characters long!");
|
||||||
|
retMsg[F("code")] = WebApiError::HardwarePinMappingLength;
|
||||||
|
retMsg[F("param")][F("max")] = DEV_MAX_MAPPING_NAME_STRLEN;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_T& config = Configuration.get();
|
||||||
|
strlcpy(config.Dev_PinMapping, root[F("curPin")][F("name")].as<String>().c_str(), sizeof(config.Dev_PinMapping));
|
||||||
|
Configuration.write();
|
||||||
|
|
||||||
|
retMsg[F("type")] = F("success");
|
||||||
|
retMsg[F("message")] = F("Settings saved!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
|
||||||
|
yield();
|
||||||
|
delay(1000);
|
||||||
|
yield();
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
19
src/main.cpp
19
src/main.cpp
@ -12,6 +12,7 @@
|
|||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "NtpSettings.h"
|
#include "NtpSettings.h"
|
||||||
|
#include "PinMapping.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
@ -58,6 +59,15 @@ void setup()
|
|||||||
}
|
}
|
||||||
MessageOutput.println(F("done"));
|
MessageOutput.println(F("done"));
|
||||||
|
|
||||||
|
// Load PinMapping
|
||||||
|
MessageOutput.print(F("Reading PinMapping... "));
|
||||||
|
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
|
||||||
|
MessageOutput.print(F("found valid mapping "));
|
||||||
|
} else {
|
||||||
|
MessageOutput.print(F("using default config "));
|
||||||
|
}
|
||||||
|
MessageOutput.println(F("done"));
|
||||||
|
|
||||||
// Initialize WiFi
|
// Initialize WiFi
|
||||||
MessageOutput.print(F("Initialize Network... "));
|
MessageOutput.print(F("Initialize Network... "));
|
||||||
NetworkSettings.init();
|
NetworkSettings.init();
|
||||||
@ -99,10 +109,12 @@ void setup()
|
|||||||
|
|
||||||
// Initialize inverter communication
|
// Initialize inverter communication
|
||||||
MessageOutput.print(F("Initialize Hoymiles interface... "));
|
MessageOutput.print(F("Initialize Hoymiles interface... "));
|
||||||
|
if (PinMapping.isValidNrf24Config()) {
|
||||||
SPIClass* spiClass = new SPIClass(HSPI);
|
SPIClass* spiClass = new SPIClass(HSPI);
|
||||||
spiClass->begin(HOYMILES_PIN_SCLK, HOYMILES_PIN_MISO, HOYMILES_PIN_MOSI, HOYMILES_PIN_CS);
|
PinMapping_t& pin = PinMapping.get();
|
||||||
|
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
|
||||||
Hoymiles.setMessageOutput(&MessageOutput);
|
Hoymiles.setMessageOutput(&MessageOutput);
|
||||||
Hoymiles.init(spiClass, HOYMILES_PIN_CE, HOYMILES_PIN_IRQ);
|
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
|
||||||
|
|
||||||
MessageOutput.println(F(" Setting radio PA level... "));
|
MessageOutput.println(F(" Setting radio PA level... "));
|
||||||
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
||||||
@ -132,6 +144,9 @@ void setup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageOutput.println(F("done"));
|
MessageOutput.println(F("done"));
|
||||||
|
} else {
|
||||||
|
MessageOutput.println(F("Invalid pin config"));
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize ve.direct communication
|
// Initialize ve.direct communication
|
||||||
MessageOutput.println(F("Initialize ve.direct interface... "));
|
MessageOutput.println(F("Initialize ve.direct interface... "));
|
||||||
|
|||||||
@ -28,14 +28,14 @@
|
|||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"@vue/eslint-config-typescript": "^11.0.2",
|
"@vue/eslint-config-typescript": "^11.0.2",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.1.3",
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.32.0",
|
||||||
"eslint-plugin-vue": "^9.8.0",
|
"eslint-plugin-vue": "^9.9.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.0.3",
|
"vite": "^4.0.4",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-css-injected-by-js": "^2.2.0",
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
||||||
"vue-tsc": "^1.0.19"
|
"vue-tsc": "^1.0.24"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('devinfo.DetectedMaxPower') }}</td>
|
<td>{{ $t('devinfo.DetectedMaxPower') }}</td>
|
||||||
<td>{{ devInfoList.max_power }} W</td>
|
<td>{{ $n(devInfoList.max_power, 'decimal') }} W</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('devinfo.BootloaderVersion') }}</td>
|
<td>{{ $t('devinfo.BootloaderVersion') }}</td>
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('firmwareinfo.ConfigSaveCount') }}</th>
|
<th>{{ $t('firmwareinfo.ConfigSaveCount') }}</th>
|
||||||
<td>{{ systemStatus.cfgsavecount }}</td>
|
<td>{{ $n(systemStatus.cfgsavecount, 'decimal') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('firmwareinfo.Uptime') }}</th>
|
<th>{{ $t('firmwareinfo.Uptime') }}</th>
|
||||||
|
|||||||
@ -5,16 +5,15 @@
|
|||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="progress-bar" role="progressbar" :style="{ width: getPercent() + '%' }"
|
<div class="progress-bar" role="progressbar" :style="{ width: getPercent() + '%' }"
|
||||||
v-bind:aria-valuenow="getPercent()" aria-valuemin="0" aria-valuemax="100">
|
v-bind:aria-valuenow="getPercent()" aria-valuemin="0" aria-valuemax="100">
|
||||||
{{ getPercent() }}%
|
{{ $n(getPercent() / 100, 'percent') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="rightCell">
|
<td class="rightCell">
|
||||||
{{ Math.round((total - used) / 1024) }}
|
{{ $n(Math.round((total - used) / 1024), 'kilobyte') }}
|
||||||
KByte
|
|
||||||
</td>
|
</td>
|
||||||
<td class="rightCell">{{ Math.round(used / 1024) }} KByte</td>
|
<td class="rightCell">{{ $n(Math.round(used / 1024), 'kilobyte') }}</td>
|
||||||
<td class="rightCell">{{ Math.round(total / 1024) }} KByte</td>
|
<td class="rightCell">{{ $n(Math.round(total / 1024), 'kilobyte') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,12 @@
|
|||||||
<tr v-for="(property, key) in channelData" :key="`prop-${key}`">
|
<tr v-for="(property, key) in channelData" :key="`prop-${key}`">
|
||||||
<template v-if="key != 'name' && property">
|
<template v-if="key != 'name' && property">
|
||||||
<th scope="row">{{ $t('inverterchannelproperty.' + key) }}</th>
|
<th scope="row">{{ $t('inverterchannelproperty.' + key) }}</th>
|
||||||
<td style="text-align: right">{{ formatNumber(property.v, property.d) }}</td>
|
<td style="text-align: right">
|
||||||
|
{{ $n(property.v, 'decimal', {
|
||||||
|
minimumFractionDigits: property.d,
|
||||||
|
maximumFractionDigits: property.d})
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
<td>{{ property.u }}</td>
|
<td>{{ property.u }}</td>
|
||||||
</template>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
@ -31,7 +36,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { InverterStatistics } from '@/types/LiveDataStatus';
|
import type { InverterStatistics } from '@/types/LiveDataStatus';
|
||||||
import { formatNumber } from '@/utils';
|
|
||||||
import { defineComponent, type PropType } from 'vue';
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -39,8 +43,5 @@ export default defineComponent({
|
|||||||
channelData: { type: Object as PropType<InverterStatistics>, required: true },
|
channelData: { type: Object as PropType<InverterStatistics>, required: true },
|
||||||
channelNumber: { type: Number, required: true },
|
channelNumber: { type: Number, required: true },
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
formatNumber,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -4,7 +4,11 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalYieldTotal') }}</div>
|
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalYieldTotal') }}</div>
|
||||||
<div class="card-body card-text text-center">
|
<div class="card-body card-text text-center">
|
||||||
<h2>{{ formatNumber(totalData.YieldTotal.v, totalData.YieldTotal.d) }}
|
<h2>
|
||||||
|
{{ $n(totalData.YieldTotal.v, 'decimal', {
|
||||||
|
minimumFractionDigits: totalData.YieldTotal.d,
|
||||||
|
maximumFractionDigits: totalData.YieldTotal.d
|
||||||
|
})}}
|
||||||
<small class="text-muted">{{ totalData.YieldTotal.u }}</small>
|
<small class="text-muted">{{ totalData.YieldTotal.u }}</small>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -14,7 +18,11 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalYieldDay') }}</div>
|
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalYieldDay') }}</div>
|
||||||
<div class="card-body card-text text-center">
|
<div class="card-body card-text text-center">
|
||||||
<h2>{{ formatNumber(totalData.YieldDay.v, totalData.YieldDay.d) }}
|
<h2>
|
||||||
|
{{ $n(totalData.YieldDay.v, 'decimal', {
|
||||||
|
minimumFractionDigits: totalData.YieldDay.d,
|
||||||
|
maximumFractionDigits: totalData.YieldDay.d
|
||||||
|
})}}
|
||||||
<small class="text-muted">{{ totalData.YieldDay.u }}</small>
|
<small class="text-muted">{{ totalData.YieldDay.u }}</small>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -24,7 +32,11 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalPower') }}</div>
|
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.TotalPower') }}</div>
|
||||||
<div class="card-body card-text text-center">
|
<div class="card-body card-text text-center">
|
||||||
<h2>{{ formatNumber(totalData.Power.v, totalData.Power.d) }}
|
<h2>
|
||||||
|
{{ $n(totalData.Power.v, 'decimal', {
|
||||||
|
minimumFractionDigits: totalData.Power.d,
|
||||||
|
maximumFractionDigits: totalData.Power.d
|
||||||
|
})}}
|
||||||
<small class="text-muted">{{ totalData.Power.u }}</small>
|
<small class="text-muted">{{ totalData.Power.u }}</small>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -35,15 +47,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Total } from '@/types/LiveDataStatus';
|
import type { Total } from '@/types/LiveDataStatus';
|
||||||
import { formatNumber } from '@/utils';
|
|
||||||
import { defineComponent, type PropType } from 'vue';
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
totalData: { type: Object as PropType<Total>, required: true },
|
totalData: { type: Object as PropType<Total>, required: true },
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
formatNumber,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -1,28 +0,0 @@
|
|||||||
<template>
|
|
||||||
<table class="table table-hover">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Current Limit</td>
|
|
||||||
<td>{{ formatNumber(limitData.limit, 2) }}%</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { formatNumber } from '@/utils';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
declare interface LimitData {
|
|
||||||
limit: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
limitData: { type: Object as () => LimitData, required: true },
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
formatNumber,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,13 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<select class="form-select" @change="updateLanguage()" v-model="$i18n.locale">
|
<select class="form-select" @change="updateLanguage()" v-model="$i18n.locale">
|
||||||
<option v-for="locale in $i18n.availableLocales" :key="`locale-${locale}`" :value="locale">
|
<option v-for="locale in $i18n.availableLocales" :key="`locale-${locale}`" :value="locale">
|
||||||
{{ locale.toUpperCase() }}
|
{{ getLocaleName(locale) }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import { LOCALES } from '@/locales';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "LocaleSwitcher",
|
name: "LocaleSwitcher",
|
||||||
@ -15,6 +16,9 @@ export default defineComponent({
|
|||||||
updateLanguage() {
|
updateLanguage() {
|
||||||
localStorage.setItem("locale", this.$i18n.locale);
|
localStorage.setItem("locale", this.$i18n.locale);
|
||||||
},
|
},
|
||||||
|
getLocaleName(locale: string): string {
|
||||||
|
return LOCALES.find(i => i.value === locale)?.caption || "";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (localStorage.getItem("locale")) {
|
if (localStorage.getItem("locale")) {
|
||||||
|
|||||||
@ -51,6 +51,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/vedirect">{{ $t('menu.VedirectSettings') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/settings/vedirect">{{ $t('menu.VedirectSettings') }}</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link @click="onClick" class="dropdown-item" to="/settings/device">{{ $t('menu.DeviceManager') }}</router-link>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<hr class="dropdown-divider" />
|
<hr class="dropdown-divider" />
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
103
webapp/src/components/PinInfo.vue
Normal file
103
webapp/src/components/PinInfo.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<CardElement :text="$t('pininfo.PinOverview')" textVariant="text-bg-primary">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('pininfo.Category') }}</th>
|
||||||
|
<th>{{ $t('pininfo.Name') }}</th>
|
||||||
|
<th>{{ $t('pininfo.ValueSelected') }}</th>
|
||||||
|
<th>{{ $t('pininfo.ValueActive') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td rowspan="6">NRF24</td>
|
||||||
|
<td>MISO</td>
|
||||||
|
<td>{{ selectedPinAssignment?.nrf24?.miso }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.nrf24?.miso }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>MOSI</td>
|
||||||
|
<td>{{ selectedPinAssignment?.nrf24?.mosi }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.nrf24?.mosi }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>CLK</td>
|
||||||
|
<td>{{ selectedPinAssignment?.nrf24?.clk }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.nrf24?.clk }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>IRQ</td>
|
||||||
|
<td>{{ selectedPinAssignment?.nrf24?.irq }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.nrf24?.irq }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>EN</td>
|
||||||
|
<td>{{ selectedPinAssignment?.nrf24?.en }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.nrf24?.en }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>CS</td>
|
||||||
|
<td>{{ selectedPinAssignment?.nrf24?.cs }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.nrf24?.cs }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td rowspan="7">Ethernet</td>
|
||||||
|
<td>enabled</td>
|
||||||
|
<td>{{ selectedPinAssignment?.eth?.enabled }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.eth?.enabled }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>phy_addr</td>
|
||||||
|
<td>{{ selectedPinAssignment?.eth?.phy_addr }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.eth?.phy_addr }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>power</td>
|
||||||
|
<td>{{ selectedPinAssignment?.eth?.power }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.eth?.power }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>mdc</td>
|
||||||
|
<td>{{ selectedPinAssignment?.eth?.mdc }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.eth?.mdc }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>mdio</td>
|
||||||
|
<td>{{ selectedPinAssignment?.eth?.mdio }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.eth?.mdio }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>type</td>
|
||||||
|
<td>{{ selectedPinAssignment?.eth?.type }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.eth?.type }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>clk_mode</td>
|
||||||
|
<td>{{ selectedPinAssignment?.eth?.clk_mode }}</td>
|
||||||
|
<td>{{ currentPinAssignment?.eth?.clk_mode }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</CardElement>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import CardElement from '@/components/CardElement.vue';
|
||||||
|
import type { Device } from '@/types/PinMapping';
|
||||||
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
CardElement,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
selectedPinAssignment: { type: Object as PropType<Device | undefined>, required: true },
|
||||||
|
currentPinAssignment: { type: Object as PropType<Device | undefined>, required: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('wifiapinfo.Stations') }}</th>
|
<th>{{ $t('wifiapinfo.Stations') }}</th>
|
||||||
<td>{{ networkStatus.ap_stationnum }}</td>
|
<td>{{ $n(networkStatus.ap_stationnum, 'decimal') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -19,11 +19,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('wifistationinfo.Quality') }}</th>
|
<th>{{ $t('wifistationinfo.Quality') }}</th>
|
||||||
<td>{{ getRSSIasQuality(networkStatus.sta_rssi) }} %</td>
|
<td>{{ $n(getRSSIasQuality(networkStatus.sta_rssi), 'percent') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('wifistationinfo.Rssi') }}</th>
|
<th>{{ $t('wifistationinfo.Rssi') }}</th>
|
||||||
<td>{{ networkStatus.sta_rssi }}</td>
|
<td>{{ $n(networkStatus.sta_rssi, 'decimal') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -55,7 +55,7 @@ export default defineComponent({
|
|||||||
quality = 2 * (rssi + 100);
|
quality = 2 * (rssi + 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
return quality;
|
return quality / 100;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"InverterSettings": "Wechselrichter Einstellungen",
|
"InverterSettings": "Wechselrichter Einstellungen",
|
||||||
"SecuritySettings": "Sicherheitseinstellungen",
|
"SecuritySettings": "Sicherheitseinstellungen",
|
||||||
"DTUSettings": "DTU Einstellungen",
|
"DTUSettings": "DTU Einstellungen",
|
||||||
|
"DeviceManager": "Geräte-Manager",
|
||||||
"VedirectSettings": "Ve.direct Settings",
|
"VedirectSettings": "Ve.direct Settings",
|
||||||
"ConfigManagement": "Konfigurationsverwaltung",
|
"ConfigManagement": "Konfigurationsverwaltung",
|
||||||
"FirmwareUpgrade": "Firmware Aktualisierung",
|
"FirmwareUpgrade": "Firmware Aktualisierung",
|
||||||
@ -84,7 +85,8 @@
|
|||||||
"10001": "Das Passwort muss zwischen 8 und {max} Zeichen lang sein!",
|
"10001": "Das Passwort muss zwischen 8 und {max} Zeichen lang sein!",
|
||||||
"10002": "Authentifizierung erfolgreich!",
|
"10002": "Authentifizierung erfolgreich!",
|
||||||
"11001": "@:apiresponse.2001",
|
"11001": "@:apiresponse.2001",
|
||||||
"11002": "@:apiresponse:5004"
|
"11002": "@:apiresponse:5004",
|
||||||
|
"12001": "Profil muss zwischen 1 und {max} Zeichen lang sein!"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"LiveData": "Live Daten",
|
"LiveData": "Live Daten",
|
||||||
@ -461,6 +463,8 @@
|
|||||||
"BackupHeader": "Sicherung: Sicherung der Konfigurationsdatei",
|
"BackupHeader": "Sicherung: Sicherung der Konfigurationsdatei",
|
||||||
"BackupConfig": "Sicherung der Konfigurationsdatei",
|
"BackupConfig": "Sicherung der Konfigurationsdatei",
|
||||||
"Backup": "Sichern",
|
"Backup": "Sichern",
|
||||||
|
"Restore": "Wiederherstellen",
|
||||||
|
"NoFileSelected": "Keine Datei Ausgewählt",
|
||||||
"RestoreHeader": "Wiederherstellen: Wiederherstellen der Konfigurationsdatei",
|
"RestoreHeader": "Wiederherstellen: Wiederherstellen der Konfigurationsdatei",
|
||||||
"Back": "Zurück",
|
"Back": "Zurück",
|
||||||
"UploadSuccess": "Erfolgreich hochgeladen",
|
"UploadSuccess": "Erfolgreich hochgeladen",
|
||||||
@ -513,5 +517,20 @@
|
|||||||
"TimeSyncLink": "Bitte überprüfen Sie Ihre Zeiteinstellungen.",
|
"TimeSyncLink": "Bitte überprüfen Sie Ihre Zeiteinstellungen.",
|
||||||
"DefaultPassword": "Sie verwenden das Standardpasswort für die Weboberfläche und den Notfall Access Point. Dies ist potenziell unsicher.",
|
"DefaultPassword": "Sie verwenden das Standardpasswort für die Weboberfläche und den Notfall Access Point. Dies ist potenziell unsicher.",
|
||||||
"DefaultPasswordLink": "Bitte ändern Sie das Passwort."
|
"DefaultPasswordLink": "Bitte ändern Sie das Passwort."
|
||||||
|
},
|
||||||
|
"deviceadmin": {
|
||||||
|
"DeviceManager": "Geräte-Manager",
|
||||||
|
"PinAssignment": "Anschlusseinstellungen",
|
||||||
|
"SelectedProfile": "Ausgewähltes Profil:",
|
||||||
|
"DefaultProfile": "(Standard Einstellungen)",
|
||||||
|
"ProfileHint": "Ihr Gerät reagiert möglicherweise nicht mehr, wenn Sie ein inkompatibles Profil wählen. In diesem Fall müssen Sie eine Löschung über das serielle Interface durchführen.",
|
||||||
|
"Save": "@:dtuadmin.Save"
|
||||||
|
},
|
||||||
|
"pininfo": {
|
||||||
|
"PinOverview": "Anschlussübersicht",
|
||||||
|
"Category": "Kategorie",
|
||||||
|
"Name": "Name",
|
||||||
|
"ValueSelected": "Ausgewählt",
|
||||||
|
"ValueActive": "Aktiv"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,6 +8,7 @@
|
|||||||
"InverterSettings": "Inverter Settings",
|
"InverterSettings": "Inverter Settings",
|
||||||
"SecuritySettings": "Security Settings",
|
"SecuritySettings": "Security Settings",
|
||||||
"DTUSettings": "DTU Settings",
|
"DTUSettings": "DTU Settings",
|
||||||
|
"DeviceManager": "Device-Manager",
|
||||||
"VedirectSettings": "Ve.direct Settings",
|
"VedirectSettings": "Ve.direct Settings",
|
||||||
"ConfigManagement": "Config Management",
|
"ConfigManagement": "Config Management",
|
||||||
"FirmwareUpgrade": "Firmware Upgrade",
|
"FirmwareUpgrade": "Firmware Upgrade",
|
||||||
@ -84,7 +85,8 @@
|
|||||||
"10001": "Password must between 8 and {max} characters long!",
|
"10001": "Password must between 8 and {max} characters long!",
|
||||||
"10002": "Authentication successfull!",
|
"10002": "Authentication successfull!",
|
||||||
"11001": "@:apiresponse.2001",
|
"11001": "@:apiresponse.2001",
|
||||||
"11002": "@:apiresponse:5004"
|
"11002": "@:apiresponse:5004",
|
||||||
|
"12001": "Profil must between 1 and {max} characters long!"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"LiveData": "Live Data",
|
"LiveData": "Live Data",
|
||||||
@ -461,6 +463,8 @@
|
|||||||
"BackupHeader": "Backup: Configuration File Backup",
|
"BackupHeader": "Backup: Configuration File Backup",
|
||||||
"BackupConfig": "Backup the configuration file",
|
"BackupConfig": "Backup the configuration file",
|
||||||
"Backup": "Backup",
|
"Backup": "Backup",
|
||||||
|
"Restore": "Restore",
|
||||||
|
"NoFileSelected": "No file selected",
|
||||||
"RestoreHeader": "Restore: Restore the Configuration File",
|
"RestoreHeader": "Restore: Restore the Configuration File",
|
||||||
"Back": "Back",
|
"Back": "Back",
|
||||||
"UploadSuccess": "Upload Success",
|
"UploadSuccess": "Upload Success",
|
||||||
@ -513,5 +517,21 @@
|
|||||||
"TimeSyncLink": "Please check your time settings.",
|
"TimeSyncLink": "Please check your time settings.",
|
||||||
"DefaultPassword": "You are using the default password for the web interface and the emergency access point. This is potentially insecure.",
|
"DefaultPassword": "You are using the default password for the web interface and the emergency access point. This is potentially insecure.",
|
||||||
"DefaultPasswordLink": "Please change the password."
|
"DefaultPasswordLink": "Please change the password."
|
||||||
|
},
|
||||||
|
"deviceadmin": {
|
||||||
|
"DeviceManager": "Device-Manager",
|
||||||
|
"PinAssignment": "Connection settings",
|
||||||
|
"SelectedProfile": "Selected profile:",
|
||||||
|
"DefaultProfile": "(Default settings)",
|
||||||
|
"ProfileHint": "Your device may stop responding if you select an incompatible profile. In this case, you must perform a deletion via the serial interface.",
|
||||||
|
"Save": "@:dtuadmin.Save"
|
||||||
|
},
|
||||||
|
"pininfo": {
|
||||||
|
"PinOverview": "Connection overview",
|
||||||
|
"Category": "Category",
|
||||||
|
"Name": "Name",
|
||||||
|
"Number": "Number",
|
||||||
|
"ValueSelected": "Selected",
|
||||||
|
"ValueActive": "Active"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,6 +8,7 @@
|
|||||||
"InverterSettings": "Paramètres des onduleurs",
|
"InverterSettings": "Paramètres des onduleurs",
|
||||||
"SecuritySettings": "Paramètres de sécurité",
|
"SecuritySettings": "Paramètres de sécurité",
|
||||||
"DTUSettings": "Paramètres DTU",
|
"DTUSettings": "Paramètres DTU",
|
||||||
|
"DeviceManager": "Device-Manager",
|
||||||
"VedirectSettings": "Paramètres Ve.direct",
|
"VedirectSettings": "Paramètres Ve.direct",
|
||||||
"ConfigManagement": "Gestion de la configuration",
|
"ConfigManagement": "Gestion de la configuration",
|
||||||
"FirmwareUpgrade": "Mise à jour du firmware",
|
"FirmwareUpgrade": "Mise à jour du firmware",
|
||||||
@ -84,7 +85,8 @@
|
|||||||
"10001": "Le mot de passe doit comporter entre 8 et {max} caractères !",
|
"10001": "Le mot de passe doit comporter entre 8 et {max} caractères !",
|
||||||
"10002": "Authentification réussie !",
|
"10002": "Authentification réussie !",
|
||||||
"11001": "@:apiresponse.2001",
|
"11001": "@:apiresponse.2001",
|
||||||
"11002": "@:apiresponse:5004"
|
"11002": "@:apiresponse:5004",
|
||||||
|
"12001": "Profil must between 1 and {max} characters long!"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"LiveData": "Données en direct",
|
"LiveData": "Données en direct",
|
||||||
@ -307,9 +309,7 @@
|
|||||||
"invertertotalinfo": {
|
"invertertotalinfo": {
|
||||||
"TotalYieldTotal": "Rendement total",
|
"TotalYieldTotal": "Rendement total",
|
||||||
"TotalYieldDay": "Rendement du jour",
|
"TotalYieldDay": "Rendement du jour",
|
||||||
"TotalPower": "Puissance de l'installation",
|
"TotalPower": "Puissance de l'installation"
|
||||||
"TotalVoltage": "Tension moyenne",
|
|
||||||
"TotalCurrent": "Courant"
|
|
||||||
},
|
},
|
||||||
"inverterchannelproperty": {
|
"inverterchannelproperty": {
|
||||||
"Power": "Puissance",
|
"Power": "Puissance",
|
||||||
@ -449,6 +449,7 @@
|
|||||||
"InverterName": "Nom de l'onduleur :",
|
"InverterName": "Nom de l'onduleur :",
|
||||||
"InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.",
|
"InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.",
|
||||||
"StringName": "Nom de la ligne {num}:",
|
"StringName": "Nom de la ligne {num}:",
|
||||||
|
"StringNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour le port respectif de votre onduleur.",
|
||||||
"StringMaxPower": "Puissance maximale de la ligne {num}:",
|
"StringMaxPower": "Puissance maximale de la ligne {num}:",
|
||||||
"StringMaxPowerHint": "Entrez la puissance maximale des panneaux solaires connectés.",
|
"StringMaxPowerHint": "Entrez la puissance maximale des panneaux solaires connectés.",
|
||||||
"InverterHint": "*) Entrez le W<sub>p</sub> du canal pour calculer l'irradiation.",
|
"InverterHint": "*) Entrez le W<sub>p</sub> du canal pour calculer l'irradiation.",
|
||||||
@ -462,6 +463,8 @@
|
|||||||
"BackupHeader": "Sauvegarder le fichier de configuration",
|
"BackupHeader": "Sauvegarder le fichier de configuration",
|
||||||
"BackupConfig": "Fichier de configuration",
|
"BackupConfig": "Fichier de configuration",
|
||||||
"Backup": "Sauvegarder",
|
"Backup": "Sauvegarder",
|
||||||
|
"Restore": "Restore",
|
||||||
|
"NoFileSelected": "No file selected",
|
||||||
"RestoreHeader": "Restaurer le fichier de configuration",
|
"RestoreHeader": "Restaurer le fichier de configuration",
|
||||||
"Back": "Retour",
|
"Back": "Retour",
|
||||||
"UploadSuccess": "Succès du téléversement",
|
"UploadSuccess": "Succès du téléversement",
|
||||||
@ -514,5 +517,20 @@
|
|||||||
"TimeSyncLink": "Veuillez vérifier vos paramètres horaires.",
|
"TimeSyncLink": "Veuillez vérifier vos paramètres horaires.",
|
||||||
"DefaultPassword": "Vous utilisez le mot de passe par défaut pour l'interface Web et le point d'accès d'urgence. Ceci est potentiellement non sécurisé.",
|
"DefaultPassword": "Vous utilisez le mot de passe par défaut pour l'interface Web et le point d'accès d'urgence. Ceci est potentiellement non sécurisé.",
|
||||||
"DefaultPasswordLink": "Merci de changer le mot de passe."
|
"DefaultPasswordLink": "Merci de changer le mot de passe."
|
||||||
|
},
|
||||||
|
"deviceadmin": {
|
||||||
|
"DeviceManager": "Device-Manager",
|
||||||
|
"PinAssignment": "Connection settings",
|
||||||
|
"SelectedProfile": "Selected profile:",
|
||||||
|
"DefaultProfile": "(Default settings)",
|
||||||
|
"ProfileHint": "Your device may stop responding if you select an incompatible profile. In this case, you must perform a deletion via the serial interface.",
|
||||||
|
"Save": "@:dtuadmin.Save"
|
||||||
|
},
|
||||||
|
"pininfo": {
|
||||||
|
"PinOverview": "Connection overview",
|
||||||
|
"Category": "Category",
|
||||||
|
"Name": "Name",
|
||||||
|
"ValueSelected": "Selected",
|
||||||
|
"ValueActive": "Active"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,4 +57,58 @@ export const dateTimeFormats: I18nOptions["datetimeFormats"] = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const numberFormats: I18nOptions["numberFormats"] = {
|
||||||
|
[Locales.EN]: {
|
||||||
|
decimal: {
|
||||||
|
style: 'decimal',
|
||||||
|
},
|
||||||
|
decimalNoDigits: {
|
||||||
|
style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0
|
||||||
|
},
|
||||||
|
decimalTwoDigits: {
|
||||||
|
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
|
||||||
|
},
|
||||||
|
percent: {
|
||||||
|
style: 'percent',
|
||||||
|
},
|
||||||
|
kilobyte: {
|
||||||
|
style: 'unit', unit: 'kilobyte',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[Locales.DE]: {
|
||||||
|
decimal: {
|
||||||
|
style: 'decimal',
|
||||||
|
},
|
||||||
|
decimalNoDigits: {
|
||||||
|
style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0
|
||||||
|
},
|
||||||
|
decimalTwoDigits: {
|
||||||
|
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
|
||||||
|
},
|
||||||
|
percent: {
|
||||||
|
style: 'percent',
|
||||||
|
},
|
||||||
|
kilobyte: {
|
||||||
|
style: 'unit', unit: 'kilobyte',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[Locales.FR]: {
|
||||||
|
decimal: {
|
||||||
|
style: 'decimal',
|
||||||
|
},
|
||||||
|
decimalNoDigits: {
|
||||||
|
style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0
|
||||||
|
},
|
||||||
|
decimalTwoDigits: {
|
||||||
|
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
|
||||||
|
},
|
||||||
|
percent: {
|
||||||
|
style: 'percent',
|
||||||
|
},
|
||||||
|
kilobyte: {
|
||||||
|
style: 'unit', unit: 'kilobyte',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const defaultLocale = Locales.EN;
|
export const defaultLocale = Locales.EN;
|
||||||
@ -2,7 +2,7 @@ import mitt from 'mitt'
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { defaultLocale, messages, dateTimeFormats } from './locales'
|
import { defaultLocale, messages, dateTimeFormats, numberFormats } from './locales'
|
||||||
import { tooltip } from './plugins/bootstrap'
|
import { tooltip } from './plugins/bootstrap'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
||||||
@ -23,6 +23,7 @@ const i18n = createI18n({
|
|||||||
fallbackLocale: defaultLocale,
|
fallbackLocale: defaultLocale,
|
||||||
messages,
|
messages,
|
||||||
datetimeFormats: dateTimeFormats,
|
datetimeFormats: dateTimeFormats,
|
||||||
|
numberFormats: numberFormats
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import AboutView from '@/views/AboutView.vue';
|
import AboutView from '@/views/AboutView.vue';
|
||||||
import ConfigAdminView from '@/views/ConfigAdminView.vue';
|
import ConfigAdminView from '@/views/ConfigAdminView.vue';
|
||||||
import ConsoleInfoView from '@/views/ConsoleInfoView.vue';
|
import ConsoleInfoView from '@/views/ConsoleInfoView.vue';
|
||||||
|
import DeviceAdminView from '@/views/DeviceAdminView.vue'
|
||||||
import DtuAdminView from '@/views/DtuAdminView.vue';
|
import DtuAdminView from '@/views/DtuAdminView.vue';
|
||||||
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
|
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
|
||||||
import HomeView from '@/views/HomeView.vue';
|
import HomeView from '@/views/HomeView.vue';
|
||||||
@ -98,6 +99,11 @@ const router = createRouter({
|
|||||||
name: 'DTU Settings',
|
name: 'DTU Settings',
|
||||||
component: DtuAdminView
|
component: DtuAdminView
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/settings/device',
|
||||||
|
name: 'Device Manager',
|
||||||
|
component: DeviceAdminView
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/firmware/upgrade',
|
path: '/firmware/upgrade',
|
||||||
name: 'Firmware Upgrade',
|
name: 'Firmware Upgrade',
|
||||||
|
|||||||
7
webapp/src/types/Config.ts
Normal file
7
webapp/src/types/Config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface ConfigFileInfo {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigFileList {
|
||||||
|
configs: Array<ConfigFileInfo>;
|
||||||
|
}
|
||||||
5
webapp/src/types/DeviceConfig.ts
Normal file
5
webapp/src/types/DeviceConfig.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import type { Device } from "./PinMapping";
|
||||||
|
|
||||||
|
export interface DeviceConfig {
|
||||||
|
curPin: Device;
|
||||||
|
}
|
||||||
26
webapp/src/types/PinMapping.ts
Normal file
26
webapp/src/types/PinMapping.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export interface Nrf24 {
|
||||||
|
miso: number;
|
||||||
|
mosi: number;
|
||||||
|
clk: number;
|
||||||
|
irq: number;
|
||||||
|
en: number;
|
||||||
|
cs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Ethernet {
|
||||||
|
enabled: boolean;
|
||||||
|
phy_addr: number;
|
||||||
|
power: number;
|
||||||
|
mdc: number;
|
||||||
|
mdio: number;
|
||||||
|
type: number;
|
||||||
|
clk_mode: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Device {
|
||||||
|
name: string;
|
||||||
|
nrf24: Nrf24;
|
||||||
|
eth: Ethernet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PinMapping extends Array<Device>{}
|
||||||
@ -1,10 +1,8 @@
|
|||||||
import { isLoggedIn, login, logout } from './authentication';
|
import { isLoggedIn, login, logout } from './authentication';
|
||||||
import { formatNumber } from './number';
|
|
||||||
import { timestampToString } from './time';
|
import { timestampToString } from './time';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
timestampToString,
|
timestampToString,
|
||||||
formatNumber,
|
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
@ -12,7 +10,6 @@ export {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
timestampToString,
|
timestampToString,
|
||||||
formatNumber,
|
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
export const formatNumber = (num: number, digits: number): string => {
|
|
||||||
return new Intl.NumberFormat(
|
|
||||||
undefined, { minimumFractionDigits: digits, maximumFractionDigits: digits }
|
|
||||||
).format(num);
|
|
||||||
}
|
|
||||||
@ -5,9 +5,22 @@
|
|||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
|
|
||||||
<CardElement :text="$t('configadmin.BackupHeader')" textVariant="text-bg-primary" center-content>
|
<CardElement :text="$t('configadmin.BackupHeader')" textVariant="text-bg-primary" center-content>
|
||||||
|
<div class="row g-3 align-items-center">
|
||||||
|
<div class="col-sm">
|
||||||
{{ $t('configadmin.BackupConfig') }}
|
{{ $t('configadmin.BackupConfig') }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<select class="form-select" v-model="backupFileSelect">
|
||||||
|
<option v-for="(file) in fileList.configs" :key="file.name" :value="file.name">
|
||||||
|
{{ file.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
<button class="btn btn-primary" @click="downloadConfig">{{ $t('configadmin.Backup') }}
|
<button class="btn btn-primary" @click="downloadConfig">{{ $t('configadmin.Backup') }}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('configadmin.RestoreHeader')" textVariant="text-bg-primary" center-content add-space>
|
<CardElement :text="$t('configadmin.RestoreHeader')" textVariant="text-bg-primary" center-content add-space>
|
||||||
@ -38,8 +51,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!uploading">
|
<div v-else-if="!uploading">
|
||||||
<div class="form-group pt-2 mt-3">
|
<div class="row g-3 align-items-center form-group pt-2">
|
||||||
<input class="form-control" type="file" ref="file" accept=".json" @change="uploadConfig" />
|
<div class="col-sm">
|
||||||
|
<select class="form-select" v-model="restoreFileSelect">
|
||||||
|
<option selected value="config.json">Main Config (config.json)</option>
|
||||||
|
<option selected value="pin_mapping.json">Pin Mapping (pin_mapping.json)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input class="form-control" type="file" ref="file" accept=".json" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<button class="btn btn-primary" @click="uploadConfig">{{ $t('configadmin.Restore') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -89,7 +114,8 @@
|
|||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import { authHeader, handleResponse, isLoggedIn } from '@/utils/authentication';
|
import type { ConfigFileList } from '@/types/Config';
|
||||||
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import * as bootstrap from 'bootstrap';
|
import * as bootstrap from 'bootstrap';
|
||||||
import {
|
import {
|
||||||
BIconArrowLeft,
|
BIconArrowLeft,
|
||||||
@ -119,14 +145,16 @@ export default defineComponent({
|
|||||||
UploadError: "",
|
UploadError: "",
|
||||||
UploadSuccess: false,
|
UploadSuccess: false,
|
||||||
file: {} as Blob,
|
file: {} as Blob,
|
||||||
|
fileList: {} as ConfigFileList,
|
||||||
|
backupFileSelect: "",
|
||||||
|
restoreFileSelect: "config.json",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!isLoggedIn()) {
|
|
||||||
this.$router.push({ path: "/login", query: { returnUrl: this.$router.currentRoute.value.fullPath } });
|
|
||||||
}
|
|
||||||
this.modalFactoryReset = new bootstrap.Modal('#factoryReset');
|
this.modalFactoryReset = new bootstrap.Modal('#factoryReset');
|
||||||
this.loading = false;
|
},
|
||||||
|
created() {
|
||||||
|
this.getFileList();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onFactoryResetModal() {
|
onFactoryResetModal() {
|
||||||
@ -154,27 +182,42 @@ export default defineComponent({
|
|||||||
)
|
)
|
||||||
this.modalFactoryReset.hide();
|
this.modalFactoryReset.hide();
|
||||||
},
|
},
|
||||||
|
getFileList() {
|
||||||
|
this.loading = true;
|
||||||
|
fetch("/api/config/list", { headers: authHeader() })
|
||||||
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
|
.then((data) => {
|
||||||
|
this.fileList = data;
|
||||||
|
if (this.fileList.configs) {
|
||||||
|
this.backupFileSelect = this.fileList.configs[0].name;
|
||||||
|
}
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
downloadConfig() {
|
downloadConfig() {
|
||||||
fetch("/api/config/get", { headers: authHeader() })
|
fetch("/api/config/get?file=" + this.backupFileSelect, { headers: authHeader() })
|
||||||
.then(res => res.blob())
|
.then(res => res.blob())
|
||||||
.then(blob => {
|
.then(blob => {
|
||||||
var file = window.URL.createObjectURL(blob);
|
var file = window.URL.createObjectURL(blob);
|
||||||
var a = document.createElement('a');
|
var a = document.createElement('a');
|
||||||
a.href = file;
|
a.href = file;
|
||||||
a.download = "config.json";
|
a.download = this.backupFileSelect;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
uploadConfig(event: Event | null) {
|
uploadConfig() {
|
||||||
this.uploading = true;
|
this.uploading = true;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
if (event !== null) {
|
const target = this.$refs.file as HTMLInputElement; // event.target as HTMLInputElement;
|
||||||
const target = event.target as HTMLInputElement;
|
if (target.files !== null && target.files?.length > 0) {
|
||||||
if (target.files !== null) {
|
|
||||||
this.file = target.files[0];
|
this.file = target.files[0];
|
||||||
}
|
} else {
|
||||||
|
this.UploadError = this.$t("configadmin.NoFileSelected");
|
||||||
|
this.uploading = false;
|
||||||
|
this.progress = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
request.addEventListener("load", () => {
|
request.addEventListener("load", () => {
|
||||||
@ -196,7 +239,7 @@ export default defineComponent({
|
|||||||
request.withCredentials = true;
|
request.withCredentials = true;
|
||||||
|
|
||||||
formData.append("config", this.file, "config");
|
formData.append("config", this.file, "config");
|
||||||
request.open("post", "/api/config/upload");
|
request.open("post", "/api/config/upload?file=" + this.restoreFileSelect);
|
||||||
authHeader().forEach((value, key) => {
|
authHeader().forEach((value, key) => {
|
||||||
request.setRequestHeader(key, value);
|
request.setRequestHeader(key, value);
|
||||||
});
|
});
|
||||||
@ -205,6 +248,7 @@ export default defineComponent({
|
|||||||
clear() {
|
clear() {
|
||||||
this.UploadError = "";
|
this.UploadError = "";
|
||||||
this.UploadSuccess = false;
|
this.UploadSuccess = false;
|
||||||
|
this.getFileList();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
138
webapp/src/views/DeviceAdminView.vue
Normal file
138
webapp/src/views/DeviceAdminView.vue
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<BasePage :title="$t('deviceadmin.DeviceManager')" :isLoading="dataLoading || pinMappingLoading">
|
||||||
|
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
|
||||||
|
{{ alertMessage }}
|
||||||
|
</BootstrapAlert>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
|
<button class="nav-link active" id="nav-pin-tab" data-bs-toggle="tab" data-bs-target="#nav-pin"
|
||||||
|
type="button" role="tab" aria-controls="nav-pin" aria-selected="true">{{
|
||||||
|
$t('deviceadmin.PinAssignment')
|
||||||
|
}}</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="tab-content" id="nav-tabContent">
|
||||||
|
<div class="tab-pane fade show active" id="nav-pin" role="tabpanel" aria-labelledby="nav-pin-tab"
|
||||||
|
tabindex="0">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<form @submit="savePinConfig">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="inputPinProfile" class="col-sm-2 col-form-label">{{
|
||||||
|
$t('deviceadmin.SelectedProfile')
|
||||||
|
}}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="form-select" id="inputPinProfile"
|
||||||
|
v-model="deviceConfigList.curPin.name">
|
||||||
|
<option v-for="device in pinMappingList" :value="device.name" :key="device.name">
|
||||||
|
{{ device.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-danger mt-3" role="alert" v-html="$t('deviceadmin.ProfileHint')">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PinInfo
|
||||||
|
:selectedPinAssignment="pinMappingList.find(i => i.name === deviceConfigList.curPin.name)"
|
||||||
|
:currentPinAssignment="deviceConfigList.curPin" />
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary mb-3">{{ $t('deviceadmin.Save') }}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</BasePage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import BasePage from '@/components/BasePage.vue';
|
||||||
|
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||||
|
import PinInfo from '@/components/PinInfo.vue';
|
||||||
|
import type { DeviceConfig } from "@/types/DeviceConfig";
|
||||||
|
import type { PinMapping, Device } from "@/types/PinMapping";
|
||||||
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
BasePage,
|
||||||
|
BootstrapAlert,
|
||||||
|
PinInfo,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dataLoading: true,
|
||||||
|
pinMappingLoading: true,
|
||||||
|
deviceConfigList: {} as DeviceConfig,
|
||||||
|
pinMappingList: {} as PinMapping,
|
||||||
|
alertMessage: "",
|
||||||
|
alertType: "info",
|
||||||
|
showAlert: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getDeviceConfig();
|
||||||
|
this.getPinMappingList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getPinMappingList() {
|
||||||
|
this.pinMappingLoading = true;
|
||||||
|
fetch("/api/config/get?file=pin_mapping.json", { headers: authHeader() })
|
||||||
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
|
.then(
|
||||||
|
(data) => {
|
||||||
|
this.pinMappingList = data;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(() => {
|
||||||
|
this.pinMappingList = Array<Device>();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.pinMappingList.push({
|
||||||
|
"name": this.$t('deviceadmin.DefaultProfile')
|
||||||
|
} as Device);
|
||||||
|
this.pinMappingList.sort((a, b) => (a.name < b.name) ? -1 : 1);
|
||||||
|
this.pinMappingLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getDeviceConfig() {
|
||||||
|
this.dataLoading = true;
|
||||||
|
fetch("/api/device/config", { headers: authHeader() })
|
||||||
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
|
.then(
|
||||||
|
(data) => {
|
||||||
|
this.deviceConfigList = data;
|
||||||
|
this.dataLoading = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
savePinConfig(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("data", JSON.stringify(this.deviceConfigList));
|
||||||
|
|
||||||
|
fetch("/api/device/config", {
|
||||||
|
method: "POST",
|
||||||
|
headers: authHeader(),
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
|
.then(
|
||||||
|
(response) => {
|
||||||
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
|
this.alertType = response.type;
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -41,11 +41,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('home.CurrentLimit') }}<template v-if="inverter.limit_absolute > -1"> {{
|
{{ $t('home.CurrentLimit') }}<template v-if="inverter.limit_absolute > -1"> {{
|
||||||
formatNumber(inverter.limit_absolute, 0)
|
$n(inverter.limit_absolute, 'decimalNoDigits')
|
||||||
}} W | </template>{{ formatNumber(inverter.limit_relative, 0) }} %
|
}} W | </template>{{ $n(inverter.limit_relative / 100, 'percent') }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em;">
|
||||||
{{ $t('home.DataAge') }} {{ $t('home.Seconds', {'val': inverter.data_age }) }}
|
{{ $t('home.DataAge') }} {{ $t('home.Seconds', {'val': $n(inverter.data_age) }) }}
|
||||||
<template v-if="inverter.data_age > 300">
|
<template v-if="inverter.data_age > 300">
|
||||||
/ {{ calculateAbsoluteTime(inverter.data_age) }}
|
/ {{ calculateAbsoluteTime(inverter.data_age) }}
|
||||||
</template>
|
</template>
|
||||||
@ -323,7 +323,6 @@ import type { EventlogItems } from '@/types/EventlogStatus';
|
|||||||
import type { LimitConfig } from '@/types/LimitConfig';
|
import type { LimitConfig } from '@/types/LimitConfig';
|
||||||
import type { LimitStatus } from '@/types/LimitStatus';
|
import type { LimitStatus } from '@/types/LimitStatus';
|
||||||
import type { Inverter, LiveData } from '@/types/LiveDataStatus';
|
import type { Inverter, LiveData } from '@/types/LiveDataStatus';
|
||||||
import { formatNumber } from '@/utils';
|
|
||||||
import { authHeader, authUrl, handleResponse, isLoggedIn } from '@/utils/authentication';
|
import { authHeader, authUrl, handleResponse, isLoggedIn } from '@/utils/authentication';
|
||||||
import * as bootstrap from 'bootstrap';
|
import * as bootstrap from 'bootstrap';
|
||||||
import {
|
import {
|
||||||
@ -443,19 +442,20 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
currentLimitAbsolute(): string {
|
currentLimitAbsolute(): string {
|
||||||
if (this.currentLimitList.max_power > 0) {
|
if (this.currentLimitList.max_power > 0) {
|
||||||
return formatNumber(this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100, 2);
|
return this.$n(this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100,
|
||||||
|
'decimalTwoDigits');
|
||||||
}
|
}
|
||||||
return "0";
|
return "0";
|
||||||
},
|
},
|
||||||
currentLimitRelative(): string {
|
currentLimitRelative(): string {
|
||||||
return formatNumber(this.currentLimitList.limit_relative, 2);
|
return this.$n(this.currentLimitList.limit_relative,
|
||||||
|
'decimalTwoDigits');
|
||||||
},
|
},
|
||||||
inverterData(): Inverter[] {
|
inverterData(): Inverter[] {
|
||||||
return this.liveData.inverters;
|
return this.liveData.inverters;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatNumber,
|
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
getInitialData() {
|
getInitialData() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
|
|||||||
@ -59,7 +59,7 @@
|
|||||||
<InputElement v-show="mqttConfigList.mqtt_tls"
|
<InputElement v-show="mqttConfigList.mqtt_tls"
|
||||||
:label="$t('mqttadmin.RootCa')"
|
:label="$t('mqttadmin.RootCa')"
|
||||||
v-model="mqttConfigList.mqtt_root_ca_cert"
|
v-model="mqttConfigList.mqtt_root_ca_cert"
|
||||||
type="textarea" maxlength="2048" rows="10"/>
|
type="textarea" maxlength="2560" rows="10"/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('mqttadmin.LwtParameters')" textVariant="text-bg-primary" add-space
|
<CardElement :text="$t('mqttadmin.LwtParameters')" textVariant="text-bg-primary" add-space
|
||||||
|
|||||||
@ -366,35 +366,35 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz#93815beffd23db46288c787352a8ea31a0c03e5e"
|
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz#93815beffd23db46288c787352a8ea31a0c03e5e"
|
||||||
integrity sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==
|
integrity sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==
|
||||||
|
|
||||||
"@volar/language-core@1.0.19":
|
"@volar/language-core@1.0.24":
|
||||||
version "1.0.19"
|
version "1.0.24"
|
||||||
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.0.19.tgz#787de46fd7de64c50c8500d799905e9da8d1ce36"
|
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.0.24.tgz#5d767571e77728464635e61af1debca944811fe0"
|
||||||
integrity sha512-BRxhwqn66VHeLIxxgV4ybY9NDtwMp2bl1w7085qlK7i1pa4jeFR5lJG2U5qd0oI3e0PIWML+PryxSrKNd3+SZw==
|
integrity sha512-vTN+alJiWwK0Pax6POqrmevbtFW2dXhjwWiW/MW4f48eDYPLdyURWcr8TixO7EN/nHsUBj2udT7igFKPtjyAKg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/source-map" "1.0.19"
|
"@volar/source-map" "1.0.24"
|
||||||
muggle-string "^0.1.0"
|
muggle-string "^0.1.0"
|
||||||
|
|
||||||
"@volar/source-map@1.0.19":
|
"@volar/source-map@1.0.24":
|
||||||
version "1.0.19"
|
version "1.0.24"
|
||||||
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.0.19.tgz#6939a2e47ff99166af653ab92fb8dfafba170279"
|
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.0.24.tgz#ad4c827fea5c26b4bf38a86d983e7deb65b1c61e"
|
||||||
integrity sha512-5fYKsl1evR/QAZ9LADto3kzbYKfpjZLWS9reNpxGR3ODPFTpaJgYk4lqghFyq4yU7/e/ZPZ1zLXjEsnL526URw==
|
integrity sha512-Qsv/tkplx18pgBr8lKAbM1vcDqgkGKQzbChg6NW+v0CZc3G7FLmK+WrqEPzKlN7Cwdc6XVL559Nod8WKAfKr4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
muggle-string "^0.1.0"
|
muggle-string "^0.1.0"
|
||||||
|
|
||||||
"@volar/typescript@1.0.19":
|
"@volar/typescript@1.0.24":
|
||||||
version "1.0.19"
|
version "1.0.24"
|
||||||
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.0.19.tgz#e2dd5b9868c6233df5dafcf514d76f095c6c0233"
|
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.0.24.tgz#f934eda9774b31abdff53efc56782cd2623723d5"
|
||||||
integrity sha512-S6n945uhpc5J1qCVXVV4tz4k1nyxWaoG+wqy9TYdRDazPHeq9l45WDg58g/ehblUWux85TZN8i3zdsLRLkFrdw==
|
integrity sha512-f8hCSk+PfKR1/RQHxZ79V1NpDImHoivqoizK+mstphm25tn/YJ/JnKNjZHB+o21fuW0yKlI26NV3jkVb2Cc/7A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/language-core" "1.0.19"
|
"@volar/language-core" "1.0.24"
|
||||||
|
|
||||||
"@volar/vue-language-core@1.0.19":
|
"@volar/vue-language-core@1.0.24":
|
||||||
version "1.0.19"
|
version "1.0.24"
|
||||||
resolved "https://registry.yarnpkg.com/@volar/vue-language-core/-/vue-language-core-1.0.19.tgz#351f3cf08c1039259d422eabaa49d22fcc5bbaa3"
|
resolved "https://registry.yarnpkg.com/@volar/vue-language-core/-/vue-language-core-1.0.24.tgz#81d180a8e09a53cb575e83acb79a31493891a1a4"
|
||||||
integrity sha512-3mIjJvQ+0tNOp+U9+Nggy92HYIqnltf882UMG9RuNHrd0Jn/rdvjRBs0jNTzwYDV9tn3tjDHGIfQak9XrUCaRg==
|
integrity sha512-2NTJzSgrwKu6uYwPqLiTMuAzi7fAY3yFy5PJ255bGJc82If0Xr+cW8pC80vpjG0D/aVLmlwAdO4+Ya2BI8GdDg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/language-core" "1.0.19"
|
"@volar/language-core" "1.0.24"
|
||||||
"@volar/source-map" "1.0.19"
|
"@volar/source-map" "1.0.24"
|
||||||
"@vue/compiler-dom" "^3.2.45"
|
"@vue/compiler-dom" "^3.2.45"
|
||||||
"@vue/compiler-sfc" "^3.2.45"
|
"@vue/compiler-sfc" "^3.2.45"
|
||||||
"@vue/reactivity" "^3.2.45"
|
"@vue/reactivity" "^3.2.45"
|
||||||
@ -402,13 +402,13 @@
|
|||||||
minimatch "^5.1.1"
|
minimatch "^5.1.1"
|
||||||
vue-template-compiler "^2.7.14"
|
vue-template-compiler "^2.7.14"
|
||||||
|
|
||||||
"@volar/vue-typescript@1.0.19":
|
"@volar/vue-typescript@1.0.24":
|
||||||
version "1.0.19"
|
version "1.0.24"
|
||||||
resolved "https://registry.yarnpkg.com/@volar/vue-typescript/-/vue-typescript-1.0.19.tgz#bd1dc58bf9aecb760a9052fbc34f9e6e6ad6a406"
|
resolved "https://registry.yarnpkg.com/@volar/vue-typescript/-/vue-typescript-1.0.24.tgz#bef9b2bfb1b108c0f6cb12ec6fbf449b43fc8257"
|
||||||
integrity sha512-HKaLCz/lb5xkJ1SyaMmms0Ww/OVStQ16qWttSbHRnnyRV/IDMFrwlovA/bIAPzHUq8EVoDAznRVsCysr2QCOGA==
|
integrity sha512-9a25oHDvGaNC0okRS47uqJI6FxY4hUQZUsxeOUFHcqVxZEv8s17LPuP/pMMXyz7jPygrZubB/qXqHY5jEu/akA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/typescript" "1.0.19"
|
"@volar/typescript" "1.0.24"
|
||||||
"@volar/vue-language-core" "1.0.19"
|
"@volar/vue-language-core" "1.0.24"
|
||||||
|
|
||||||
"@vue/compiler-core@3.2.45":
|
"@vue/compiler-core@3.2.45":
|
||||||
version "3.2.45"
|
version "3.2.45"
|
||||||
@ -850,10 +850,10 @@ escape-string-regexp@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||||
|
|
||||||
eslint-plugin-vue@^9.8.0:
|
eslint-plugin-vue@^9.9.0:
|
||||||
version "9.8.0"
|
version "9.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.8.0.tgz#91de2aabbee8cdbef078ccd4f650a9ecfa445f4f"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.9.0.tgz#ac788ebccd2eb94d846a507df55da50693b80c91"
|
||||||
integrity sha512-E/AXwcTzunyzM83C2QqDHxepMzvI2y6x+mmeYHbVDQlKFqmKYvRrhaVixEeeG27uI44p9oKDFiyCRw4XxgtfHA==
|
integrity sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-utils "^3.0.0"
|
eslint-utils "^3.0.0"
|
||||||
natural-compare "^1.4.0"
|
natural-compare "^1.4.0"
|
||||||
@ -896,10 +896,10 @@ eslint-visitor-keys@^3.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
||||||
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
||||||
|
|
||||||
eslint@^8.31.0:
|
eslint@^8.32.0:
|
||||||
version "8.31.0"
|
version "8.32.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.31.0.tgz#75028e77cbcff102a9feae1d718135931532d524"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.32.0.tgz#d9690056bb6f1a302bd991e7090f5b68fbaea861"
|
||||||
integrity sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==
|
integrity sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/eslintrc" "^1.4.1"
|
"@eslint/eslintrc" "^1.4.1"
|
||||||
"@humanwhocodes/config-array" "^0.11.8"
|
"@humanwhocodes/config-array" "^0.11.8"
|
||||||
@ -2081,15 +2081,15 @@ vite-plugin-compression@^0.5.1:
|
|||||||
debug "^4.3.3"
|
debug "^4.3.3"
|
||||||
fs-extra "^10.0.0"
|
fs-extra "^10.0.0"
|
||||||
|
|
||||||
vite-plugin-css-injected-by-js@^2.2.0:
|
vite-plugin-css-injected-by-js@^2.4.0:
|
||||||
version "2.2.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-2.2.0.tgz#5ff95fbee027698e399a30437335214dc09bb989"
|
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-2.4.0.tgz#32eb77e3ea0c45fbecf1c5f1e65703cccd42ecdb"
|
||||||
integrity sha512-SRGuyY1WUHj7cPzv7AIE0bG5Cb+vioxuq3CkFc1j0b8z5Cy3rXLG8SwxjriylFcZAY7tH2jU4i1bsCJRE/ou6g==
|
integrity sha512-fQkJ5baPEasjjJLxHINLjXuPREO61VIDFUeUqleEBghOLfZZe/k/zrxG5b3kFZXu6JtdI11pnwtj3dh3CN9X4Q==
|
||||||
|
|
||||||
vite@^4.0.3:
|
vite@^4.0.4:
|
||||||
version "4.0.3"
|
version "4.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-4.0.3.tgz#de27ad3f263a03ae9419cdc8bc07721eadcba8b9"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-4.0.4.tgz#4612ce0b47bbb233a887a54a4ae0c6e240a0da31"
|
||||||
integrity sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==
|
integrity sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild "^0.16.3"
|
esbuild "^0.16.3"
|
||||||
postcss "^8.4.20"
|
postcss "^8.4.20"
|
||||||
@ -2136,13 +2136,13 @@ vue-template-compiler@^2.7.14:
|
|||||||
de-indent "^1.0.2"
|
de-indent "^1.0.2"
|
||||||
he "^1.2.0"
|
he "^1.2.0"
|
||||||
|
|
||||||
vue-tsc@^1.0.19:
|
vue-tsc@^1.0.24:
|
||||||
version "1.0.19"
|
version "1.0.24"
|
||||||
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.0.19.tgz#dffbcb8abb6675626719bba438caf954d7615e58"
|
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.0.24.tgz#c0b270a7c8422408d3b6694fee61b39a4b9e4740"
|
||||||
integrity sha512-UuI4G9PwV07Q2U+xYDLP5y3aUXTfuIF0Exy0qXT8+BbLlahubQ2r2PGSodSBnHxAhm/XsrD0KleC2rSzLKXDfQ==
|
integrity sha512-mmU1s5SAqE1nByQAiQnao9oU4vX+mSdsgI8H57SfKH6UVzq/jP9+Dbi2GaV+0b4Cn361d2ln8m6xeU60ApiEXg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/vue-language-core" "1.0.19"
|
"@volar/vue-language-core" "1.0.24"
|
||||||
"@volar/vue-typescript" "1.0.19"
|
"@volar/vue-typescript" "1.0.24"
|
||||||
|
|
||||||
vue@^3.2.45:
|
vue@^3.2.45:
|
||||||
version "3.2.45"
|
version "3.2.45"
|
||||||
|
|||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user