diff --git a/README.md b/README.md index afa3b2e0..b5f5ea57 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ This project is still under development and adds following features: [![OpenDTU Build](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/build.yml) [![cpplint](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml) +## !! IMPORTANT UPGRADE NOTES !! + +If you are upgrading from a version before 15.03.2023 you have to upgrade the partition table of the ESP32. Please follow the [this](docs/UpgradePartition.md) documentation! + ## Background This project was started from [this](https://www.mikrocontroller.net/topic/525778) discussion (Mikrocontroller.net). It was the goal to replace the original Hoymiles DTU (Telemetry Gateway) with their cloud access. With a lot of reverse engineering the Hoymiles protocol was decrypted and analyzed. @@ -217,6 +221,7 @@ Firmware version seems to play not a significant role and cannot be read from th ## Breaking changes Generated using: `git log --date=short --pretty=format:"* %h%x09%ad%x09%s" | grep BREAKING` ``` +* 318136d 2023-03-15 BREAKING CHANGE: Updated partition table: Make sure you have a configuration backup and completly reflash the device! * 3b7aef6 2023-02-13 BREAKING CHANGE: Web API! * d4c838a 2023-02-06 BREAKING CHANGE: Prometheus API! * daf847e 2022-11-14 BREAKING CHANGE: Removed deprecated config parsing method diff --git a/docs/UpgradePartition.md b/docs/UpgradePartition.md new file mode 100644 index 00000000..dce998ae --- /dev/null +++ b/docs/UpgradePartition.md @@ -0,0 +1,24 @@ +# Upgrade Partition + +To be able to install further updates you have to update the partition table of the ESP32. Doing so will **erase** all configuration data. Over The Air update using the web interface is **NOT** possible! + +**So make sure you export a backup of your configuration files before continuing.** + +There are several possibilities to update the partition table: +- Using Visual Studio Code or PlatformIO CLI + + If you have already used Visual Studio Code or the `platformio` command you can use it again to install the latest version. The partition table is upgraded automatically. + +- Any kind of flash interface + + If you like to use any kind of flash interface like `esptool.py`, Espressif Flash Download Tool, ESP_Flasher or esptool-js you have to make sure to upload **ALL** provided .bin files. It is important to enter the correct target addresses. + + | Address | File | + | ---------| ---------------------- | + | 0x1000 | bootloader.bin | + | 0x8000 | partitions.bin | + | 0xe000 | boot_app0.bin | + | 0x10000 | opendtu-*.bin | + + +After upgrading the ESP32 will open the intergrated access point (AP) again. Just connect to it using the default password ("openDTU42"). If you are connected, just visit http://192.168.4.1 and enter the "Configuration Management". Recover the previously backuped config files. \ No newline at end of file diff --git a/include/Configuration.h b/include/Configuration.h index 6b0e2c17..620c0d07 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -128,7 +128,7 @@ struct CONFIG_T { bool Display_PowerSafe; bool Display_ScreenSaver; - bool Display_ShowLogo; + uint8_t Display_Rotation; uint8_t Display_Contrast; }; diff --git a/include/Display_Graphic.h b/include/Display_Graphic.h index 13f6c62d..18c1c3aa 100644 --- a/include/Display_Graphic.h +++ b/include/Display_Graphic.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "defaults.h" #include enum DisplayType_t { @@ -17,25 +18,29 @@ public: void init(DisplayType_t type, uint8_t data, uint8_t clk, uint8_t cs, uint8_t reset); void loop(); + void setContrast(uint8_t contrast); + void setOrientation(uint8_t rotation = DISPLAY_ROTATION); + void setStartupDisplay(); bool enablePowerSafe = true; bool enableScreensaver = true; - bool showLogo = true; - uint8_t contrast = 60; private: void printText(const char* text, uint8_t line); + void calcLineHeights(); + void setFont(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]; + bool _isLarge = false; + uint8_t _lineOffsets[5]; }; extern DisplayGraphicClass Display; \ No newline at end of file diff --git a/include/PinMapping.h b/include/PinMapping.h index 4e2900f0..412f4360 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -2,8 +2,8 @@ #pragma once #include -#include #include +#include #define PINMAPPING_FILENAME "/pin_mapping.json" diff --git a/include/WebApi_prometheus.h b/include/WebApi_prometheus.h index 5eb894ee..f2b5b66a 100644 --- a/include/WebApi_prometheus.h +++ b/include/WebApi_prometheus.h @@ -15,6 +15,8 @@ private: void addField(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, const char* channelName = NULL); + void addPanelInfo(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel); + AsyncWebServer* _server; enum { diff --git a/include/defaults.h b/include/defaults.h index fd6df35d..a365d50b 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -85,7 +85,7 @@ #define DISPLAY_POWERSAFE true #define DISPLAY_SCREENSAVER true -#define DISPLAY_SHOWLOGO true +#define DISPLAY_ROTATION 2 #define DISPLAY_CONTRAST 60 #define VEDIRECT_ENABLED false diff --git a/partitions_custom.csv b/partitions_custom.csv index abf9a200..d18ab18f 100644 --- a/partitions_custom.csv +++ b/partitions_custom.csv @@ -1,6 +1,6 @@ -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x5000, -otadata, data, ota, 0xe000, 0x2000, -app0, app, ota_0, 0x10000, 0x180000, -app1, app, ota_1, 0x190000,0x180000, -spiffs, data, spiffs, ,0x50000 \ No newline at end of file +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1E0000, +app1, app, ota_1, 0x1F0000, 0x1E0000, +spiffs, data, spiffs, 0x3D0000, 0x30000, \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 4da51a62..97272cda 100644 --- a/platformio.ini +++ b/platformio.ini @@ -23,8 +23,8 @@ build_flags = lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer - bblanchon/ArduinoJson @ ^6.20.1 - https://github.com/bertmelis/espMqttClient.git#v1.3.3 + bblanchon/ArduinoJson @ ^6.21.0 + https://github.com/bertmelis/espMqttClient.git#v1.4.1 nrf24/RF24 @ ^1.4.5 olikraus/U8g2 @ ^2.34.13 buelowp/sunset @ ^1.1.7 diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 50002326..d815745f 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -88,7 +88,7 @@ bool ConfigurationClass::write() JsonObject display = device.createNestedObject("display"); display["powersafe"] = config.Display_PowerSafe; display["screensaver"] = config.Display_ScreenSaver; - display["showlogo"] = config.Display_ShowLogo; + display["rotation"] = config.Display_Rotation; display["contrast"] = config.Display_Contrast; JsonArray inverters = doc.createNestedArray("inverters"); @@ -254,7 +254,7 @@ bool ConfigurationClass::read() 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_Rotation = display["rotation"] | DISPLAY_ROTATION; config.Display_Contrast = display["contrast"] | DISPLAY_CONTRAST; JsonArray inverters = doc["inverters"]; diff --git a/src/Display_Graphic.cpp b/src/Display_Graphic.cpp index 23c826ea..eac532cc 100644 --- a/src/Display_Graphic.cpp +++ b/src/Display_Graphic.cpp @@ -5,31 +5,6 @@ #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); } }, @@ -52,56 +27,83 @@ void DisplayGraphicClass::init(DisplayType_t type, uint8_t data, uint8_t clk, ui auto constructor = display_types[_display_type]; _display = constructor(reset, clk, data, cs); _display->begin(); + setContrast(DISPLAY_CONTRAST); + } +} + +void DisplayGraphicClass::calcLineHeights() +{ + uint8_t yOff = 0; + for (uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff += (_display->getMaxCharHeight()); + _lineOffsets[i] = yOff; + } +} + +void DisplayGraphicClass::setFont(uint8_t line) +{ + switch (line) { + case 0: + _display->setFont((_isLarge) ? u8g2_font_ncenB14_tr : u8g2_font_logisoso16_tr); + break; + case 3: + _display->setFont(u8g2_font_5x8_tr); + break; + default: + _display->setFont((_isLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); + break; } } 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; + uint8_t dispX; + if (!_isLarge) { + dispX = (line == 0) ? 5 : 0; } else { - dispX = 5 + ex; + dispX = (line == 0) ? 20 : 5; + } + setFont(line); + + dispX += enableScreensaver ? (_mExtra % 7) : 0; + _display->drawStr(dispX, _lineOffsets[line], text); +} + +void DisplayGraphicClass::setOrientation(uint8_t rotation) +{ + if (_display_type == DisplayType_t::None) { + return; } - // draw the Text, on the calculated pos - _display->drawStr(dispX, _dispY, text); + switch (rotation) { + case 0: + _display->setDisplayRotation(U8G2_R0); + break; + case 1: + _display->setDisplayRotation(U8G2_R1); + break; + case 2: + _display->setDisplayRotation(U8G2_R2); + break; + case 3: + _display->setDisplayRotation(U8G2_R3); + break; + } + + _isLarge = (_display->getWidth() > 100); + calcLineHeights(); +} + +void DisplayGraphicClass::setStartupDisplay() +{ + if (_display_type == DisplayType_t::None) { + return; + } + + _display->clearBuffer(); + printText("OpenDTU!", 0); + _display->sendBuffer(); } void DisplayGraphicClass::loop() @@ -136,20 +138,6 @@ void DisplayGraphicClass::loop() _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); @@ -158,14 +146,14 @@ void DisplayGraphicClass::loop() } else { snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower); } - printText(_fmtText, 1); + printText(_fmtText, 0); _previousMillis = millis(); } //<======================= //=====> Offline =========== else { - printText("offline", 1); + printText("offline", 0); // check if it's time to enter power saving mode if (millis() - _previousMillis >= (_interval * 2)) { _display->setPowerSave(enablePowerSafe); @@ -175,27 +163,34 @@ void DisplayGraphicClass::loop() //=====> Today & Total Production ======= snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay); - printText(_fmtText, 2); + printText(_fmtText, 1); snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal); - printText(_fmtText, 3); + printText(_fmtText, 2); //<======================= //=====> IP or Date-Time ======== if (!(_mExtra % 10) && NetworkSettings.localIP()) { - printText(NetworkSettings.localIP().toString().c_str(), 4); + printText(NetworkSettings.localIP().toString().c_str(), 3); } else { // Get current time time_t now = time(nullptr); strftime(_fmtText, sizeof(_fmtText), "%a %d.%m.%Y %H:%M", localtime(&now)); - printText(_fmtText, 4); + printText(_fmtText, 3); } _display->sendBuffer(); - _dispY = 0; _mExtra++; _lastDisplayUpdate = millis(); } } +void DisplayGraphicClass::setContrast(uint8_t contrast) +{ + if (_display_type == DisplayType_t::None) { + return; + } + _display->setContrast(contrast * 2.55f); +} + DisplayGraphicClass Display; \ No newline at end of file diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index 3703630c..67196fec 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.cpp @@ -126,7 +126,9 @@ void MqttHandleInverterClass::publishField(std::shared_ptr inv return; } - MqttSettings.publish(topic, String(inv->Statistics()->getChannelFieldValue(type, channel, fieldId))); + MqttSettings.publish(topic, String( + inv->Statistics()->getChannelFieldValue(type, channel, fieldId), + static_cast(inv->Statistics()->getChannelFieldDigits(type, channel, fieldId)))); } String MqttHandleInverterClass::getTopic(std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 2cc191cf..1a4674d7 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -64,7 +64,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) displayPinObj[F("reset")] = pin.display_reset; JsonObject display = root.createNestedObject("display"); - display[F("show_logo")] = config.Display_ShowLogo; + display[F("rotation")] = config.Display_Rotation; display[F("power_safe")] = config.Display_PowerSafe; display[F("screensaver")] = config.Display_ScreenSaver; display[F("contrast")] = config.Display_Contrast; @@ -141,15 +141,15 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) 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_Rotation = root[F("display")][F("rotation")].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.setOrientation(config.Display_Rotation); Display.enablePowerSafe = config.Display_PowerSafe; Display.enableScreensaver = config.Display_ScreenSaver; - Display.contrast = config.Display_Contrast; + Display.setContrast(config.Display_Contrast); Configuration.write(); diff --git a/src/WebApi_prometheus.cpp b/src/WebApi_prometheus.cpp index 1a92d31f..cef15e59 100644 --- a/src/WebApi_prometheus.cpp +++ b/src/WebApi_prometheus.cpp @@ -70,6 +70,7 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques if (inv->Statistics()->getLastUpdate() > 0) { for (auto& t : inv->Statistics()->getChannelTypes()) { for (auto& c : inv->Statistics()->getChannelsByType(t)) { + addPanelInfo(stream, serial, i, inv, t, c); addField(stream, serial, i, inv, t, c, FLD_PAC); addField(stream, serial, i, inv, t, c, FLD_UAC); addField(stream, serial, i, inv, t, c, FLD_IAC); @@ -119,4 +120,50 @@ void WebApiPrometheusClass::addField(AsyncResponseStream* stream, String& serial channel, inv->Statistics()->getChannelFieldValue(type, channel, fieldId)); } -} \ No newline at end of file +} + +void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel) +{ + if (type != TYPE_DC) { + return; + } + + const CONFIG_T& config = Configuration.get(); + + const bool printHelp = (idx == 0 && channel == 0); + if (printHelp) { + stream->print(F("# HELP opendtu_PanelInfo panel information\n")); + stream->print(F("# TYPE opendtu_PanelInfo gauge\n")); + } + stream->printf("opendtu_PanelInfo{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\",panelname=\"%s\"} 1\n", + serial.c_str(), + idx, + inv->name(), + channel, + config.Inverter[idx].channel[channel].Name + ); + + if (printHelp) { + stream->print(F("# HELP opendtu_MaxPower panel maximum output power\n")); + stream->print(F("# TYPE opendtu_MaxPower gauge\n")); + } + stream->printf("opendtu_MaxPower{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %d\n", + serial.c_str(), + idx, + inv->name(), + channel, + config.Inverter[idx].channel[channel].MaxChannelPower + ); + + if (printHelp) { + stream->print(F("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n")); + stream->print(F("# TYPE opendtu_YieldTotalOffset gauge\n")); + } + stream->printf("opendtu_YieldTotalOffset{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %f\n", + serial.c_str(), + idx, + inv->name(), + channel, + config.Inverter[idx].channel[channel].YieldTotalOffset + ); +} diff --git a/src/main.cpp b/src/main.cpp index 33066ac9..b96a6aa9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,10 +114,11 @@ void setup() pin.display_clk, pin.display_cs, pin.display_reset); - Display.showLogo = config.Display_ShowLogo; + Display.setOrientation(config.Display_Rotation); Display.enablePowerSafe = config.Display_PowerSafe; Display.enableScreensaver = config.Display_ScreenSaver; - Display.contrast = config.Display_Contrast; + Display.setContrast(config.Display_Contrast); + Display.setStartupDisplay(); MessageOutput.println(F("done")); // Check for default DTU serial diff --git a/webapp/package.json b/webapp/package.json index 538e91a7..27bad5ac 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -13,7 +13,7 @@ "dependencies": { "@popperjs/core": "^2.11.6", "bootstrap": "^5.3.0-alpha1", - "bootstrap-icons-vue": "^1.8.1", + "bootstrap-icons-vue": "^1.10.3", "mitt": "^3.0.0", "spark-md5": "^3.0.2", "vue": "^3.2.47", @@ -21,23 +21,23 @@ "vue-router": "^4.1.6" }, "devDependencies": { - "@intlify/unplugin-vue-i18n": "^0.8.2", + "@intlify/unplugin-vue-i18n": "^0.9.2", "@rushstack/eslint-patch": "^1.2.0", "@types/bootstrap": "^5.2.6", - "@types/node": "^18.14.6", + "@types/node": "^18.15.3", "@types/spark-md5": "^3.0.2", - "@vitejs/plugin-vue": "^4.0.0", + "@vitejs/plugin-vue": "^4.1.0", "@vue/eslint-config-typescript": "^11.0.2", "@vue/tsconfig": "^0.1.3", - "eslint": "^8.35.0", + "eslint": "^8.36.0", "eslint-plugin-vue": "^9.9.0", "npm-run-all": "^4.1.5", - "sass": "^1.58.3", - "terser": "^5.16.5", + "sass": "^1.59.3", + "terser": "^5.16.6", "typescript": "^4.9.5", - "vite": "^4.1.4", + "vite": "^4.2.0", "vite-plugin-compression": "^0.5.1", - "vite-plugin-css-injected-by-js": "^3.0.1", + "vite-plugin-css-injected-by-js": "^3.1.0", "vue-tsc": "^1.2.0" } } diff --git a/webapp/src/components/PinInfo.vue b/webapp/src/components/PinInfo.vue index 45d4ccaa..0770e3d5 100644 --- a/webapp/src/components/PinInfo.vue +++ b/webapp/src/components/PinInfo.vue @@ -11,126 +11,21 @@ - - NRF24 - MISO - {{ selectedPinAssignment?.nrf24?.miso }} - {{ currentPinAssignment?.nrf24?.miso }} - - - MOSI - {{ selectedPinAssignment?.nrf24?.mosi }} - {{ currentPinAssignment?.nrf24?.mosi }} - - - CLK - {{ selectedPinAssignment?.nrf24?.clk }} - {{ currentPinAssignment?.nrf24?.clk }} - - - IRQ - {{ selectedPinAssignment?.nrf24?.irq }} - {{ currentPinAssignment?.nrf24?.irq }} - - - EN - {{ selectedPinAssignment?.nrf24?.en }} - {{ currentPinAssignment?.nrf24?.en }} - - - CS - {{ selectedPinAssignment?.nrf24?.cs }} - {{ currentPinAssignment?.nrf24?.cs }} - - - - Ethernet - enabled - {{ selectedPinAssignment?.eth?.enabled }} - {{ currentPinAssignment?.eth?.enabled }} - - - phy_addr - {{ selectedPinAssignment?.eth?.phy_addr }} - {{ currentPinAssignment?.eth?.phy_addr }} - - - power - {{ selectedPinAssignment?.eth?.power }} - {{ currentPinAssignment?.eth?.power }} - - - mdc - {{ selectedPinAssignment?.eth?.mdc }} - {{ currentPinAssignment?.eth?.mdc }} - - - mdio - {{ selectedPinAssignment?.eth?.mdio }} - {{ currentPinAssignment?.eth?.mdio }} - - - type - {{ selectedPinAssignment?.eth?.type }} - {{ currentPinAssignment?.eth?.type }} - - - clk_mode - {{ selectedPinAssignment?.eth?.clk_mode }} - {{ 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 }} - - - - Victron - RX - {{ selectedPinAssignment?.victron?.rx }} - {{ currentPinAssignment?.victron?.rx }} - - - TX - {{ selectedPinAssignment?.victron?.tx }} - {{ currentPinAssignment?.victron?.tx }} - - - - Battery - RX - {{ selectedPinAssignment?.battery?.rx }} - {{ currentPinAssignment?.battery?.rx }} - - - TX - {{ selectedPinAssignment?.battery?.tx }} - {{ currentPinAssignment?.battery?.tx }} - - + @@ -150,5 +45,53 @@ export default defineComponent({ selectedPinAssignment: { type: Object as PropType, required: true }, currentPinAssignment: { type: Object as PropType, required: true }, }, + computed: { + categories(): string[] { + let curArray: Array = []; + if (this.currentPinAssignment) { + curArray = Object.keys(this.currentPinAssignment as Device); + } + + let selArray: Array = []; + if (this.selectedPinAssignment) { + selArray = Object.keys(this.selectedPinAssignment as Device); + } + + let total: Array = []; + total = total.concat(curArray, selArray); + return Array.from(new Set(total)).filter(cat => cat != 'name').sort(); + }, + }, + methods: { + properties(category: string): string[] { + let curArray: Array = []; + if ((this.currentPinAssignment as Device)[category as keyof Device]) { + curArray = Object.keys((this.currentPinAssignment as Device)[category as keyof Device]); + } + + let selArray: Array = []; + if ((this.selectedPinAssignment as Device)[category as keyof Device]) { + selArray = Object.keys((this.selectedPinAssignment as Device)[category as keyof Device]); + } + + let total: Array = []; + total = total.concat(curArray, selArray); + + return Array.from(new Set(total)).sort(); + }, + isEqual(category: string, prop: string): boolean { + if (!((this.selectedPinAssignment as Device)[category as keyof Device])) { + return false; + } + if (!((this.currentPinAssignment as Device)[category as keyof Device])) { + return false; + } + + return (this.selectedPinAssignment as any)[category][prop] == (this.currentPinAssignment as any)[category][prop]; + }, + capitalizeFirstLetter(value: string): string { + return value.charAt(0).toUpperCase() + value.slice(1); + }, + } }); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index e13d95e6..82f7437f 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -601,8 +601,12 @@ "PowerSafeHint": "Schaltet das Display aus, wenn kein Wechselrichter Strom erzeugt", "Screensaver": "Screensaver aktivieren:", "ScreensaverHint": "Bewegt die Ausgabe bei jeder Aktualisierung um ein Einbrennen zu verhindern (v. a. für OLED-Displays nützlich)", - "ShowLogo": "Logo anzeigen:", "Contrast": "Kontrast ({contrast}):", + "Rotation": "Rotation:", + "rot0": "Keine Rotation", + "rot90": "90 Grad Drehung", + "rot180": "180 Grad Drehung", + "rot270": "270 Grad Drehung", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index e4d7e133..670213dd 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -601,8 +601,12 @@ "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}):", + "Rotation": "Rotation:", + "rot0": "No rotation", + "rot90": "90 degree rotation", + "rot180": "180 degree rotation", + "rot270": "270 degree rotation", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 415d74a7..586e36f0 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -557,8 +557,12 @@ "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}):", + "Rotation": "Rotation:", + "rot0": "No rotation", + "rot90": "90 degree rotation", + "rot180": "180 degree rotation", + "rot270": "270 degree rotation", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/types/DeviceConfig.ts b/webapp/src/types/DeviceConfig.ts index 99ce70c2..8bd87f70 100644 --- a/webapp/src/types/DeviceConfig.ts +++ b/webapp/src/types/DeviceConfig.ts @@ -1,7 +1,7 @@ import type { Device } from "./PinMapping"; export interface Display { - show_logo: boolean; + rotation: number; power_safe: boolean; screensaver: boolean; contrast: number; diff --git a/webapp/src/views/DeviceAdminView.vue b/webapp/src/views/DeviceAdminView.vue index 0eccf5b3..b8da5770 100644 --- a/webapp/src/views/DeviceAdminView.vue +++ b/webapp/src/views/DeviceAdminView.vue @@ -57,8 +57,18 @@ v-model="deviceConfigList.display.screensaver" type="checkbox" :tooltip="$t('deviceadmin.ScreensaverHint')" /> - +
+ +
+ +
+