diff --git a/README.md b/README.md index 8349fc6a..f2144d60 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Firmware version seems to play not a significant role and cannot be read from th * Ethernet support * Prometheus API endpoint (/api/prometheus/metrics) * English, german and french web interface +* Displays (SSD1306, SH1106, PCD8544) ## Features for developers * The microcontroller part diff --git a/docs/DeviceProfiles.md b/docs/DeviceProfiles.md index 269b4af0..083b9ecf 100644 --- a/docs/DeviceProfiles.md +++ b/docs/DeviceProfiles.md @@ -30,6 +30,31 @@ To change the device profile, navigate to the "Device Manager" and selected the "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", "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. \ No newline at end of file +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:
* 0 = ETH_PHY_LAN8720
* 1 = ETH_PHY_TLK110
* 2 = ETH_PHY_RTL8201
* 3 = ETH_PHY_DP83848
* 4 = ETH_PHY_DM9051
* 5 = ETH_PHY_KSZ8041
* 6 = ETH_PHY_KSZ8081 | +| eth.clk_mode | number | Possible values:
* 0 = ETH_CLOCK_GPIO0_IN
* 1 = ETH_CLOCK_GPIO0_OUT
* 2 = ETH_CLOCK_GPIO16_OUT
* 3 = ETH_CLOCK_GPIO17_OUT | +| display.type | number | Specify type of display. Possible values:
* 0 = None (default)
* 1 = PCD8544
* 2 = SSD1306
* 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. | \ No newline at end of file diff --git a/docs/DeviceProfiles/lilygo_ttgo_t-internet_poe.json b/docs/DeviceProfiles/lilygo_ttgo_t-internet_poe.json new file mode 100644 index 00000000..0bc2857c --- /dev/null +++ b/docs/DeviceProfiles/lilygo_ttgo_t-internet_poe.json @@ -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 + } + } +] \ No newline at end of file diff --git a/docs/DeviceProfiles/nodemcu_esp32.json b/docs/DeviceProfiles/nodemcu_esp32.json new file mode 100644 index 00000000..c08575ea --- /dev/null +++ b/docs/DeviceProfiles/nodemcu_esp32.json @@ -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 + } + } +] \ No newline at end of file diff --git a/docs/DeviceProfiles/olimex_esp32_evb.json b/docs/DeviceProfiles/olimex_esp32_evb.json new file mode 100644 index 00000000..9b66926d --- /dev/null +++ b/docs/DeviceProfiles/olimex_esp32_evb.json @@ -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 + } + } +] \ No newline at end of file diff --git a/docs/DeviceProfiles/olimex_esp32_poe.json b/docs/DeviceProfiles/olimex_esp32_poe.json new file mode 100644 index 00000000..001ba7b9 --- /dev/null +++ b/docs/DeviceProfiles/olimex_esp32_poe.json @@ -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 + } + } +] \ No newline at end of file diff --git a/docs/DeviceProfiles/wt32-eth01.json b/docs/DeviceProfiles/wt32-eth01.json new file mode 100644 index 00000000..ebf874e7 --- /dev/null +++ b/docs/DeviceProfiles/wt32-eth01.json @@ -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 + } + } +] \ No newline at end of file diff --git a/docs/Display.md b/docs/Display.md new file mode 100644 index 00000000..43dcb757 --- /dev/null +++ b/docs/Display.md @@ -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) \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 75d07d62..0af56eb3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,7 @@ More detailed descriptions for some topics can be found here. +## [Display Documentation](Display.md) ## [MQTT Topic Documentation](MQTT_Topics.md) ## [Web API Documentation](Web-API.md) ## [Device Profile Documentation](DeviceProfiles.md) diff --git a/docs/pin_mapping.json b/docs/pin_mapping.json deleted file mode 100644 index 2d3d3fde..00000000 --- a/docs/pin_mapping.json +++ /dev/null @@ -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 - } - } -] \ No newline at end of file diff --git a/docs/screenshots/01_LiveView.png b/docs/screenshots/01_LiveView.png index 3121a268..594f8467 100644 Binary files a/docs/screenshots/01_LiveView.png and b/docs/screenshots/01_LiveView.png differ diff --git a/docs/screenshots/02_NetworkAdmin.png b/docs/screenshots/02_NetworkAdmin.png index 2e1505e2..4e1859e2 100644 Binary files a/docs/screenshots/02_NetworkAdmin.png and b/docs/screenshots/02_NetworkAdmin.png differ diff --git a/docs/screenshots/03_NtpAdmin.png b/docs/screenshots/03_NtpAdmin.png index 92a9228b..a3f304b0 100644 Binary files a/docs/screenshots/03_NtpAdmin.png and b/docs/screenshots/03_NtpAdmin.png differ diff --git a/docs/screenshots/04_MqttAdmin.png b/docs/screenshots/04_MqttAdmin.png index d1da0190..7de7ccd4 100644 Binary files a/docs/screenshots/04_MqttAdmin.png and b/docs/screenshots/04_MqttAdmin.png differ diff --git a/docs/screenshots/05_InverterAdmin.png b/docs/screenshots/05_InverterAdmin.png index d794a3b2..43519964 100644 Binary files a/docs/screenshots/05_InverterAdmin.png and b/docs/screenshots/05_InverterAdmin.png differ diff --git a/docs/screenshots/06_DtuAdmin.png b/docs/screenshots/06_DtuAdmin.png index 67d6dd03..ca740396 100644 Binary files a/docs/screenshots/06_DtuAdmin.png and b/docs/screenshots/06_DtuAdmin.png differ diff --git a/docs/screenshots/07_FirmwareUpgrade.png b/docs/screenshots/07_FirmwareUpgrade.png index ecaca952..f4df11c7 100644 Binary files a/docs/screenshots/07_FirmwareUpgrade.png and b/docs/screenshots/07_FirmwareUpgrade.png differ diff --git a/docs/screenshots/08_NetworkInfo.png b/docs/screenshots/08_NetworkInfo.png index e667ea23..aa0063a2 100644 Binary files a/docs/screenshots/08_NetworkInfo.png and b/docs/screenshots/08_NetworkInfo.png differ diff --git a/docs/screenshots/09_NtpInfo.png b/docs/screenshots/09_NtpInfo.png index 0acbe3f7..b75d4fa4 100644 Binary files a/docs/screenshots/09_NtpInfo.png and b/docs/screenshots/09_NtpInfo.png differ diff --git a/docs/screenshots/10_MqttInfo.png b/docs/screenshots/10_MqttInfo.png index 2ab58f3e..24dcc64a 100644 Binary files a/docs/screenshots/10_MqttInfo.png and b/docs/screenshots/10_MqttInfo.png differ diff --git a/docs/screenshots/11_SystemInfo.png b/docs/screenshots/11_SystemInfo.png index a88faac0..cb316d0b 100644 Binary files a/docs/screenshots/11_SystemInfo.png and b/docs/screenshots/11_SystemInfo.png differ diff --git a/docs/screenshots/13_InverterSettings.png b/docs/screenshots/13_InverterSettings.png index be35ded4..a76a3252 100644 Binary files a/docs/screenshots/13_InverterSettings.png and b/docs/screenshots/13_InverterSettings.png differ diff --git a/docs/screenshots/14_ConfigManagement.png b/docs/screenshots/14_ConfigManagement.png index 04c569b0..f0ab7847 100644 Binary files a/docs/screenshots/14_ConfigManagement.png and b/docs/screenshots/14_ConfigManagement.png differ diff --git a/docs/screenshots/17_InverterInfo.png b/docs/screenshots/17_InverterInfo.png index da65f323..04d7aa41 100644 Binary files a/docs/screenshots/17_InverterInfo.png and b/docs/screenshots/17_InverterInfo.png differ diff --git a/docs/screenshots/18_Console.png b/docs/screenshots/18_Console.png new file mode 100644 index 00000000..aae8f912 Binary files /dev/null and b/docs/screenshots/18_Console.png differ diff --git a/docs/screenshots/19_Reboot.png b/docs/screenshots/19_Reboot.png new file mode 100644 index 00000000..d38ba0b7 Binary files /dev/null and b/docs/screenshots/19_Reboot.png differ diff --git a/docs/screenshots/20_DeviceManager_Pin.png b/docs/screenshots/20_DeviceManager_Pin.png new file mode 100644 index 00000000..cb5983f4 Binary files /dev/null and b/docs/screenshots/20_DeviceManager_Pin.png differ diff --git a/docs/screenshots/21_DeviceManager_Display.png b/docs/screenshots/21_DeviceManager_Display.png new file mode 100644 index 00000000..cd27a7b0 Binary files /dev/null and b/docs/screenshots/21_DeviceManager_Display.png differ diff --git a/docs/screenshots/22_Security.png b/docs/screenshots/22_Security.png new file mode 100644 index 00000000..cf844e8f Binary files /dev/null and b/docs/screenshots/22_Security.png differ diff --git a/docs/screenshots/README.md b/docs/screenshots/README.md index f0bbbd51..ab038245 100644 --- a/docs/screenshots/README.md +++ b/docs/screenshots/README.md @@ -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) *** @@ -24,14 +36,42 @@ here are some screenshots of OpenDTU's web interface. *** +![](13_InverterSettings.png) + +*** + +![](22_Security.png) + +*** + ![](06_DtuAdmin.png) *** +![](20_DeviceManager_Pin.png) + +*** + +![](21_DeviceManager_Display.png) + +*** + +![](14_ConfigManagement.png) + +*** + ![](07_FirmwareUpgrade.png) *** +![](19_Reboot.png) + +*** + +![](11_SystemInfo.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) - -*** - -![](13_InverterSettings.png) - -*** - -![](14_ConfigManagement.png) - -*** - -![](15_LimitSettings.png) - -*** - -![](16_PowerSettings.png) - -*** - -![](17_InverterInfo.png) diff --git a/include/Configuration.h b/include/Configuration.h index 36e1f89a..c0cc4b5d 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -27,7 +27,7 @@ #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 @@ -96,6 +96,11 @@ struct CONFIG_T { bool Security_AllowReadonly; char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1]; + + bool Display_PowerSafe; + bool Display_ScreenSaver; + bool Display_ShowLogo; + uint8_t Display_Contrast; }; class ConfigurationClass { diff --git a/include/Display_Graphic.h b/include/Display_Graphic.h new file mode 100644 index 00000000..13f6c62d --- /dev/null +++ b/include/Display_Graphic.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +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; \ No newline at end of file diff --git a/include/PinMapping.h b/include/PinMapping.h index 163dbbe5..1d0ad4d9 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -24,6 +24,11 @@ struct PinMapping_t { int eth_mdio; eth_phy_type_t eth_type; 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 { diff --git a/include/defaults.h b/include/defaults.h index f0473fd1..5e2426c8 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -81,6 +81,11 @@ #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_UPDATESONLY true #define VEDIRECT_POLL_INTERVAL 5 \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 1dd14061..ab4da9b7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,6 +26,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.20.0 https://github.com/bertmelis/espMqttClient.git#v1.3.3 nrf24/RF24 @ ^1.4.5 + olikraus/U8g2 @ ^2.34.13 extra_scripts = pre:auto_firmware_version.py diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 04dbf340..9f171284 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -83,6 +83,12 @@ bool ConfigurationClass::write() JsonObject device = doc.createNestedObject("device"); 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"); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters.createNestedObject(); @@ -212,6 +218,12 @@ bool ConfigurationClass::read() JsonObject device = doc["device"]; 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"]; for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters[i].as(); diff --git a/src/Display_Graphic.cpp b/src/Display_Graphic.cpp new file mode 100644 index 00000000..aa192b92 --- /dev/null +++ b/src/Display_Graphic.cpp @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "Display_Graphic.h" +#include +#include +#include +#include + +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> 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; \ No newline at end of file diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index 2fccabd3..a0f09f77 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -10,6 +10,26 @@ #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::PinMappingClass() @@ -34,6 +54,13 @@ PinMappingClass::PinMappingClass() _pinMapping.eth_mdio = ETH_PHY_MDIO; _pinMapping.eth_type = ETH_PHY_TYPE; _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() @@ -80,6 +107,12 @@ bool PinMappingClass::init(const String& deviceMapping) _pinMapping.eth_type = doc[i]["eth"]["type"] | ETH_PHY_TYPE; _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; } } diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index e3638696..44f6824a 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -4,6 +4,7 @@ */ #include "WebApi_device.h" #include "Configuration.h" +#include "Display_Graphic.h" #include "PinMapping.h" #include "WebApi.h" #include "WebApi_errors.h" @@ -38,22 +39,35 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) 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 nrfPinObj = curPin.createNestedObject("nrf24"); + nrfPinObj[F("clk")] = pin.nrf24_clk; + nrfPinObj[F("cs")] = pin.nrf24_cs; + nrfPinObj[F("en")] = pin.nrf24_en; + nrfPinObj[F("irq")] = pin.nrf24_irq; + nrfPinObj[F("miso")] = pin.nrf24_miso; + nrfPinObj[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; + JsonObject ethPinObj = curPin.createNestedObject("eth"); + ethPinObj[F("enabled")] = pin.eth_enabled; + ethPinObj[F("phy_addr")] = pin.eth_phy_addr; + ethPinObj[F("power")] = pin.eth_power; + ethPinObj[F("mdc")] = pin.eth_mdc; + ethPinObj[F("mdio")] = pin.eth_mdio; + ethPinObj[F("type")] = pin.eth_type; + 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(); request->send(response); @@ -98,7 +112,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) return; } - if (!(root.containsKey("curPin"))) { + if (!(root.containsKey("curPin") || root.containsKey("display"))) { retMsg[F("message")] = F("Values are missing!"); retMsg[F("code")] = WebApiError::GenericValueMissing; response->setLength(); @@ -116,7 +130,19 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); + bool performRestart = root[F("curPin")][F("name")].as() != config.Dev_PinMapping; + strlcpy(config.Dev_PinMapping, root[F("curPin")][F("name")].as().c_str(), sizeof(config.Dev_PinMapping)); + config.Display_ShowLogo = root[F("display")][F("show_logo")].as(); + config.Display_PowerSafe = root[F("display")][F("power_safe")].as(); + config.Display_ScreenSaver = root[F("display")][F("screensaver")].as(); + config.Display_Contrast = root[F("display")][F("contrast")].as(); + + Display.showLogo = config.Display_ShowLogo; + Display.enablePowerSafe = config.Display_PowerSafe; + Display.enableScreensaver = config.Display_ScreenSaver; + Display.contrast = config.Display_Contrast; + Configuration.write(); retMsg[F("type")] = F("success"); @@ -126,8 +152,10 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) response->setLength(); request->send(response); - yield(); - delay(1000); - yield(); - ESP.restart(); + if (performRestart) { + yield(); + delay(1000); + yield(); + ESP.restart(); + } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 503ca976..e15dbf6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2022 Thomas Basler and others */ #include "Configuration.h" +#include "Display_Graphic.h" #include "MessageOutput.h" #include "VeDirectFrameHandler.h" #include "MqttHandleDtu.h" @@ -57,6 +58,7 @@ void setup() MessageOutput.print(F("migrated... ")); Configuration.migrate(); } + CONFIG_T& config = Configuration.get(); MessageOutput.println(F("done")); // Load PinMapping @@ -66,6 +68,7 @@ void setup() } else { MessageOutput.print(F("using default config ")); } + const PinMapping_t& pin = PinMapping.get(); MessageOutput.println(F("done")); // Initialize WiFi @@ -93,9 +96,22 @@ void setup() WebApi.init(); MessageOutput.println(F("done")); + // Initialize Display + MessageOutput.print(F("Initialize Display... ")); + Display.init( + static_cast(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 MessageOutput.print(F("Check for default DTU serial... ")); - CONFIG_T& config = Configuration.get(); if (config.Dtu_Serial == DTU_SERIAL) { MessageOutput.print(F("generate serial based on ESP chip id: ")); uint64_t dtuId = Utils::generateDtuSerial(); @@ -111,7 +127,6 @@ void setup() MessageOutput.print(F("Initialize Hoymiles interface... ")); if (PinMapping.isValidNrf24Config()) { SPIClass* spiClass = new SPIClass(HSPI); - PinMapping_t& pin = PinMapping.get(); spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs); Hoymiles.setMessageOutput(&MessageOutput); Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq); @@ -177,6 +192,8 @@ void loop() yield(); WebApi.loop(); yield(); + Display.loop(); + yield(); MessageOutput.loop(); yield(); } \ No newline at end of file diff --git a/webapp/src/components/PinInfo.vue b/webapp/src/components/PinInfo.vue index e3946b57..de7524b7 100644 --- a/webapp/src/components/PinInfo.vue +++ b/webapp/src/components/PinInfo.vue @@ -80,6 +80,33 @@ {{ currentPinAssignment?.eth?.clk_mode }} + + Display + type + {{ selectedPinAssignment?.display?.type }} + {{ currentPinAssignment?.display?.type }} + + + data + {{ selectedPinAssignment?.display?.data }} + {{ currentPinAssignment?.display?.data }} + + + clk + {{ selectedPinAssignment?.display?.clk }} + {{ currentPinAssignment?.display?.clk }} + + + cs + {{ selectedPinAssignment?.display?.cs }} + {{ currentPinAssignment?.display?.cs }} + + + reset + {{ selectedPinAssignment?.display?.reset }} + {{ currentPinAssignment?.display?.reset }} + + diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index ecf47651..4cfe1e91 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -493,7 +493,7 @@ "Back": "Zurück", "Retry": "Wiederholen", "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", "UploadProgress": "Hochlade Fortschritt" }, @@ -524,6 +524,13 @@ "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.", + "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" }, "pininfo": { diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 1586be7b..9d34c093 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -493,7 +493,7 @@ "Back": "Back", "Retry": "Retry", "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", "UploadProgress": "Upload Progress" }, @@ -524,6 +524,13 @@ "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.", + "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" }, "pininfo": { diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index a7fac46d..e2a37f27 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -8,7 +8,7 @@ "InverterSettings": "Paramètres des onduleurs", "SecuritySettings": "Paramètres de sécurité", "DTUSettings": "Paramètres DTU", - "DeviceManager": "Device-Manager", + "DeviceManager": "Gestionnaire de périphériques", "VedirectSettings": "Paramètres Ve.direct", "ConfigManagement": "Gestion de la configuration", "FirmwareUpgrade": "Mise à jour du firmware", @@ -86,7 +86,7 @@ "10002": "Authentification réussie !", "11001": "@:apiresponse.2001", "11002": "@:apiresponse:5004", - "12001": "Profil must between 1 and {max} characters long!" + "12001": "Le profil doit comporter entre 1 et {max} caractères !" }, "home": { "LiveData": "Données en direct", @@ -493,7 +493,7 @@ "Back": "Retour", "Retry": "Réessayer", "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", "UploadProgress": "Progression du téléversement" }, @@ -519,18 +519,25 @@ "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.", + "DeviceManager": "Gestionnaire de périphériques", + "PinAssignment": "Paramètres de connexion", + "SelectedProfile": "Profil sélectionné", + "DefaultProfile": "(Réglages par défaut)", + "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" }, "pininfo": { - "PinOverview": "Connection overview", - "Category": "Category", - "Name": "Name", - "ValueSelected": "Selected", - "ValueActive": "Active" + "PinOverview": "Vue d'ensemble des connexions", + "Category": "Catégorie", + "Name": "Nom", + "ValueSelected": "Sélectionné", + "ValueActive": "Activé" } } \ No newline at end of file diff --git a/webapp/src/types/DeviceConfig.ts b/webapp/src/types/DeviceConfig.ts index 51b967b7..99ce70c2 100644 --- a/webapp/src/types/DeviceConfig.ts +++ b/webapp/src/types/DeviceConfig.ts @@ -1,5 +1,13 @@ import type { Device } from "./PinMapping"; +export interface Display { + show_logo: boolean; + power_safe: boolean; + screensaver: boolean; + contrast: number; +} + export interface DeviceConfig { curPin: Device; + display: Display; } \ No newline at end of file diff --git a/webapp/src/types/PinMapping.ts b/webapp/src/types/PinMapping.ts index fa7bf493..0445c1f8 100644 --- a/webapp/src/types/PinMapping.ts +++ b/webapp/src/types/PinMapping.ts @@ -17,10 +17,19 @@ export interface Ethernet { clk_mode: number; } +export interface Display { + type: number; + data: number; + clk: number; + cs: number; + reset: number; +} + export interface Device { name: string; nrf24: Nrf24; eth: Ethernet; + display: Display; } export interface PinMapping extends Array{} \ No newline at end of file diff --git a/webapp/src/views/DeviceAdminView.vue b/webapp/src/views/DeviceAdminView.vue index cc6606a8..0eccf5b3 100644 --- a/webapp/src/views/DeviceAdminView.vue +++ b/webapp/src/views/DeviceAdminView.vue @@ -4,21 +4,22 @@ {{ alertMessage }} - -