Merge remote-tracking branch 'tbnobody/OpenDTU/master'

This commit is contained in:
helgeerbe 2023-01-26 10:30:55 +01:00
commit bec6c20531
48 changed files with 824 additions and 202 deletions

View File

@ -87,6 +87,7 @@ Firmware version seems to play not a significant role and cannot be read from th
* Ethernet support * Ethernet support
* Prometheus API endpoint (/api/prometheus/metrics) * Prometheus API endpoint (/api/prometheus/metrics)
* English, german and french web interface * English, german and french web interface
* Displays (SSD1306, SH1106, PCD8544)
## Features for developers ## Features for developers
* The microcontroller part * The microcontroller part

View File

@ -30,6 +30,31 @@ To change the device profile, navigate to the "Device Manager" and selected the
"clk_mode": -1 "clk_mode": -1
} }
}, },
{
"name": "Generic NodeMCU 38 pin with SSD1306",
"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
},
"display": {
"type": 2,
"data": 21,
"clk": 22
}
},
{ {
"name": "Olimex ESP32-POE", "name": "Olimex ESP32-POE",
"nrf24": { "nrf24": {
@ -53,4 +78,28 @@ To change the device profile, navigate to the "Device Manager" and selected the
] ]
``` ```
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. 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. Sample files for some boards can be found [here](DeviceProfiles/). This means you can just flash the generic bin file and upload the json file. Then you select your board and everything works hopyfully as expected.
## Implemented configuration values
| Parameter | Data Type | Description |
| ------------- | --------- | ----------- |
| name | string | Unique name of the profile (max 63 characters) |
| nrf24.miso | number | MISO Pin |
| nrf24.mosi | number | MOSI Pin |
| nrf24.clk | number | Clock Pin |
| nrf24.irq | number | Interrupt Pin |
| nrf24.en | number | Enable Pin |
| nrf24.cs | number | Chip Select Pin |
| eth.enabled | boolean | Enable/Disable the ethernet stack |
| eth.phy_addr | number | Unique PHY addr |
| eth.power | number | Power Pin (if available). Use -1 for not assigned pins. |
| eth.mdc | number | Serial Management Interface MDC Pin. Use -1 for not assigned pins. |
| eth.mdio | number | Serial Management Interface MDIO Pin. Use -1 for not assigned pins. |
| eth.type | number | Possible values:<br>* 0 = ETH_PHY_LAN8720<br>* 1 = ETH_PHY_TLK110<br>* 2 = ETH_PHY_RTL8201<br>* 3 = ETH_PHY_DP83848<br>* 4 = ETH_PHY_DM9051<br>* 5 = ETH_PHY_KSZ8041<br>* 6 = ETH_PHY_KSZ8081 |
| eth.clk_mode | number | Possible values:<br>* 0 = ETH_CLOCK_GPIO0_IN<br>* 1 = ETH_CLOCK_GPIO0_OUT<br>* 2 = ETH_CLOCK_GPIO16_OUT<br>* 3 = ETH_CLOCK_GPIO17_OUT |
| display.type | number | Specify type of display. Possible values:<br>* 0 = None (default)<br>* 1 = PCD8544 <br>* 2 = SSD1306 <br>* 3 = SH1106 |
| display.data | number | Data Pin (e.g. SDA for i2c displays) required for all displays. Use 255 for not assigned pins. |
| display.clk | number | Clock Pin (e.g. SCL for i2c displays) required for SSD1306 and SH1106. Use 255 for not assigned pins. |
| display.cs | number | Chip Select Pin required for PCD8544. Use 255 for not assigned pins. |
| display.reset | number | Reset Pin required for PCD8544, optional for all other displays. Use 255 for not assigned pins. |

View File

@ -0,0 +1,22 @@
[
{
"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
}
}
]

View File

@ -0,0 +1,72 @@
[
{
"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": 0,
"clk_mode": 0
}
},
{
"name": "Generic NodeMCU 32 with SSD1306",
"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": 0,
"clk_mode": 0
},
"display": {
"type": 2,
"data": 21,
"clk": 22
}
},
{
"name": "Generic NodeMCU 32 with SH1106",
"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": 0,
"clk_mode": 0
},
"display": {
"type": 3,
"data": 21,
"clk": 22
}
}
]

View File

@ -0,0 +1,22 @@
[
{
"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
}
}
]

View File

@ -0,0 +1,72 @@
[
{
"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-POE with SSD1306",
"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
},
"display": {
"type": 2,
"data": 33,
"clk": 32
}
},
{
"name": "Olimex ESP32-POE with SH1106",
"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
},
"display": {
"type": 2,
"data": 33,
"clk": 32
}
}
]

View File

@ -0,0 +1,22 @@
[
{
"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
}
}
]

20
docs/Display.md Normal file
View File

@ -0,0 +1,20 @@
# Display integration
OpenDTU currently supports 3 types of displays (SSD1306, SH1106 and PCD8544). Currently only displays with a resolution of 128x64 pixel are supported. To activate a display you have to specify it's type and pin assignment either in the `platformio_override.ini` or in a device profile. Due to the fact that device profiles work with the pre-compiled binary the following documentation will only cover the device profile method.
You can either create your own device profile as described [here](DeviceProfiles.md) or use some pre-defined. The pre-defined profiles can be found [here](DeviceProfiles/). You can simply open the json file with a text editor of your choice to view/edit the pin assignment.
## Uploading Device Profiles
Use the "Config Management" site to upload (Restore) the json file. Make sure to choose "Pin Mapping (pin_mapping.json)" in the combo box. After you click on restore the ESP will restart. At this point, the profile is not yet active. Please read the next chapter.
![](screenshots/14_ConfigManagement.png)
## Selecting the a Device Profile
After you uploaded the device profile you can select the profile in the "Device Manager" view. After a click on "Save" the ESP will be restarted and the pin assignment is active. At this point the display should already show something. Please see the next chapter for display settings.
![](screenshots/20_DeviceManager_Pin.png)
## Display Settings
Display settings can also be found in the "Device Manager".
![](screenshots/21_DeviceManager_Display.png)

View File

@ -2,6 +2,7 @@
More detailed descriptions for some topics can be found here. More detailed descriptions for some topics can be found here.
## [Display Documentation](Display.md)
## [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) ## [Device Profile Documentation](DeviceProfiles.md)

View File

@ -1,122 +0,0 @@
[
{
"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
}
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -8,6 +8,18 @@ here are some screenshots of OpenDTU's web interface.
*** ***
![](15_LimitSettings.png)
***
![](16_PowerSettings.png)
***
![](17_InverterInfo.png)
***
![](02_NetworkAdmin.png) ![](02_NetworkAdmin.png)
*** ***
@ -24,14 +36,42 @@ here are some screenshots of OpenDTU's web interface.
*** ***
![](13_InverterSettings.png)
***
![](22_Security.png)
***
![](06_DtuAdmin.png) ![](06_DtuAdmin.png)
*** ***
![](20_DeviceManager_Pin.png)
***
![](21_DeviceManager_Display.png)
***
![](14_ConfigManagement.png)
***
![](07_FirmwareUpgrade.png) ![](07_FirmwareUpgrade.png)
*** ***
![](19_Reboot.png)
***
![](11_SystemInfo.png)
***
![](08_NetworkInfo.png) ![](08_NetworkInfo.png)
*** ***
@ -44,28 +84,8 @@ here are some screenshots of OpenDTU's web interface.
*** ***
![](11_SystemInfo.png) ![](18_Console.png)
*** ***
![](12_Eventlog.png) ![](12_Eventlog.png)
***
![](13_InverterSettings.png)
***
![](14_ConfigManagement.png)
***
![](15_LimitSettings.png)
***
![](16_PowerSettings.png)
***
![](17_InverterInfo.png)

View File

@ -27,7 +27,7 @@
#define CHAN_MAX_NAME_STRLEN 31 #define CHAN_MAX_NAME_STRLEN 31
#define DEV_MAX_MAPPING_NAME_STRLEN 31 #define DEV_MAX_MAPPING_NAME_STRLEN 63
#define JSON_BUFFER_SIZE 6144 #define JSON_BUFFER_SIZE 6144
@ -96,6 +96,11 @@ struct CONFIG_T {
bool Security_AllowReadonly; bool Security_AllowReadonly;
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1]; char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
bool Display_PowerSafe;
bool Display_ScreenSaver;
bool Display_ShowLogo;
uint8_t Display_Contrast;
}; };
class ConfigurationClass { class ConfigurationClass {

41
include/Display_Graphic.h Normal file
View File

@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <U8g2lib.h>
enum DisplayType_t {
None,
PCD8544,
SSD1306,
SH1106,
};
class DisplayGraphicClass {
public:
DisplayGraphicClass();
~DisplayGraphicClass();
void init(DisplayType_t type, uint8_t data, uint8_t clk, uint8_t cs, uint8_t reset);
void loop();
bool enablePowerSafe = true;
bool enableScreensaver = true;
bool showLogo = true;
uint8_t contrast = 60;
private:
void printText(const char* text, uint8_t line);
U8G2* _display;
DisplayType_t _display_type = DisplayType_t::None;
uint8_t _mExtra;
uint16_t _dispY = 0;
uint16_t _period = 1000;
uint16_t _interval = 60000; // interval at which to power save (milliseconds)
uint32_t _lastDisplayUpdate = 0;
uint32_t _previousMillis = 0;
char _fmtText[32];
};
extern DisplayGraphicClass Display;

View File

@ -24,6 +24,11 @@ struct PinMapping_t {
int eth_mdio; int eth_mdio;
eth_phy_type_t eth_type; eth_phy_type_t eth_type;
eth_clock_mode_t eth_clk_mode; eth_clock_mode_t eth_clk_mode;
uint8_t display_type;
uint8_t display_data;
uint8_t display_clk;
uint8_t display_cs;
uint8_t display_reset;
}; };
class PinMappingClass { class PinMappingClass {

View File

@ -81,6 +81,11 @@
#define DEV_PINMAPPING "" #define DEV_PINMAPPING ""
#define DISPLAY_POWERSAFE true
#define DISPLAY_SCREENSAVER true
#define DISPLAY_SHOWLOGO true
#define DISPLAY_CONTRAST 60
#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

View File

@ -26,6 +26,7 @@ lib_deps =
bblanchon/ArduinoJson @ ^6.20.0 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
olikraus/U8g2 @ ^2.34.13
extra_scripts = extra_scripts =
pre:auto_firmware_version.py pre:auto_firmware_version.py

View File

@ -83,6 +83,12 @@ bool ConfigurationClass::write()
JsonObject device = doc.createNestedObject("device"); JsonObject device = doc.createNestedObject("device");
device["pinmapping"] = config.Dev_PinMapping; device["pinmapping"] = config.Dev_PinMapping;
JsonObject display = device.createNestedObject("display");
display["powersafe"] = config.Display_PowerSafe;
display["screensaver"] = config.Display_ScreenSaver;
display["showlogo"] = config.Display_ShowLogo;
display["contrast"] = config.Display_Contrast;
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();
@ -212,6 +218,12 @@ bool ConfigurationClass::read()
JsonObject device = doc["device"]; JsonObject device = doc["device"];
strlcpy(config.Dev_PinMapping, device["pinmapping"] | DEV_PINMAPPING, sizeof(config.Dev_PinMapping)); strlcpy(config.Dev_PinMapping, device["pinmapping"] | DEV_PINMAPPING, sizeof(config.Dev_PinMapping));
JsonObject display = device["display"];
config.Display_PowerSafe = display["powersafe"] | DISPLAY_POWERSAFE;
config.Display_ScreenSaver = display["screensaver"] | DISPLAY_SCREENSAVER;
config.Display_ShowLogo = display["showlogo"] | DISPLAY_SHOWLOGO;
config.Display_Contrast = display["contrast"] | DISPLAY_CONTRAST;
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>();

198
src/Display_Graphic.cpp Normal file
View File

@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Display_Graphic.h"
#include <Hoymiles.h>
#include <NetworkSettings.h>
#include <map>
#include <time.h>
static uint8_t bmp_logo[] PROGMEM = {
B00000000, B00000000, // ................
B11101100, B00110111, // ..##.######.##..
B11101100, B00110111, // ..##.######.##..
B11100000, B00000111, // .....######.....
B11010000, B00001011, // ....#.####.#....
B10011000, B00011001, // ...##..##..##...
B10000000, B00000001, // .......##.......
B00000000, B00000000, // ................
B01111000, B00011110, // ...####..####...
B11111100, B00111111, // ..############..
B01111100, B00111110, // ..#####..#####..
B00000000, B00000000, // ................
B11111100, B00111111, // ..############..
B11111110, B01111111, // .##############.
B01111110, B01111110, // .######..######.
B00000000, B00000000 // ................
};
static uint8_t bmp_arrow[] PROGMEM = {
B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111,
B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111,
B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000
};
std::map<DisplayType_t, std::function<U8G2*(uint8_t, uint8_t, uint8_t, uint8_t)>> display_types = {
{ DisplayType_t::PCD8544, [](uint8_t reset, uint8_t clock, uint8_t data, uint8_t cs) { return new U8G2_PCD8544_84X48_F_4W_HW_SPI(U8G2_R0, cs, data, reset); } },
{ DisplayType_t::SSD1306, [](uint8_t reset, uint8_t clock, uint8_t data, uint8_t cs) { return new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0, reset, clock, data); } },
{ DisplayType_t::SH1106, [](uint8_t reset, uint8_t clock, uint8_t data, uint8_t cs) { return new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, reset, clock, data); } },
};
DisplayGraphicClass::DisplayGraphicClass()
{}
DisplayGraphicClass::~DisplayGraphicClass()
{
delete _display;
}
void DisplayGraphicClass::init(DisplayType_t type, uint8_t data, uint8_t clk, uint8_t cs, uint8_t reset)
{
_display_type = type;
if (_display_type > DisplayType_t::None) {
auto constructor = display_types[_display_type];
_display = constructor(reset, clk, data, cs);
_display->begin();
}
}
void DisplayGraphicClass::printText(const char* text, uint8_t line)
{
// get the width and height of the display
uint16_t maxWidth = _display->getWidth();
uint16_t maxHeight = _display->getHeight();
// pxMovement +x (0 - 6 px)
uint8_t ex = enableScreensaver ? (_mExtra % 7) : 0;
// set the font size based on the display size
switch (line) {
case 1:
if (maxWidth > 120 && maxHeight > 60) {
_display->setFont(u8g2_font_ncenB14_tr); // large display
} else {
_display->setFont(u8g2_font_logisoso16_tr); // small display
}
break;
case 4:
if (maxWidth > 120 && maxHeight > 60) {
_display->setFont(u8g2_font_5x8_tr); // large display
} else {
_display->setFont(u8g2_font_5x8_tr); // small display
}
break;
default:
if (maxWidth > 120 && maxHeight > 60) {
_display->setFont(u8g2_font_ncenB10_tr); // large display
} else {
_display->setFont(u8g2_font_5x8_tr); // small display
}
break;
}
// get the font height, to calculate the textheight
_dispY += (_display->getMaxCharHeight()) + 1;
// calculate the starting position of the text
uint16_t dispX;
if (line == 1) {
dispX = 20 + ex;
} else {
dispX = 5 + ex;
}
// draw the Text, on the calculated pos
_display->drawStr(dispX, _dispY, text);
}
void DisplayGraphicClass::loop()
{
if (_display_type == DisplayType_t::None) {
return;
}
if ((millis() - _lastDisplayUpdate) > _period) {
float totalPower = 0;
float totalYieldDay = 0;
float totalYieldTotal = 0;
uint8_t isprod = 0;
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i);
if (inv == nullptr) {
continue;
}
if (inv->isProducing()) {
isprod++;
}
totalPower += inv->Statistics()->getChannelFieldValue(CH0, FLD_PAC);
totalYieldDay += inv->Statistics()->getChannelFieldValue(CH0, FLD_YD);
totalYieldTotal += inv->Statistics()->getChannelFieldValue(CH0, FLD_YT);
}
_display->clearBuffer();
// set Contrast of the Display to raise the lifetime
_display->setContrast(contrast);
//=====> Logo and Lighting ==========
// pxMovement +x (0 - 6 px)
uint8_t ex = enableScreensaver ? (_mExtra % 7) : 0;
if (isprod > 0) {
_display->drawXBMP(5 + ex, 1, 8, 17, bmp_arrow);
if (showLogo) {
_display->drawXBMP(_display->getWidth() - 24 + ex, 2, 16, 16, bmp_logo);
}
}
//<=======================
//=====> Actual Production ==========
if ((totalPower > 0) && (isprod > 0)) {
_display->setPowerSave(false);
if (totalPower > 999) {
snprintf(_fmtText, sizeof(_fmtText), "%2.1f kW", (totalPower / 1000));
} else {
snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower);
}
printText(_fmtText, 1);
_previousMillis = millis();
}
//<=======================
//=====> Offline ===========
else {
printText("offline", 1);
// check if it's time to enter power saving mode
if (millis() - _previousMillis >= (_interval * 2)) {
_display->setPowerSave(enablePowerSafe);
}
}
//<=======================
//=====> Today & Total Production =======
snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay);
printText(_fmtText, 2);
snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal);
printText(_fmtText, 3);
//<=======================
//=====> IP or Date-Time ========
if (!(_mExtra % 10) && NetworkSettings.localIP()) {
printText(NetworkSettings.localIP().toString().c_str(), 4);
} else {
// Get current time
time_t now = time(nullptr);
strftime(_fmtText, sizeof(_fmtText), "%a %d.%m.%Y %H:%M", localtime(&now));
printText(_fmtText, 4);
}
_display->sendBuffer();
_dispY = 0;
_mExtra++;
_lastDisplayUpdate = millis();
}
}
DisplayGraphicClass Display;

View File

@ -10,6 +10,26 @@
#define JSON_BUFFER_SIZE 6144 #define JSON_BUFFER_SIZE 6144
#ifndef DISPLAY_TYPE
#define DISPLAY_TYPE 0
#endif
#ifndef DISPLAY_DATA
#define DISPLAY_DATA 255
#endif
#ifndef DISPLAY_CLK
#define DISPLAY_CLK 255
#endif
#ifndef DISPLAY_CS
#define DISPLAY_CS 255
#endif
#ifndef DISPLAY_RESET
#define DISPLAY_RESET 255
#endif
PinMappingClass PinMapping; PinMappingClass PinMapping;
PinMappingClass::PinMappingClass() PinMappingClass::PinMappingClass()
@ -34,6 +54,13 @@ PinMappingClass::PinMappingClass()
_pinMapping.eth_mdio = ETH_PHY_MDIO; _pinMapping.eth_mdio = ETH_PHY_MDIO;
_pinMapping.eth_type = ETH_PHY_TYPE; _pinMapping.eth_type = ETH_PHY_TYPE;
_pinMapping.eth_clk_mode = ETH_CLK_MODE; _pinMapping.eth_clk_mode = ETH_CLK_MODE;
_pinMapping.display_type = DISPLAY_TYPE;
_pinMapping.display_data = DISPLAY_DATA;
_pinMapping.display_clk = DISPLAY_CLK;
_pinMapping.display_cs = DISPLAY_CS;
_pinMapping.display_reset = DISPLAY_RESET;
} }
PinMapping_t& PinMappingClass::get() PinMapping_t& PinMappingClass::get()
@ -80,6 +107,12 @@ bool PinMappingClass::init(const String& deviceMapping)
_pinMapping.eth_type = doc[i]["eth"]["type"] | ETH_PHY_TYPE; _pinMapping.eth_type = doc[i]["eth"]["type"] | ETH_PHY_TYPE;
_pinMapping.eth_clk_mode = doc[i]["eth"]["clk_mode"] | ETH_CLK_MODE; _pinMapping.eth_clk_mode = doc[i]["eth"]["clk_mode"] | ETH_CLK_MODE;
_pinMapping.display_type = doc[i]["display"]["type"] | DISPLAY_TYPE;
_pinMapping.display_data = doc[i]["display"]["data"] | DISPLAY_DATA;
_pinMapping.display_clk = doc[i]["display"]["clk"] | DISPLAY_CLK;
_pinMapping.display_cs = doc[i]["display"]["cs"] | DISPLAY_CS;
_pinMapping.display_reset = doc[i]["display"]["reset"] | DISPLAY_RESET;
return true; return true;
} }
} }

View File

@ -4,6 +4,7 @@
*/ */
#include "WebApi_device.h" #include "WebApi_device.h"
#include "Configuration.h" #include "Configuration.h"
#include "Display_Graphic.h"
#include "PinMapping.h" #include "PinMapping.h"
#include "WebApi.h" #include "WebApi.h"
#include "WebApi_errors.h" #include "WebApi_errors.h"
@ -38,22 +39,35 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
JsonObject curPin = root.createNestedObject("curPin"); JsonObject curPin = root.createNestedObject("curPin");
curPin[F("name")] = config.Dev_PinMapping; curPin[F("name")] = config.Dev_PinMapping;
JsonObject nrfObj = curPin.createNestedObject("nrf24"); JsonObject nrfPinObj = curPin.createNestedObject("nrf24");
nrfObj[F("clk")] = pin.nrf24_clk; nrfPinObj[F("clk")] = pin.nrf24_clk;
nrfObj[F("cs")] = pin.nrf24_cs; nrfPinObj[F("cs")] = pin.nrf24_cs;
nrfObj[F("en")] = pin.nrf24_en; nrfPinObj[F("en")] = pin.nrf24_en;
nrfObj[F("irq")] = pin.nrf24_irq; nrfPinObj[F("irq")] = pin.nrf24_irq;
nrfObj[F("miso")] = pin.nrf24_miso; nrfPinObj[F("miso")] = pin.nrf24_miso;
nrfObj[F("mosi")] = pin.nrf24_mosi; nrfPinObj[F("mosi")] = pin.nrf24_mosi;
JsonObject ethObj = curPin.createNestedObject("eth"); JsonObject ethPinObj = curPin.createNestedObject("eth");
ethObj[F("enabled")] = pin.eth_enabled; ethPinObj[F("enabled")] = pin.eth_enabled;
ethObj[F("phy_addr")] = pin.eth_phy_addr; ethPinObj[F("phy_addr")] = pin.eth_phy_addr;
ethObj[F("power")] = pin.eth_power; ethPinObj[F("power")] = pin.eth_power;
ethObj[F("mdc")] = pin.eth_mdc; ethPinObj[F("mdc")] = pin.eth_mdc;
ethObj[F("mdio")] = pin.eth_mdio; ethPinObj[F("mdio")] = pin.eth_mdio;
ethObj[F("type")] = pin.eth_type; ethPinObj[F("type")] = pin.eth_type;
ethObj[F("clk_mode")] = pin.eth_clk_mode; ethPinObj[F("clk_mode")] = pin.eth_clk_mode;
JsonObject displayPinObj = curPin.createNestedObject("display");
displayPinObj[F("type")] = pin.display_type;
displayPinObj[F("data")] = pin.display_data;
displayPinObj[F("clk")] = pin.display_clk;
displayPinObj[F("cs")] = pin.display_cs;
displayPinObj[F("reset")] = pin.display_reset;
JsonObject display = root.createNestedObject("display");
display[F("show_logo")] = config.Display_ShowLogo;
display[F("power_safe")] = config.Display_PowerSafe;
display[F("screensaver")] = config.Display_ScreenSaver;
display[F("contrast")] = config.Display_Contrast;
response->setLength(); response->setLength();
request->send(response); request->send(response);
@ -98,7 +112,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
return; return;
} }
if (!(root.containsKey("curPin"))) { if (!(root.containsKey("curPin") || root.containsKey("display"))) {
retMsg[F("message")] = F("Values are missing!"); retMsg[F("message")] = F("Values are missing!");
retMsg[F("code")] = WebApiError::GenericValueMissing; retMsg[F("code")] = WebApiError::GenericValueMissing;
response->setLength(); response->setLength();
@ -116,7 +130,19 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
} }
CONFIG_T& config = Configuration.get(); CONFIG_T& config = Configuration.get();
bool performRestart = root[F("curPin")][F("name")].as<String>() != config.Dev_PinMapping;
strlcpy(config.Dev_PinMapping, root[F("curPin")][F("name")].as<String>().c_str(), sizeof(config.Dev_PinMapping)); strlcpy(config.Dev_PinMapping, root[F("curPin")][F("name")].as<String>().c_str(), sizeof(config.Dev_PinMapping));
config.Display_ShowLogo = root[F("display")][F("show_logo")].as<bool>();
config.Display_PowerSafe = root[F("display")][F("power_safe")].as<bool>();
config.Display_ScreenSaver = root[F("display")][F("screensaver")].as<bool>();
config.Display_Contrast = root[F("display")][F("contrast")].as<uint8_t>();
Display.showLogo = config.Display_ShowLogo;
Display.enablePowerSafe = config.Display_PowerSafe;
Display.enableScreensaver = config.Display_ScreenSaver;
Display.contrast = config.Display_Contrast;
Configuration.write(); Configuration.write();
retMsg[F("type")] = F("success"); retMsg[F("type")] = F("success");
@ -126,8 +152,10 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
response->setLength(); response->setLength();
request->send(response); request->send(response);
yield(); if (performRestart) {
delay(1000); yield();
yield(); delay(1000);
ESP.restart(); yield();
ESP.restart();
}
} }

View File

@ -3,6 +3,7 @@
* Copyright (C) 2022 Thomas Basler and others * Copyright (C) 2022 Thomas Basler and others
*/ */
#include "Configuration.h" #include "Configuration.h"
#include "Display_Graphic.h"
#include "MessageOutput.h" #include "MessageOutput.h"
#include "VeDirectFrameHandler.h" #include "VeDirectFrameHandler.h"
#include "MqttHandleDtu.h" #include "MqttHandleDtu.h"
@ -57,6 +58,7 @@ void setup()
MessageOutput.print(F("migrated... ")); MessageOutput.print(F("migrated... "));
Configuration.migrate(); Configuration.migrate();
} }
CONFIG_T& config = Configuration.get();
MessageOutput.println(F("done")); MessageOutput.println(F("done"));
// Load PinMapping // Load PinMapping
@ -66,6 +68,7 @@ void setup()
} else { } else {
MessageOutput.print(F("using default config ")); MessageOutput.print(F("using default config "));
} }
const PinMapping_t& pin = PinMapping.get();
MessageOutput.println(F("done")); MessageOutput.println(F("done"));
// Initialize WiFi // Initialize WiFi
@ -93,9 +96,22 @@ void setup()
WebApi.init(); WebApi.init();
MessageOutput.println(F("done")); MessageOutput.println(F("done"));
// Initialize Display
MessageOutput.print(F("Initialize Display... "));
Display.init(
static_cast<DisplayType_t>(pin.display_type),
pin.display_data,
pin.display_clk,
pin.display_cs,
pin.display_reset);
Display.showLogo = config.Display_ShowLogo;
Display.enablePowerSafe = config.Display_PowerSafe;
Display.enableScreensaver = config.Display_ScreenSaver;
Display.contrast = config.Display_Contrast;
MessageOutput.println(F("done"));
// Check for default DTU serial // Check for default DTU serial
MessageOutput.print(F("Check for default DTU serial... ")); MessageOutput.print(F("Check for default DTU serial... "));
CONFIG_T& config = Configuration.get();
if (config.Dtu_Serial == DTU_SERIAL) { if (config.Dtu_Serial == DTU_SERIAL) {
MessageOutput.print(F("generate serial based on ESP chip id: ")); MessageOutput.print(F("generate serial based on ESP chip id: "));
uint64_t dtuId = Utils::generateDtuSerial(); uint64_t dtuId = Utils::generateDtuSerial();
@ -111,7 +127,6 @@ void setup()
MessageOutput.print(F("Initialize Hoymiles interface... ")); MessageOutput.print(F("Initialize Hoymiles interface... "));
if (PinMapping.isValidNrf24Config()) { if (PinMapping.isValidNrf24Config()) {
SPIClass* spiClass = new SPIClass(HSPI); SPIClass* spiClass = new SPIClass(HSPI);
PinMapping_t& pin = PinMapping.get();
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs); spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
Hoymiles.setMessageOutput(&MessageOutput); Hoymiles.setMessageOutput(&MessageOutput);
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq); Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
@ -177,6 +192,8 @@ void loop()
yield(); yield();
WebApi.loop(); WebApi.loop();
yield(); yield();
Display.loop();
yield();
MessageOutput.loop(); MessageOutput.loop();
yield(); yield();
} }

View File

@ -80,6 +80,33 @@
<td>{{ currentPinAssignment?.eth?.clk_mode }}</td> <td>{{ currentPinAssignment?.eth?.clk_mode }}</td>
</tr> </tr>
<tr>
<td rowspan="6">Display</td>
<td>type</td>
<td>{{ selectedPinAssignment?.display?.type }}</td>
<td>{{ currentPinAssignment?.display?.type }}</td>
</tr>
<tr>
<td>data</td>
<td>{{ selectedPinAssignment?.display?.data }}</td>
<td>{{ currentPinAssignment?.display?.data }}</td>
</tr>
<tr>
<td>clk</td>
<td>{{ selectedPinAssignment?.display?.clk }}</td>
<td>{{ currentPinAssignment?.display?.clk }}</td>
</tr>
<tr>
<td>cs</td>
<td>{{ selectedPinAssignment?.display?.cs }}</td>
<td>{{ currentPinAssignment?.display?.cs }}</td>
</tr>
<tr>
<td>reset</td>
<td>{{ selectedPinAssignment?.display?.reset }}</td>
<td>{{ currentPinAssignment?.display?.reset }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -493,7 +493,7 @@
"Back": "Zurück", "Back": "Zurück",
"Retry": "Wiederholen", "Retry": "Wiederholen",
"OtaStatus": "OTA Status", "OtaStatus": "OTA Status",
"OtaSuccess": "OTA Erfolgreich. Das Gerät wurde automatisch neu gestartet und wird in wenigen Augenblicken wieder zur Verfügung stehen.", "OtaSuccess": "OTA Erfolgreich. Das Gerät wurde automatisch neu gestartet und wird in wenigen Augenblicken wieder zur Verfügung stehen. Bitte nicht vergessen die Weboberfläche neu zu laden!",
"FirmwareUpload": "Firmware hochladen", "FirmwareUpload": "Firmware hochladen",
"UploadProgress": "Hochlade Fortschritt" "UploadProgress": "Hochlade Fortschritt"
}, },
@ -524,6 +524,13 @@
"SelectedProfile": "Ausgewähltes Profil:", "SelectedProfile": "Ausgewähltes Profil:",
"DefaultProfile": "(Standard Einstellungen)", "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.", "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.",
"Display": "Display",
"PowerSafe": "Power Safe aktivieren:",
"PowerSafeHint": "Schaltet das Display aus wenn kein Wechselrichter produziert",
"Screensaver": "Screensaver aktivieren:",
"ScreensaverHint": "Bewegt die Ausgabe bei jeder Aktualisierung um ein Einbrennen zu verhindern. (Nützlich v.a. für OLED Displays)",
"ShowLogo": "Logo anzeigen:",
"Contrast": "Kontrast ({contrast}):",
"Save": "@:dtuadmin.Save" "Save": "@:dtuadmin.Save"
}, },
"pininfo": { "pininfo": {

View File

@ -493,7 +493,7 @@
"Back": "Back", "Back": "Back",
"Retry": "Retry", "Retry": "Retry",
"OtaStatus": "OTA Status", "OtaStatus": "OTA Status",
"OtaSuccess": "OTA Success. The unit has been automatically restarted and will be available again in a few moments.", "OtaSuccess": "OTA Success. The unit has been automatically restarted and will be available again in a few moments. Please do not forget to reload the web interface!",
"FirmwareUpload": "Firmware Upload", "FirmwareUpload": "Firmware Upload",
"UploadProgress": "Upload Progress" "UploadProgress": "Upload Progress"
}, },
@ -524,6 +524,13 @@
"SelectedProfile": "Selected profile:", "SelectedProfile": "Selected profile:",
"DefaultProfile": "(Default settings)", "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.", "ProfileHint": "Your device may stop responding if you select an incompatible profile. In this case, you must perform a deletion via the serial interface.",
"Display": "Display",
"PowerSafe": "Enable Power Safe:",
"PowerSafeHint": "Turn off the display if no inverter is producing.",
"Screensaver": "Enable Screensaver:",
"ScreensaverHint": "Move the display a little bit on each update to prevent burn-in. (Useful especially for OLED displays)",
"ShowLogo": "Show Logo:",
"Contrast": "Contrast ({contrast}):",
"Save": "@:dtuadmin.Save" "Save": "@:dtuadmin.Save"
}, },
"pininfo": { "pininfo": {

View File

@ -8,7 +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", "DeviceManager": "Gestionnaire de périphériques",
"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",
@ -86,7 +86,7 @@
"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!" "12001": "Le profil doit comporter entre 1 et {max} caractères !"
}, },
"home": { "home": {
"LiveData": "Données en direct", "LiveData": "Données en direct",
@ -493,7 +493,7 @@
"Back": "Retour", "Back": "Retour",
"Retry": "Réessayer", "Retry": "Réessayer",
"OtaStatus": "Statut OTA", "OtaStatus": "Statut OTA",
"OtaSuccess": "Succès de l'OTA. L'unité a été automatiquement redémarrée et sera à nouveau disponible dans quelques instants.", "OtaSuccess": "Succès de l'OTA. L'unité a été automatiquement redémarrée et sera à nouveau disponible dans quelques instants. N'oubliez pas de recharger l'interface web !",
"FirmwareUpload": "Téléversement du firmware", "FirmwareUpload": "Téléversement du firmware",
"UploadProgress": "Progression du téléversement" "UploadProgress": "Progression du téléversement"
}, },
@ -519,18 +519,25 @@
"DefaultPasswordLink": "Merci de changer le mot de passe." "DefaultPasswordLink": "Merci de changer le mot de passe."
}, },
"deviceadmin": { "deviceadmin": {
"DeviceManager": "Device-Manager", "DeviceManager": "Gestionnaire de périphériques",
"PinAssignment": "Connection settings", "PinAssignment": "Paramètres de connexion",
"SelectedProfile": "Selected profile:", "SelectedProfile": "Profil sélectionné",
"DefaultProfile": "(Default settings)", "DefaultProfile": "(Réglages par défaut)",
"ProfileHint": "Your device may stop responding if you select an incompatible profile. In this case, you must perform a deletion via the serial interface.", "ProfileHint": "Votre appareil peut cesser de répondre si vous sélectionnez un profil incompatible. Dans ce cas, vous devez effectuer une suppression via l'interface série.",
"Display": "Affichage",
"PowerSafe": "Activer l'économiseur d'énergie",
"PowerSafeHint": "Eteindre l'écran si aucun onduleur n'est en production.",
"Screensaver": "Activer l'écran de veille",
"ScreensaverHint": "Déplacez un peu l'écran à chaque mise à jour pour éviter le phénomène de brûlure. (Utile surtout pour les écrans OLED)",
"ShowLogo": "Afficher le logo",
"Contrast": "Contraste ({contrast}):",
"Save": "@:dtuadmin.Save" "Save": "@:dtuadmin.Save"
}, },
"pininfo": { "pininfo": {
"PinOverview": "Connection overview", "PinOverview": "Vue d'ensemble des connexions",
"Category": "Category", "Category": "Catégorie",
"Name": "Name", "Name": "Nom",
"ValueSelected": "Selected", "ValueSelected": "Sélectionné",
"ValueActive": "Active" "ValueActive": "Activé"
} }
} }

View File

@ -1,5 +1,13 @@
import type { Device } from "./PinMapping"; import type { Device } from "./PinMapping";
export interface Display {
show_logo: boolean;
power_safe: boolean;
screensaver: boolean;
contrast: number;
}
export interface DeviceConfig { export interface DeviceConfig {
curPin: Device; curPin: Device;
display: Display;
} }

View File

@ -17,10 +17,19 @@ export interface Ethernet {
clk_mode: number; clk_mode: number;
} }
export interface Display {
type: number;
data: number;
clk: number;
cs: number;
reset: number;
}
export interface Device { export interface Device {
name: string; name: string;
nrf24: Nrf24; nrf24: Nrf24;
eth: Ethernet; eth: Ethernet;
display: Display;
} }
export interface PinMapping extends Array<Device>{} export interface PinMapping extends Array<Device>{}

View File

@ -4,21 +4,22 @@
{{ alertMessage }} {{ alertMessage }}
</BootstrapAlert> </BootstrapAlert>
<nav> <form @submit="savePinConfig">
<div class="nav nav-tabs" id="nav-tab" role="tablist"> <nav>
<button class="nav-link active" id="nav-pin-tab" data-bs-toggle="tab" data-bs-target="#nav-pin" <div class="nav nav-tabs" id="nav-tab" role="tablist">
type="button" role="tab" aria-controls="nav-pin" aria-selected="true">{{ <button class="nav-link active" id="nav-pin-tab" data-bs-toggle="tab" data-bs-target="#nav-pin"
$t('deviceadmin.PinAssignment') type="button" role="tab" aria-controls="nav-pin" aria-selected="true">{{
}}</button> $t('deviceadmin.PinAssignment')
</div> }}</button>
</nav> <button class="nav-link" id="nav-display-tab" data-bs-toggle="tab" data-bs-target="#nav-display"
<div class="tab-content" id="nav-tabContent"> type="button" role="tab" aria-controls="nav-display">{{ $t('deviceadmin.Display') }}</button>
<div class="tab-pane fade show active" id="nav-pin" role="tabpanel" aria-labelledby="nav-pin-tab" </div>
tabindex="0"> </nav>
<div class="card"> <div class="tab-content" id="nav-tabContent">
<div class="card-body"> <div class="tab-pane fade show active" id="nav-pin" role="tabpanel" aria-labelledby="nav-pin-tab"
tabindex="0">
<form @submit="savePinConfig"> <div class="card">
<div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputPinProfile" class="col-sm-2 col-form-label">{{ <label for="inputPinProfile" class="col-sm-2 col-form-label">{{
$t('deviceadmin.SelectedProfile') $t('deviceadmin.SelectedProfile')
@ -26,7 +27,8 @@
<div class="col-sm-10"> <div class="col-sm-10">
<select class="form-select" id="inputPinProfile" <select class="form-select" id="inputPinProfile"
v-model="deviceConfigList.curPin.name"> v-model="deviceConfigList.curPin.name">
<option v-for="device in pinMappingList" :value="device.name" :key="device.name"> <option v-for="device in pinMappingList" :value="device.name"
:key="device.name">
{{ device.name }} {{ device.name }}
</option> </option>
</select> </select>
@ -39,21 +41,51 @@
<PinInfo <PinInfo
:selectedPinAssignment="pinMappingList.find(i => i.name === deviceConfigList.curPin.name)" :selectedPinAssignment="pinMappingList.find(i => i.name === deviceConfigList.curPin.name)"
:currentPinAssignment="deviceConfigList.curPin" /> :currentPinAssignment="deviceConfigList.curPin" />
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mb-3">{{ $t('deviceadmin.Save') }}</button> <div class="tab-pane fade show" id="nav-display" role="tabpanel" aria-labelledby="nav-display-tab"
</form> tabindex="0">
<div class="card">
<div class="card-body">
<InputElement :label="$t('deviceadmin.PowerSafe')"
v-model="deviceConfigList.display.power_safe" type="checkbox"
:tooltip="$t('deviceadmin.PowerSafeHint')" />
<InputElement :label="$t('deviceadmin.Screensaver')"
v-model="deviceConfigList.display.screensaver" type="checkbox"
:tooltip="$t('deviceadmin.ScreensaverHint')" />
<InputElement :label="$t('deviceadmin.ShowLogo')"
v-model="deviceConfigList.display.show_logo" type="checkbox" />
<div class="row mb-3">
<label for="inputDisplayContrast" class="col-sm-2 col-form-label">{{
$t('deviceadmin.Contrast', { contrast: $n(deviceConfigList.display.contrast / 100,
'percent')
}) }}</label>
<div class="col-sm-10">
<input type="range" class="form-range" min="0" max="100" id="inputDisplayContrast"
v-model="deviceConfigList.display.contrast" />
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<button type="submit" class="btn btn-primary mb-3">{{ $t('deviceadmin.Save') }}</button>
</form>
</BasePage> </BasePage>
</template> </template>
<script lang="ts"> <script lang="ts">
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 InputElement from '@/components/InputElement.vue';
import PinInfo from '@/components/PinInfo.vue'; import PinInfo from '@/components/PinInfo.vue';
import type { DeviceConfig } from "@/types/DeviceConfig"; import type { DeviceConfig } from "@/types/DeviceConfig";
import type { PinMapping, Device } from "@/types/PinMapping"; import type { PinMapping, Device } from "@/types/PinMapping";
@ -64,8 +96,9 @@ export default defineComponent({
components: { components: {
BasePage, BasePage,
BootstrapAlert, BootstrapAlert,
InputElement,
PinInfo, PinInfo,
}, },
data() { data() {
return { return {
dataLoading: true, dataLoading: true,

Binary file not shown.