diff --git a/evcharger-app/CMakeLists.txt b/evcharger-app/CMakeLists.txt index 5d04d39..39b9b2b 100644 --- a/evcharger-app/CMakeLists.txt +++ b/evcharger-app/CMakeLists.txt @@ -115,6 +115,8 @@ qt_add_qml_module(evcharger-app qml/HotspotPage.qml qml/InformationsTabPage.qml qml/InformationChartPanel.qml + qml/IndexedEditValueItem.qml + qml/IndexedOnOffSwitch.qml qml/KwhLimitPage.qml qml/LanguageSelector.qml qml/LedPage.qml @@ -124,6 +126,7 @@ qt_add_qml_module(evcharger-app qml/MainScreen.qml qml/MaterialIcon.qml qml/MaterialIconFont.qml + qml/MeasurementRow.qml qml/MqttPage.qml qml/NamePage.qml qml/NavigationItem.qml @@ -144,6 +147,10 @@ qt_add_qml_module(evcharger-app qml/SelectPhaseSwitchModeItem.qml qml/SendMessageButton.qml qml/SensorsConfigurationPage.qml + qml/CurrentSensorPage.qml + qml/CurrentSensorsPage.qml + qml/SensorCategoriesPage.qml + qml/SensorCategoryPage.qml qml/SettingsCard.qml qml/SettingsLabel.qml qml/SettingsSectionLabel.qml @@ -159,6 +166,7 @@ qt_add_qml_module(evcharger-app qml/TimePicker.qml qml/TitleText.qml qml/VerticalTabButton.qml + qml/VoltageSensorsPage.qml qml/WhiteBox.qml qml/WhiteCheckDelegate.qml qml/WhiteItemDelegate.qml diff --git a/evcharger-app/i18n/qml_da.ts b/evcharger-app/i18n/qml_da.ts index de457bb..f9f2085 100644 --- a/evcharger-app/i18n/qml_da.ts +++ b/evcharger-app/i18n/qml_da.ts @@ -1015,6 +1015,163 @@ Strømniveauer + + CurrentSensorPage + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Live measurement + + + + + Current + + + + + A + + + + + Active power + + + + + W + + + + + Power factor + + + + + Voltage + + + + + V + + + + + Sensor settings + + + + + Electrical phase + + + + + Invert measurement direction + + + + + Current transformer + + + + + Enter the primary and secondary current printed on the current transformer. + + + + + Primary current + + + + + + %1 A + + + + + Secondary current + + + + + CurrentSensorsPage + + + Current sensors + + + + + Current sensor %1 + + + + + No measurement available + + + + + %1 A + + + + + %1 W + %1 W + + + + power factor %1 + + + + + Select a sensor to inspect its live data and configure its phase, direction and transformer ratio. + + + + + Calibration + + + + + Current calibration factor + + + + + Phase calibration factor + + + DailyTripPage @@ -1679,28 +1836,28 @@ -Switch firmware partition? + Switch firmware partition? Skift firmwarepartition? -Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. + Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. Skift til firmware %1, og genstart laderen? Forbindelsen til laderen afbrydes midlertidigt. -Cancel + Cancel Annuller -Switch and reboot + Switch and reboot Skift og genstart -OTHER PARTITION + OTHER PARTITION ANDEN PARTITION @@ -3054,12 +3211,325 @@ Ingen enhedsforbindelse indstillet! + + SensorCategoriesPage + + + Energy categories + + + + + %1 W + %1 W + + + + %1 Wh imported + + + + + hidden + + + + + No measurement available + + + + + Categories combine one or more current sensors into meaningful totals such as grid, home, car, solar or battery. + + + + + SensorCategoryPage + + + Current sensor %1 + + + + + None + Ingen + + + + Home + + + + + Grid + Elnet + + + + Car + Bil + + + + Relay + + + + + Solar + + + + + Battery + + + + + Live data + + + + + Power + + + + + W + + + + + Current L1 + + + + + + + + A + + + + + Current L2 + + + + + Current L3 + + + + + Neutral current + + + + + Imported energy + + + + + + + + + + + + + + Wh + + + + + Exported energy + + + + + Imported energy L1 + + + + + Exported energy L1 + + + + + Imported energy L2 + + + + + Exported energy L2 + + + + + Imported energy L3 + + + + + Exported energy L3 + + + + + Imported neutral energy + + + + + Exported neutral energy + + + + + Category settings + + + + + Show category + + + + + Category name + + + + + Category icon + + + + + Temporary external power + + + + + Inactive + + + + + %1 W + %1 W + + + + External power is a live input and expires if it is not refreshed by the sender. + + + + + Sensor factors + + + + + Each factor multiplies that sensor's current and power before adding it to this category. Use 0 to exclude a sensor, 1 to add it unchanged, or -1 to subtract it. + + + + + + Reset category + + + + + Reset this custom category, including its name, icon, factors and energy counters. + + + + + Reset this category's visibility, sensor factors and energy counters to their defaults. + + + + + Reset category? + + + + + This resets the configuration and accumulated energy of %1. + + + SensorsConfigurationPage - Sensors Configuration - Sensorkonfiguration + Sensorkonfiguration + + + + Sensors and categories + + + + + Measurement + + + + + Measurement service enabled + + + + + Continuous raw measurement can reduce controller performance. + + + + + Measurement update rate + + + + + Voltage sensors + + + + + Live voltages, phase assignment and calibration + + + + + Current sensors + + + + + Live current, power, phase, direction and CT ratio + + + + + Energy categories + + + + + Power, energy counters and sensor factors + @@ -3153,122 +3623,122 @@ Jordingskontrol - + Sensors Configuration Sensorkonfiguration - + Sensors Sensorer - + Categories Kategorier - + Connection Forbindelse - + Wi-Fi Wi-Fi - + Cellular Mobilnetværk - + Hotspot Hotspot - + Ethernet Ethernet - + Cloud Sky - + OCPP OCPP - + MQTT MQTT - + API Settings API-indstillinger - + General Generelt - + Name Navn - + Switch Language Skift sprog - + Notifications Meddelelser - + Date and time Dato og tid - + LED LED - + Controller Styreenhed - + Display settings Skærmindstillinger - + About Om appen - + Firmware Firmware - + Hardware information Hardwareoplysninger - + Licenses Licenser @@ -3307,6 +3777,122 @@ PM + + VoltageSensorsPage + + + Voltage sensors + + + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Voltage sensor %1 + + + + + Live measurements + + + + + Voltage L1 + + + + + + + + V + + + + + Voltage L2 + + + + + Voltage L3 + + + + + Neutral voltage + + + + + Frequency + + + + + Hz + + + + + Phase assignment + + + + + Assign each physical voltage input to the electrical phase it measures. + + + + + Voltage input 1 + + + + + Voltage input 2 + + + + + Voltage input 3 + + + + + Voltage input 4 + + + + + Calibration + + + + + Voltage calibration factor + + + WiFiErrorsPage diff --git a/evcharger-app/i18n/qml_de.ts b/evcharger-app/i18n/qml_de.ts index c9c87b3..9e41e05 100644 --- a/evcharger-app/i18n/qml_de.ts +++ b/evcharger-app/i18n/qml_de.ts @@ -1123,6 +1123,163 @@ Strompegel + + CurrentSensorPage + + + L1 + L1 + + + + L2 + L2 + + + + L3 + L3 + + + + Neutral + Neutralleiter + + + + Live measurement + Live-Messwerte + + + + Current + Strom + + + + A + A + + + + Active power + Wirkleistung + + + + W + W + + + + Power factor + Leistungsfaktor + + + + Voltage + Spannung + + + + V + V + + + + Sensor settings + Sensoreinstellungen + + + + Electrical phase + Elektrische Phase + + + + Invert measurement direction + Messrichtung umkehren + + + + Current transformer + Stromwandler + + + + Enter the primary and secondary current printed on the current transformer. + Primär- und Sekundärstrom laut Beschriftung des Stromwandlers eingeben. + + + + Primary current + Primärstrom + + + + + %1 A + %1 A + + + + Secondary current + Sekundärstrom + + + + CurrentSensorsPage + + + Current sensors + Stromsensoren + + + + Current sensor %1 + Stromsensor %1 + + + + No measurement available + Kein Messwert verfügbar + + + + %1 A + %1 A + + + + %1 W + %1 W + + + + power factor %1 + Leistungsfaktor %1 + + + + Select a sensor to inspect its live data and configure its phase, direction and transformer ratio. + Sensor auswählen, um Live-Messwerte anzuzeigen und Phase, Messrichtung sowie Wandlerverhältnis einzustellen. + + + + Calibration + Kalibrierung + + + + Current calibration factor + Strom-Kalibrierfaktor + + + + Phase calibration factor + Phasen-Kalibrierfaktor + + DailyTripPage @@ -1842,28 +1999,28 @@ -Switch firmware partition? + Switch firmware partition? Firmware-Partition wechseln? -Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. + Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. Zu Firmware %1 wechseln und das Ladegerät neu starten? Die Verbindung zum Ladegerät wird vorübergehend getrennt. -Cancel + Cancel Abbrechen -Switch and reboot + Switch and reboot Wechseln und neu starten -OTHER PARTITION + OTHER PARTITION ANDERE PARTITION @@ -2987,7 +3144,7 @@ %1 W - + %1 W @@ -3334,12 +3491,325 @@ + + SensorCategoriesPage + + + Energy categories + Energiekategorien + + + + %1 W + %1 W + + + + %1 Wh imported + %1 Wh bezogen + + + + hidden + ausgeblendet + + + + No measurement available + Kein Messwert verfügbar + + + + Categories combine one or more current sensors into meaningful totals such as grid, home, car, solar or battery. + Kategorien fassen einen oder mehrere Stromsensoren zu aussagekräftigen Summen wie Netz, Haus, Auto, Solar oder Batterie zusammen. + + + + SensorCategoryPage + + + Current sensor %1 + Stromsensor %1 + + + + None + Keines + + + + Home + Haus + + + + Grid + Netz + + + + Car + Auto + + + + Relay + Relais + + + + Solar + Solar + + + + Battery + Batterie + + + + Live data + Live-Daten + + + + Power + Leistung + + + + W + W + + + + Current L1 + Strom L1 + + + + + + + A + A + + + + Current L2 + Strom L2 + + + + Current L3 + Strom L3 + + + + Neutral current + Neutralleiterstrom + + + + Imported energy + Bezogene Energie + + + + + + + + + + + + + Wh + Wh + + + + Exported energy + Eingespeiste Energie + + + + Imported energy L1 + Bezogene Energie L1 + + + + Exported energy L1 + Eingespeiste Energie L1 + + + + Imported energy L2 + Bezogene Energie L2 + + + + Exported energy L2 + Eingespeiste Energie L2 + + + + Imported energy L3 + Bezogene Energie L3 + + + + Exported energy L3 + Eingespeiste Energie L3 + + + + Imported neutral energy + Bezogene Neutralleiterenergie + + + + Exported neutral energy + Eingespeiste Neutralleiterenergie + + + + Category settings + Kategorieeinstellungen + + + + Show category + Kategorie anzeigen + + + + Category name + Kategoriename + + + + Category icon + Kategoriesymbol + + + + Temporary external power + Temporäre externe Leistung + + + + Inactive + Inaktiv + + + + %1 W + %1 W + + + + External power is a live input and expires if it is not refreshed by the sender. + Die externe Leistung ist ein Live-Eingang und verfällt, wenn sie vom Absender nicht regelmäßig aktualisiert wird. + + + + Sensor factors + Sensorfaktoren + + + + Each factor multiplies that sensor's current and power before adding it to this category. Use 0 to exclude a sensor, 1 to add it unchanged, or -1 to subtract it. + Jeder Faktor multipliziert Strom und Leistung des Sensors, bevor sie zu dieser Kategorie addiert werden. Mit 0 wird der Sensor ausgeschlossen, mit 1 unverändert addiert und mit -1 subtrahiert. + + + + + Reset category + Kategorie zurücksetzen + + + + Reset this custom category, including its name, icon, factors and energy counters. + Diese benutzerdefinierte Kategorie einschließlich Name, Symbol, Faktoren und Energiezählern zurücksetzen. + + + + Reset this category's visibility, sensor factors and energy counters to their defaults. + Sichtbarkeit, Sensorfaktoren und Energiezähler dieser Kategorie auf die Standardwerte zurücksetzen. + + + + Reset category? + Kategorie zurücksetzen? + + + + This resets the configuration and accumulated energy of %1. + Dadurch werden die Konfiguration und die erfasste Energie von %1 zurückgesetzt. + + SensorsConfigurationPage - Sensors Configuration - Sensorkonfiguration + Sensorkonfiguration + + + + Sensors and categories + Sensoren und Kategorien + + + + Measurement + Messung + + + + Measurement service enabled + Messdienst aktiviert + + + + Continuous raw measurement can reduce controller performance. + Die kontinuierliche Rohdatenmessung kann die Controllerleistung verringern. + + + + Measurement update rate + Messaktualisierungsrate + + + + Voltage sensors + Spannungssensoren + + + + Live voltages, phase assignment and calibration + Live-Spannungen, Phasenzuordnung und Kalibrierung + + + + Current sensors + Stromsensoren + + + + Live current, power, phase, direction and CT ratio + Live-Strom und -Leistung, Phase, Messrichtung und Stromwandlerverhältnis + + + + Energy categories + Energiekategorien + + + + Power, energy counters and sensor factors + Leistung, Energiezähler und Sensorfaktoren @@ -3428,102 +3898,102 @@ Erdungsprüfung - + Sensors Sensoren - + Categories Kategorien - + Wi-Fi WLAN - + Cellular Mobilfunk - + Hotspot Hotspot - + Ethernet Ethernet - + OCPP OCPP - + Cloud Cloud - + MQTT MQTT - + API Settings API Einstellungen - + Name Name - + Switch Language Sprache ändern - + Notifications Benachrichtigungen - + Date and time Datum und Uhrzeit - + LED LED - + Controller Controller - + Display settings Display - + Firmware Firmware - + Hardware information Hardwareinformationen - + Licenses Lizenzen @@ -3541,7 +4011,7 @@ Kabel • Zugangskontrolle • Passwort • Netz • Erdungsprüfung - + Sensors Configuration Sensorkonfiguration @@ -3550,7 +4020,7 @@ Sensoren • Kategorien - + Connection Verbindung @@ -3559,7 +4029,7 @@ WLAN • Hotspot • OCPP • API Einstellungen - + General Allgemein @@ -3568,7 +4038,7 @@ Name • Sprache ändern • Benachrichtigungen • Datum und Uhrzeit • LED • Controller - + About Über @@ -3611,6 +4081,122 @@ PM + + VoltageSensorsPage + + + Voltage sensors + Spannungssensoren + + + + L1 + L1 + + + + L2 + L2 + + + + L3 + L3 + + + + Neutral + Neutralleiter + + + + Voltage sensor %1 + Spannungssensor %1 + + + + Live measurements + Live-Messwerte + + + + Voltage L1 + Spannung L1 + + + + + + + V + V + + + + Voltage L2 + Spannung L2 + + + + Voltage L3 + Spannung L3 + + + + Neutral voltage + Neutralleiterspannung + + + + Frequency + Frequenz + + + + Hz + Hz + + + + Phase assignment + Phasenzuordnung + + + + Assign each physical voltage input to the electrical phase it measures. + Jeden physischen Spannungseingang der gemessenen elektrischen Phase zuordnen. + + + + Voltage input 1 + Spannungseingang 1 + + + + Voltage input 2 + Spannungseingang 2 + + + + Voltage input 3 + Spannungseingang 3 + + + + Voltage input 4 + Spannungseingang 4 + + + + Calibration + Kalibrierung + + + + Voltage calibration factor + Spannungs-Kalibrierfaktor + + WiFiErrorsPage diff --git a/evcharger-app/i18n/qml_fi.ts b/evcharger-app/i18n/qml_fi.ts index 77bc13f..f4e45fb 100644 --- a/evcharger-app/i18n/qml_fi.ts +++ b/evcharger-app/i18n/qml_fi.ts @@ -1015,6 +1015,163 @@ Virtatasot + + CurrentSensorPage + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Live measurement + + + + + Current + + + + + A + + + + + Active power + + + + + W + + + + + Power factor + + + + + Voltage + + + + + V + + + + + Sensor settings + + + + + Electrical phase + + + + + Invert measurement direction + + + + + Current transformer + + + + + Enter the primary and secondary current printed on the current transformer. + + + + + Primary current + + + + + + %1 A + + + + + Secondary current + + + + + CurrentSensorsPage + + + Current sensors + + + + + Current sensor %1 + + + + + No measurement available + + + + + %1 A + + + + + %1 W + %1 W + + + + power factor %1 + + + + + Select a sensor to inspect its live data and configure its phase, direction and transformer ratio. + + + + + Calibration + + + + + Current calibration factor + + + + + Phase calibration factor + + + DailyTripPage @@ -1679,28 +1836,28 @@ -Switch firmware partition? + Switch firmware partition? Vaihda laiteohjelmisto-osiota? -Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. + Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. Vaihda laiteohjelmistoon %1 ja käynnistä laturi uudelleen? Yhteys laturiin katkeaa väliaikaisesti. -Cancel + Cancel Peruuta -Switch and reboot + Switch and reboot Vaihda ja käynnistä uudelleen -OTHER PARTITION + OTHER PARTITION TOINEN OSIO @@ -3054,12 +3211,325 @@ Laiteliitäntää ei ole asetettu! + + SensorCategoriesPage + + + Energy categories + + + + + %1 W + %1 W + + + + %1 Wh imported + + + + + hidden + + + + + No measurement available + + + + + Categories combine one or more current sensors into meaningful totals such as grid, home, car, solar or battery. + + + + + SensorCategoryPage + + + Current sensor %1 + + + + + None + Ei mitään + + + + Home + + + + + Grid + Sähköverkko + + + + Car + Auto + + + + Relay + + + + + Solar + + + + + Battery + + + + + Live data + + + + + Power + + + + + W + + + + + Current L1 + + + + + + + + A + + + + + Current L2 + + + + + Current L3 + + + + + Neutral current + + + + + Imported energy + + + + + + + + + + + + + + Wh + + + + + Exported energy + + + + + Imported energy L1 + + + + + Exported energy L1 + + + + + Imported energy L2 + + + + + Exported energy L2 + + + + + Imported energy L3 + + + + + Exported energy L3 + + + + + Imported neutral energy + + + + + Exported neutral energy + + + + + Category settings + + + + + Show category + + + + + Category name + + + + + Category icon + + + + + Temporary external power + + + + + Inactive + + + + + %1 W + %1 W + + + + External power is a live input and expires if it is not refreshed by the sender. + + + + + Sensor factors + + + + + Each factor multiplies that sensor's current and power before adding it to this category. Use 0 to exclude a sensor, 1 to add it unchanged, or -1 to subtract it. + + + + + + Reset category + + + + + Reset this custom category, including its name, icon, factors and energy counters. + + + + + Reset this category's visibility, sensor factors and energy counters to their defaults. + + + + + Reset category? + + + + + This resets the configuration and accumulated energy of %1. + + + SensorsConfigurationPage - Sensors Configuration - Anturien konfigurointi + Anturien konfigurointi + + + + Sensors and categories + + + + + Measurement + + + + + Measurement service enabled + + + + + Continuous raw measurement can reduce controller performance. + + + + + Measurement update rate + + + + + Voltage sensors + + + + + Live voltages, phase assignment and calibration + + + + + Current sensors + + + + + Live current, power, phase, direction and CT ratio + + + + + Energy categories + + + + + Power, energy counters and sensor factors + @@ -3153,122 +3623,122 @@ Maadoituksen tarkistus - + Sensors Configuration Anturien konfigurointi - + Sensors Anturit - + Categories Luokat - + Connection Yhteys - + Wi-Fi Wi-Fi - + Cellular Mobiiliverkko - + Hotspot Tukiasema - + Ethernet Ethernet - + Cloud Pilvi - + OCPP OCPP - + MQTT MQTT - + API Settings API-asetukset - + General Yleiset - + Name Nimi - + Switch Language Vaihda kieltä - + Notifications Ilmoitukset - + Date and time Päivämäärä ja aika - + LED LED - + Controller Ohjain - + Display settings Näyttöasetukset - + About Tietoja - + Firmware Laiteohjelmisto - + Hardware information Laitteiston tiedot - + Licenses Lisenssit @@ -3307,6 +3777,122 @@ ip. + + VoltageSensorsPage + + + Voltage sensors + + + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Voltage sensor %1 + + + + + Live measurements + + + + + Voltage L1 + + + + + + + + V + + + + + Voltage L2 + + + + + Voltage L3 + + + + + Neutral voltage + + + + + Frequency + + + + + Hz + + + + + Phase assignment + + + + + Assign each physical voltage input to the electrical phase it measures. + + + + + Voltage input 1 + + + + + Voltage input 2 + + + + + Voltage input 3 + + + + + Voltage input 4 + + + + + Calibration + + + + + Voltage calibration factor + + + WiFiErrorsPage diff --git a/evcharger-app/i18n/qml_nb.ts b/evcharger-app/i18n/qml_nb.ts index 75d8282..df4efc3 100644 --- a/evcharger-app/i18n/qml_nb.ts +++ b/evcharger-app/i18n/qml_nb.ts @@ -1015,6 +1015,163 @@ Strømnivåer + + CurrentSensorPage + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Live measurement + + + + + Current + + + + + A + + + + + Active power + + + + + W + + + + + Power factor + + + + + Voltage + + + + + V + + + + + Sensor settings + + + + + Electrical phase + + + + + Invert measurement direction + + + + + Current transformer + + + + + Enter the primary and secondary current printed on the current transformer. + + + + + Primary current + + + + + + %1 A + + + + + Secondary current + + + + + CurrentSensorsPage + + + Current sensors + + + + + Current sensor %1 + + + + + No measurement available + + + + + %1 A + + + + + %1 W + %1 W + + + + power factor %1 + + + + + Select a sensor to inspect its live data and configure its phase, direction and transformer ratio. + + + + + Calibration + + + + + Current calibration factor + + + + + Phase calibration factor + + + DailyTripPage @@ -1679,28 +1836,28 @@ -Switch firmware partition? + Switch firmware partition? Bytte fastvarepartisjon? -Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. + Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. Bytte til fastvare %1 og starte laderen på nytt? Tilkoblingen til laderen brytes midlertidig. -Cancel + Cancel Avbryt -Switch and reboot + Switch and reboot Bytt og start på nytt -OTHER PARTITION + OTHER PARTITION ANNEN PARTISJON @@ -3054,12 +3211,325 @@ Ingen enhetstilkobling satt! + + SensorCategoriesPage + + + Energy categories + + + + + %1 W + %1 W + + + + %1 Wh imported + + + + + hidden + + + + + No measurement available + + + + + Categories combine one or more current sensors into meaningful totals such as grid, home, car, solar or battery. + + + + + SensorCategoryPage + + + Current sensor %1 + + + + + None + Ingen + + + + Home + + + + + Grid + Strømnett + + + + Car + Bil + + + + Relay + + + + + Solar + + + + + Battery + + + + + Live data + + + + + Power + + + + + W + + + + + Current L1 + + + + + + + + A + + + + + Current L2 + + + + + Current L3 + + + + + Neutral current + + + + + Imported energy + + + + + + + + + + + + + + Wh + + + + + Exported energy + + + + + Imported energy L1 + + + + + Exported energy L1 + + + + + Imported energy L2 + + + + + Exported energy L2 + + + + + Imported energy L3 + + + + + Exported energy L3 + + + + + Imported neutral energy + + + + + Exported neutral energy + + + + + Category settings + + + + + Show category + + + + + Category name + + + + + Category icon + + + + + Temporary external power + + + + + Inactive + + + + + %1 W + %1 W + + + + External power is a live input and expires if it is not refreshed by the sender. + + + + + Sensor factors + + + + + Each factor multiplies that sensor's current and power before adding it to this category. Use 0 to exclude a sensor, 1 to add it unchanged, or -1 to subtract it. + + + + + + Reset category + + + + + Reset this custom category, including its name, icon, factors and energy counters. + + + + + Reset this category's visibility, sensor factors and energy counters to their defaults. + + + + + Reset category? + + + + + This resets the configuration and accumulated energy of %1. + + + SensorsConfigurationPage - Sensors Configuration - Sensorkonfigurasjon + Sensorkonfigurasjon + + + + Sensors and categories + + + + + Measurement + + + + + Measurement service enabled + + + + + Continuous raw measurement can reduce controller performance. + + + + + Measurement update rate + + + + + Voltage sensors + + + + + Live voltages, phase assignment and calibration + + + + + Current sensors + + + + + Live current, power, phase, direction and CT ratio + + + + + Energy categories + + + + + Power, energy counters and sensor factors + @@ -3153,122 +3623,122 @@ Jordingskontroll - + Sensors Configuration Sensorkonfigurasjon - + Sensors Sensorer - + Categories Kategorier - + Connection Tilkobling - + Wi-Fi Wi-Fi - + Cellular Mobilnett - + Hotspot Hotspot - + Ethernet Ethernet - + Cloud Sky - + OCPP OCPP - + MQTT MQTT - + API Settings API-innstillinger - + General Generelt - + Name Navn - + Switch Language Bytt språk - + Notifications Varsler - + Date and time Dato og tid - + LED LED - + Controller Kontroller - + Display settings Skjerminnstillinger - + About Om - + Firmware Fastvare - + Hardware information Maskinvareinformasjon - + Licenses Lisenser @@ -3307,6 +3777,122 @@ PM + + VoltageSensorsPage + + + Voltage sensors + + + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Voltage sensor %1 + + + + + Live measurements + + + + + Voltage L1 + + + + + + + + V + + + + + Voltage L2 + + + + + Voltage L3 + + + + + Neutral voltage + + + + + Frequency + + + + + Hz + + + + + Phase assignment + + + + + Assign each physical voltage input to the electrical phase it measures. + + + + + Voltage input 1 + + + + + Voltage input 2 + + + + + Voltage input 3 + + + + + Voltage input 4 + + + + + Calibration + + + + + Voltage calibration factor + + + WiFiErrorsPage diff --git a/evcharger-app/i18n/qml_sv.ts b/evcharger-app/i18n/qml_sv.ts index d1d9420..9951c4f 100644 --- a/evcharger-app/i18n/qml_sv.ts +++ b/evcharger-app/i18n/qml_sv.ts @@ -1015,6 +1015,163 @@ Strömnivåer + + CurrentSensorPage + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Live measurement + + + + + Current + + + + + A + + + + + Active power + + + + + W + + + + + Power factor + + + + + Voltage + + + + + V + + + + + Sensor settings + + + + + Electrical phase + + + + + Invert measurement direction + + + + + Current transformer + + + + + Enter the primary and secondary current printed on the current transformer. + + + + + Primary current + + + + + + %1 A + + + + + Secondary current + + + + + CurrentSensorsPage + + + Current sensors + + + + + Current sensor %1 + + + + + No measurement available + + + + + %1 A + + + + + %1 W + %1 W + + + + power factor %1 + + + + + Select a sensor to inspect its live data and configure its phase, direction and transformer ratio. + + + + + Calibration + + + + + Current calibration factor + + + + + Phase calibration factor + + + DailyTripPage @@ -1679,28 +1836,28 @@ -Switch firmware partition? + Switch firmware partition? Byta firmwarepartition? -Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. + Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. Byta till firmware %1 och starta om laddaren? Anslutningen till laddaren bryts tillfälligt. -Cancel + Cancel Avbryt -Switch and reboot + Switch and reboot Byt och starta om -OTHER PARTITION + OTHER PARTITION ANNAN PARTITION @@ -3054,12 +3211,325 @@ Ingen enhetsanslutning inställd! + + SensorCategoriesPage + + + Energy categories + + + + + %1 W + %1 W + + + + %1 Wh imported + + + + + hidden + + + + + No measurement available + + + + + Categories combine one or more current sensors into meaningful totals such as grid, home, car, solar or battery. + + + + + SensorCategoryPage + + + Current sensor %1 + + + + + None + Ingen + + + + Home + + + + + Grid + Elnät + + + + Car + Bil + + + + Relay + + + + + Solar + + + + + Battery + + + + + Live data + + + + + Power + + + + + W + + + + + Current L1 + + + + + + + + A + + + + + Current L2 + + + + + Current L3 + + + + + Neutral current + + + + + Imported energy + + + + + + + + + + + + + + Wh + + + + + Exported energy + + + + + Imported energy L1 + + + + + Exported energy L1 + + + + + Imported energy L2 + + + + + Exported energy L2 + + + + + Imported energy L3 + + + + + Exported energy L3 + + + + + Imported neutral energy + + + + + Exported neutral energy + + + + + Category settings + + + + + Show category + + + + + Category name + + + + + Category icon + + + + + Temporary external power + + + + + Inactive + + + + + %1 W + %1 W + + + + External power is a live input and expires if it is not refreshed by the sender. + + + + + Sensor factors + + + + + Each factor multiplies that sensor's current and power before adding it to this category. Use 0 to exclude a sensor, 1 to add it unchanged, or -1 to subtract it. + + + + + + Reset category + + + + + Reset this custom category, including its name, icon, factors and energy counters. + + + + + Reset this category's visibility, sensor factors and energy counters to their defaults. + + + + + Reset category? + + + + + This resets the configuration and accumulated energy of %1. + + + SensorsConfigurationPage - Sensors Configuration - Sensorkonfiguration + Sensorkonfiguration + + + + Sensors and categories + + + + + Measurement + + + + + Measurement service enabled + + + + + Continuous raw measurement can reduce controller performance. + + + + + Measurement update rate + + + + + Voltage sensors + + + + + Live voltages, phase assignment and calibration + + + + + Current sensors + + + + + Live current, power, phase, direction and CT ratio + + + + + Energy categories + + + + + Power, energy counters and sensor factors + @@ -3153,122 +3623,122 @@ Jordningskontroll - + Sensors Configuration Sensorkonfiguration - + Sensors Sensorer - + Categories Kategorier - + Connection Anslutning - + Wi-Fi Wi-Fi - + Cellular Mobilnät - + Hotspot Hotspot - + Ethernet Ethernet - + Cloud Moln - + OCPP OCPP - + MQTT MQTT - + API Settings API-inställningar - + General Allmänt - + Name Namn - + Switch Language Byt språk - + Notifications Aviseringar - + Date and time Datum och tid - + LED LED - + Controller Styrenhet - + Display settings Displayinställningar - + About Om appen - + Firmware Firmware - + Hardware information Hårdvaruinformation - + Licenses Licenser @@ -3307,6 +3777,122 @@ em + + VoltageSensorsPage + + + Voltage sensors + + + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Voltage sensor %1 + + + + + Live measurements + + + + + Voltage L1 + + + + + + + + V + + + + + Voltage L2 + + + + + Voltage L3 + + + + + Neutral voltage + + + + + Frequency + + + + + Hz + + + + + Phase assignment + + + + + Assign each physical voltage input to the electrical phase it measures. + + + + + Voltage input 1 + + + + + Voltage input 2 + + + + + Voltage input 3 + + + + + Voltage input 4 + + + + + Calibration + + + + + Voltage calibration factor + + + WiFiErrorsPage diff --git a/evcharger-app/i18n/qml_tr.ts b/evcharger-app/i18n/qml_tr.ts index f24db77..53f23f2 100644 --- a/evcharger-app/i18n/qml_tr.ts +++ b/evcharger-app/i18n/qml_tr.ts @@ -1015,6 +1015,163 @@ Akım seviyeleri + + CurrentSensorPage + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Live measurement + + + + + Current + + + + + A + + + + + Active power + + + + + W + + + + + Power factor + + + + + Voltage + + + + + V + + + + + Sensor settings + + + + + Electrical phase + + + + + Invert measurement direction + + + + + Current transformer + + + + + Enter the primary and secondary current printed on the current transformer. + + + + + Primary current + + + + + + %1 A + + + + + Secondary current + + + + + CurrentSensorsPage + + + Current sensors + + + + + Current sensor %1 + + + + + No measurement available + + + + + %1 A + + + + + %1 W + %1 W + + + + power factor %1 + + + + + Select a sensor to inspect its live data and configure its phase, direction and transformer ratio. + + + + + Calibration + + + + + Current calibration factor + + + + + Phase calibration factor + + + DailyTripPage @@ -1679,28 +1836,28 @@ -Switch firmware partition? + Switch firmware partition? Firmware bölümü değiştirilsin mi? -Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. + Switch to firmware %1 and reboot the charger? The charger will temporarily disconnect. %1 firmware sürümüne geçilip şarj cihazı yeniden başlatılsın mı? Şarj cihazıyla bağlantı geçici olarak kesilecektir. -Cancel + Cancel İptal -Switch and reboot + Switch and reboot Değiştir ve yeniden başlat -OTHER PARTITION + OTHER PARTITION DİĞER BÖLÜM @@ -3054,12 +3211,325 @@ Cihaz bağlantısı ayarlanmadı! + + SensorCategoriesPage + + + Energy categories + + + + + %1 W + %1 W + + + + %1 Wh imported + + + + + hidden + + + + + No measurement available + + + + + Categories combine one or more current sensors into meaningful totals such as grid, home, car, solar or battery. + + + + + SensorCategoryPage + + + Current sensor %1 + + + + + None + Hiçbiri + + + + Home + + + + + Grid + Şebeke + + + + Car + Araba + + + + Relay + + + + + Solar + + + + + Battery + + + + + Live data + + + + + Power + + + + + W + + + + + Current L1 + + + + + + + + A + + + + + Current L2 + + + + + Current L3 + + + + + Neutral current + + + + + Imported energy + + + + + + + + + + + + + + Wh + + + + + Exported energy + + + + + Imported energy L1 + + + + + Exported energy L1 + + + + + Imported energy L2 + + + + + Exported energy L2 + + + + + Imported energy L3 + + + + + Exported energy L3 + + + + + Imported neutral energy + + + + + Exported neutral energy + + + + + Category settings + + + + + Show category + + + + + Category name + + + + + Category icon + + + + + Temporary external power + + + + + Inactive + + + + + %1 W + %1 W + + + + External power is a live input and expires if it is not refreshed by the sender. + + + + + Sensor factors + + + + + Each factor multiplies that sensor's current and power before adding it to this category. Use 0 to exclude a sensor, 1 to add it unchanged, or -1 to subtract it. + + + + + + Reset category + + + + + Reset this custom category, including its name, icon, factors and energy counters. + + + + + Reset this category's visibility, sensor factors and energy counters to their defaults. + + + + + Reset category? + + + + + This resets the configuration and accumulated energy of %1. + + + SensorsConfigurationPage - Sensors Configuration - Sensör Yapılandırması + Sensör Yapılandırması + + + + Sensors and categories + + + + + Measurement + + + + + Measurement service enabled + + + + + Continuous raw measurement can reduce controller performance. + + + + + Measurement update rate + + + + + Voltage sensors + + + + + Live voltages, phase assignment and calibration + + + + + Current sensors + + + + + Live current, power, phase, direction and CT ratio + + + + + Energy categories + + + + + Power, energy counters and sensor factors + @@ -3153,122 +3623,122 @@ Topraklama kontrolü - + Sensors Configuration Sensör Yapılandırması - + Sensors Sensörler - + Categories Kategoriler - + Connection Bağlantı - + Wi-Fi Wi-Fi - + Cellular Mobil ağ - + Hotspot Erişim noktası - + Ethernet Ethernet - + Cloud Bulut - + OCPP OCPP - + MQTT MQTT - + API Settings API Ayarları - + General Genel - + Name İsim - + Switch Language Dili Değiştir - + Notifications Bildirimler - + Date and time Tarih ve saat - + LED LED - + Controller Kontrol cihazı - + Display settings Ekran ayarları - + About Hakkında - + Firmware Firmware - + Hardware information Donanım bilgileri - + Licenses Lisanslar @@ -3307,6 +3777,122 @@ ÖS + + VoltageSensorsPage + + + Voltage sensors + + + + + L1 + + + + + L2 + + + + + L3 + + + + + Neutral + + + + + Voltage sensor %1 + + + + + Live measurements + + + + + Voltage L1 + + + + + + + + V + + + + + Voltage L2 + + + + + Voltage L3 + + + + + Neutral voltage + + + + + Frequency + + + + + Hz + + + + + Phase assignment + + + + + Assign each physical voltage input to the electrical phase it measures. + + + + + Voltage input 1 + + + + + Voltage input 2 + + + + + Voltage input 3 + + + + + Voltage input 4 + + + + + Calibration + + + + + Voltage calibration factor + + + WiFiErrorsPage diff --git a/evcharger-app/qml/CurrentSensorPage.qml b/evcharger-app/qml/CurrentSensorPage.qml new file mode 100644 index 0000000..b80ef1d --- /dev/null +++ b/evcharger-app/qml/CurrentSensorPage.qml @@ -0,0 +1,130 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import EVChargerApp + +NavigationPage { + id: page + + required property int sensorIndex + property string fallbackSensorName + title: sensorName() + + readonly property var phaseChoices: [ + { value: 0, text: qsTr("L1") }, + { value: 1, text: qsTr("L2") }, + { value: 2, text: qsTr("L3") }, + { value: 3, text: qsTr("Neutral") } + ] + readonly property var reading: valueAt(sensorValues.value, sensorIndex) + + function valueAt(array, index) { + return array !== null && array !== undefined && array.length !== undefined + && index >= 0 && index < array.length ? array[index] : undefined + } + + function sensorName() { + const name = valueAt(sensorNames.value, sensorIndex) + return name === null || name === undefined ? fallbackSensorName : name + } + + ApiKeyValueHelper { + id: sensorNames + deviceConnection: theDeviceConnection + apiKey: "isn" + } + + ApiKeyValueHelper { + id: sensorValues + deviceConnection: theDeviceConnection + apiKey: "isv" + } + + ApiKeyValueHelper { + id: ctRatios + deviceConnection: theDeviceConnection + apiKey: "ctr" + } + + SettingsCard { + SettingsSectionLabel { text: qsTr("Live measurement") } + MeasurementRow { label: qsTr("Current"); value: page.reading ? page.reading.i : undefined; unit: qsTr("A") } + MeasurementRow { label: qsTr("Active power"); value: page.reading ? page.reading.p : undefined; unit: qsTr("W") } + MeasurementRow { label: qsTr("Power factor"); value: page.reading ? page.reading.f : undefined; decimals: 3 } + MeasurementRow { label: qsTr("Voltage"); value: page.reading ? page.reading.u : undefined; unit: qsTr("V") } + } + + Text { + Layout.fillWidth: true + text: qsTr("Sensor settings") + font.bold: true + } + + IndexedEditValueItem { + text: qsTr("Electrical phase") + apiKey: "ips" + arrayIndex: page.sensorIndex + valueText: { + for (let i = 0; i < page.phaseChoices.length; ++i) { + if (page.phaseChoices[i].value === value) + return page.phaseChoices[i].text + } + return "—" + } + editableItem: ComboBox { + id: phaseCombo + property var value + model: page.phaseChoices + textRole: "text" + valueRole: "value" + onValueChanged: { + const selected = indexOfValue(value) + if (selected >= 0) + currentIndex = selected + } + onActivated: value = currentValue + } + } + + IndexedOnOffSwitch { + text: qsTr("Invert measurement direction") + apiKey: "iim" + arrayIndex: page.sensorIndex + } + + Text { + Layout.fillWidth: true + Layout.topMargin: 8 + text: qsTr("Current transformer") + font.bold: true + visible: ctRatios.exists + } + + Text { + Layout.fillWidth: true + text: qsTr("Enter the primary and secondary current printed on the current transformer.") + color: "gray" + wrapMode: Text.Wrap + visible: ctRatios.exists + } + + IndexedEditValueItem { + text: qsTr("Primary current") + apiKey: "ctr" + arrayIndex: page.sensorIndex + innerArrayIndex: 0 + sendCompleteArray: true + valueText: value === null || value === undefined ? "—" : qsTr("%1 A").arg(Qt.locale().toString(Number(value))) + editableItem: SpinBox { editable: true; from: 1; to: 100000; stepSize: 1 } + } + + IndexedEditValueItem { + text: qsTr("Secondary current") + apiKey: "ctr" + arrayIndex: page.sensorIndex + innerArrayIndex: 1 + sendCompleteArray: true + valueText: value === null || value === undefined ? "—" : qsTr("%1 A").arg(Qt.locale().toString(Number(value))) + editableItem: SpinBox { editable: true; from: 1; to: 100000; stepSize: 1 } + } +} diff --git a/evcharger-app/qml/CurrentSensorsPage.qml b/evcharger-app/qml/CurrentSensorsPage.qml new file mode 100644 index 0000000..d9f843a --- /dev/null +++ b/evcharger-app/qml/CurrentSensorsPage.qml @@ -0,0 +1,98 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import EVChargerApp + +NavigationPage { + id: page + title: qsTr("Current sensors") + + readonly property int sensorCount: Math.max(arrayLength(sensorNames.value), arrayLength(sensorValues.value)) + + function arrayLength(value) { + return value !== null && value !== undefined && value.length !== undefined ? value.length : 0 + } + + function sensorName(index) { + if (arrayLength(sensorNames.value) > index) + return sensorNames.value[index] + return qsTr("Current sensor %1").arg(index + 1) + } + + function sensorValue(index) { + return arrayLength(sensorValues.value) > index ? sensorValues.value[index] : null + } + + function formatReading(index) { + const reading = sensorValue(index) + if (!reading) + return qsTr("No measurement available") + + const values = [] + if (reading.i !== null && reading.i !== undefined) + values.push(qsTr("%1 A").arg(Qt.locale().toString(Number(reading.i), "f", 2))) + if (reading.p !== null && reading.p !== undefined) + values.push(qsTr("%1 W").arg(Qt.locale().toString(Number(reading.p), "f", 2))) + if (reading.f !== null && reading.f !== undefined) + values.push(qsTr("power factor %1").arg(Qt.locale().toString(Number(reading.f), "f", 3))) + return values.join(" • ") + } + + ApiKeyValueHelper { + id: sensorNames + deviceConnection: theDeviceConnection + apiKey: "isn" + } + + ApiKeyValueHelper { + id: sensorValues + deviceConnection: theDeviceConnection + apiKey: "isv" + } + + Text { + Layout.fillWidth: true + text: qsTr("Select a sensor to inspect its live data and configure its phase, direction and transformer ratio.") + color: "gray" + wrapMode: Text.Wrap + } + + Repeater { + model: page.sensorCount + + NavigationItem { + required property int index + title: page.sensorName(index) + description: page.formatReading(index) + iconName: "speed" + componentProperties: ({ + sensorIndex: index, + fallbackSensorName: page.sensorName(index) + }) + component: Component { CurrentSensorPage {} } + } + } + + Text { + Layout.fillWidth: true + Layout.topMargin: 8 + text: qsTr("Calibration") + font.bold: true + } + + EditValueItem { + text: qsTr("Current calibration factor") + apiKey: "mic" + visible: exists + valueText: value === null || value === undefined ? "—" : Qt.locale().toString(Number(value), "f", 5) + editableItem: DoubleSpinBox { decimals: 5; from: 0; to: 1000; stepSize: 0.00001 } + } + + EditValueItem { + text: qsTr("Phase calibration factor") + apiKey: "mpc" + visible: exists + valueText: value === null || value === undefined ? "—" : Qt.locale().toString(Number(value), "f", 5) + editableItem: DoubleSpinBox { decimals: 5; from: -1000; to: 1000; stepSize: 0.00001 } + } +} diff --git a/evcharger-app/qml/DoubleSpinBox.qml b/evcharger-app/qml/DoubleSpinBox.qml index 11c711e..a352fe2 100644 --- a/evcharger-app/qml/DoubleSpinBox.qml +++ b/evcharger-app/qml/DoubleSpinBox.qml @@ -4,6 +4,9 @@ import QtQuick.Controls Item { id: doubleSpinBox + implicitWidth: spinbox.implicitWidth + implicitHeight: spinbox.implicitHeight + property int decimals: 2 property real value: 0.0 property real from: 0.0 @@ -15,19 +18,25 @@ Item { id: spinbox - stepSize: doubleSpinBox.stepSize * factor - value: doubleSpinBox.value * factor + stepSize: Math.max(1, Math.round(doubleSpinBox.stepSize * factor)) + value: Math.round(doubleSpinBox.value * factor) onValueChanged: doubleSpinBox.value = spinbox.value / factor - to : doubleSpinBox.to * factor - from : doubleSpinBox.from * factor + to: Math.round(doubleSpinBox.to * factor) + from: Math.round(doubleSpinBox.from * factor) validator: DoubleValidator { - bottom: Math.min(spinbox.from, spinbox.to) * spinbox.factor - top: Math.max(spinbox.from, spinbox.to) * spinbox.factor + bottom: Math.min(doubleSpinBox.from, doubleSpinBox.to) + top: Math.max(doubleSpinBox.from, doubleSpinBox.to) + decimals: doubleSpinBox.decimals + locale: spinbox.locale.name } textFromValue: function(value, locale) { - return parseFloat(value * 1.0 / factor).toFixed(decimals) + return locale.toString(value / factor, "f", doubleSpinBox.decimals) + } + + valueFromText: function(text, locale) { + return Math.round(locale.toDouble(text) * factor) } } } diff --git a/evcharger-app/qml/IndexedEditValueItem.qml b/evcharger-app/qml/IndexedEditValueItem.qml new file mode 100644 index 0000000..30c5b6e --- /dev/null +++ b/evcharger-app/qml/IndexedEditValueItem.qml @@ -0,0 +1,133 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import EVChargerApp + +WhiteItemDelegate { + id: itemDelegate + + required property string apiKey + required property int arrayIndex + property int innerArrayIndex: -1 + property bool sendCompleteArray: false + property alias editableItem: dialog.contentItem + property var editValueTransform: null + property string valueText: value === null || value === undefined ? "—" : String(value) + readonly property alias exists: apiValue.exists + readonly property var value: valueAt(apiValue.value) + + Layout.fillWidth: true + visible: exists + + function valueAt(source) { + if (source === null || source === undefined || source.length === undefined + || arrayIndex < 0 || arrayIndex >= source.length) + return undefined + + const outerValue = source[arrayIndex] + if (innerArrayIndex < 0) + return outerValue + if (outerValue === null || outerValue === undefined || outerValue.length === undefined + || innerArrayIndex >= outerValue.length) + return undefined + return outerValue[innerArrayIndex] + } + + function sparseValue(newValue) { + if (sendCompleteArray) { + const source = apiValue.value + const complete = [] + if (source !== null && source !== undefined && source.length !== undefined) { + for (let i = 0; i < source.length; ++i) { + const sourceEntry = source[i] + if (sourceEntry !== null && sourceEntry !== undefined && sourceEntry.length !== undefined) + complete.push(Array.prototype.slice.call(sourceEntry)) + else + complete.push(sourceEntry) + } + } + + if (innerArrayIndex < 0) { + complete[arrayIndex] = newValue + } else { + while (complete.length <= arrayIndex) + complete.push(null) + let inner = complete[arrayIndex] + if (inner === null || inner === undefined || inner.length === undefined) + inner = [] + while (inner.length <= innerArrayIndex) + inner.push(null) + inner[innerArrayIndex] = newValue + complete[arrayIndex] = inner + } + return complete + } + + const result = [] + for (let i = 0; i < arrayIndex; ++i) + result.push(null) + + if (innerArrayIndex < 0) { + result.push(newValue) + } else { + const inner = [] + for (let i = 0; i < innerArrayIndex; ++i) + inner.push(null) + inner.push(newValue) + result.push(inner) + } + return result + } + + ApiKeyValueHelper { + id: apiValue + deviceConnection: theDeviceConnection + apiKey: itemDelegate.apiKey + } + + SetValueHelper { + id: valueSetter + deviceConnection: theDeviceConnection + apiKey: itemDelegate.apiKey + } + + contentItem: RowLayout { + SettingsLabel { + Layout.fillWidth: true + text: itemDelegate.text + } + + BusyIndicator { + visible: valueSetter.pending + } + + RequestStatusText { + request: valueSetter + } + + Text { + text: itemDelegate.valueText + } + + MaterialIcon { + iconName: "chevron_right" + font.pixelSize: 22 + } + } + + onClicked: { + editableItem.value = editValueTransform === null + ? value : editValueTransform(value) + dialog.open() + } + + CenteredDialog { + id: dialog + title: itemDelegate.text + standardButtons: Dialog.Ok | Dialog.Cancel + focus: true + modal: true + + onAccepted: valueSetter.setValue(itemDelegate.sparseValue(editableItem.value)) + } +} diff --git a/evcharger-app/qml/IndexedOnOffSwitch.qml b/evcharger-app/qml/IndexedOnOffSwitch.qml new file mode 100644 index 0000000..4e61579 --- /dev/null +++ b/evcharger-app/qml/IndexedOnOffSwitch.qml @@ -0,0 +1,51 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import EVChargerApp + +WhiteCheckDelegate { + id: control + + required property string apiKey + required property int arrayIndex + readonly property alias exists: apiValue.exists + readonly property var value: apiValue.value !== null && apiValue.value !== undefined + && apiValue.value.length !== undefined + && arrayIndex >= 0 && arrayIndex < apiValue.value.length + ? apiValue.value[arrayIndex] : undefined + + Layout.fillWidth: true + visible: exists + checked: value === true + + ApiKeyValueHelper { + id: apiValue + deviceConnection: theDeviceConnection + apiKey: control.apiKey + } + + SetValueHelper { + id: valueSetter + deviceConnection: theDeviceConnection + apiKey: control.apiKey + onResponseChanged: control.checked = Qt.binding(function() { + return control.value === true + }) + } + + onClicked: { + const update = [] + for (let i = 0; i < arrayIndex; ++i) + update.push(null) + update.push(checked) + valueSetter.setValue(update) + } + + BusyIndicator { + visible: valueSetter.pending + } + + RequestStatusText { + request: valueSetter + } +} diff --git a/evcharger-app/qml/MeasurementRow.qml b/evcharger-app/qml/MeasurementRow.qml new file mode 100644 index 0000000..3c73717 --- /dev/null +++ b/evcharger-app/qml/MeasurementRow.qml @@ -0,0 +1,36 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RowLayout { + id: row + + required property string label + required property var value + property string unit + property int decimals: 2 + property var valueFormatter: null + readonly property bool available: value !== null && value !== undefined + + Layout.fillWidth: true + visible: available + + SettingsLabel { + Layout.fillWidth: true + text: row.label + } + + Text { + text: { + if (!row.available) + return "—" + if (row.valueFormatter !== null) + return row.valueFormatter(row.value) + const number = Number(row.value) + const formatted = isNaN(number) + ? String(row.value) + : Qt.locale().toString(number, "f", row.decimals) + return row.unit === "" ? formatted : formatted + " " + row.unit + } + } +} diff --git a/evcharger-app/qml/NavigationItem.qml b/evcharger-app/qml/NavigationItem.qml index 69817eb..a7130ef 100644 --- a/evcharger-app/qml/NavigationItem.qml +++ b/evcharger-app/qml/NavigationItem.qml @@ -10,13 +10,14 @@ WhiteItemDelegate { property alias title: titleText.text property string description property Component component + property var componentProperties: ({}) Layout.fillWidth: true implicitWidth: row.implicitWidth implicitHeight: Math.max(row.implicitHeight, 50) - onClicked: stackView.push(navigationItem.component) + onClicked: stackView.push(navigationItem.component, navigationItem.componentProperties) RowLayout { id: row diff --git a/evcharger-app/qml/SensorCategoriesPage.qml b/evcharger-app/qml/SensorCategoriesPage.qml new file mode 100644 index 0000000..ddce3d5 --- /dev/null +++ b/evcharger-app/qml/SensorCategoriesPage.qml @@ -0,0 +1,83 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import EVChargerApp + +NavigationPage { + id: page + title: qsTr("Energy categories") + + function arrayLength(value) { + return value !== null && value !== undefined && value.length !== undefined ? value.length : 0 + } + + function valueAt(array, index) { + return arrayLength(array) > index ? array[index] : undefined + } + + function categoryDescription(index) { + const pieces = [] + const power = valueAt(categoryPowers.value, index) + const energies = valueAt(categoryEnergies.value, index) + if (power !== null && power !== undefined) + pieces.push(qsTr("%1 W").arg(Qt.locale().toString(Number(power), "f", 2))) + if (energies && energies.length > 0) + pieces.push(qsTr("%1 Wh imported").arg(Qt.locale().toString(Number(energies[0]), "f", 2))) + if (valueAt(categoryShown.value, index) === false) + pieces.push(qsTr("hidden")) + return pieces.length > 0 ? pieces.join(" • ") : qsTr("No measurement available") + } + + function categoryIcon(index) { + const icons = ["home", "electrical_services", "directions_car", "toggle_on", "solar_power", "battery_charging_full"] + return index < icons.length ? icons[index] : "category" + } + + ApiKeyValueHelper { + id: categoryNames + deviceConnection: theDeviceConnection + apiKey: "ccn" + } + + ApiKeyValueHelper { + id: categoryPowers + deviceConnection: theDeviceConnection + apiKey: "ccp" + } + + ApiKeyValueHelper { + id: categoryEnergies + deviceConnection: theDeviceConnection + apiKey: "cec" + } + + ApiKeyValueHelper { + id: categoryShown + deviceConnection: theDeviceConnection + apiKey: "ccs" + } + + Text { + Layout.fillWidth: true + text: qsTr("Categories combine one or more current sensors into meaningful totals such as grid, home, car, solar or battery.") + color: "gray" + wrapMode: Text.Wrap + } + + Repeater { + model: categoryNames.value + + NavigationItem { + required property int index + required property var modelData + title: String(modelData) + description: page.categoryDescription(index) + iconName: page.categoryIcon(index) + componentProperties: ({ + categoryIndex: index, + fallbackCategoryName: String(modelData) + }) + component: Component { SensorCategoryPage {} } + } + } +} diff --git a/evcharger-app/qml/SensorCategoryPage.qml b/evcharger-app/qml/SensorCategoryPage.qml new file mode 100644 index 0000000..ae3a484 --- /dev/null +++ b/evcharger-app/qml/SensorCategoryPage.qml @@ -0,0 +1,235 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import EVChargerApp + +NavigationPage { + id: page + + required property int categoryIndex + property string fallbackCategoryName + title: categoryName() + + readonly property int categoryCount: arrayLength(categoryNames.value) + readonly property int customCategoryCount: arrayLength(customCategoryIcons.value) + readonly property int builtInCategoryCount: Math.max(0, categoryCount - customCategoryCount) + readonly property int customCategoryIndex: categoryIndex - builtInCategoryCount + readonly property bool isCustomCategory: customCategoryIndex >= 0 + readonly property int currentSensorCount: Math.max(arrayLength(currentSensorNames.value), arrayLength(categoryFactors.value)) + readonly property var categoryPower: valueAt(categoryPowers.value, categoryIndex) + readonly property var categoryCurrents: valueAt(phaseCurrents.value, categoryIndex) + readonly property var categoryEnergy: valueAt(categoryEnergies.value, categoryIndex) + + function arrayLength(value) { + return value !== null && value !== undefined && value.length !== undefined ? value.length : 0 + } + + function valueAt(array, index) { + return arrayLength(array) > index ? array[index] : undefined + } + + function categoryName() { + const name = valueAt(categoryNames.value, categoryIndex) + return name === null || name === undefined ? fallbackCategoryName : String(name) + } + + function energyAt(index) { + return valueAt(categoryEnergy, index) + } + + function currentAt(index) { + return valueAt(categoryCurrents, index) + } + + function currentSensorName(index) { + const name = valueAt(currentSensorNames.value, index) + return name === null || name === undefined + ? qsTr("Current sensor %1").arg(index + 1) : String(name) + } + + readonly property var iconChoices: [ + { value: 0, text: qsTr("None") }, + { value: 1, text: qsTr("Home") }, + { value: 2, text: qsTr("Grid") }, + { value: 3, text: qsTr("Car") }, + { value: 4, text: qsTr("Relay") }, + { value: 5, text: qsTr("Solar") }, + { value: 6, text: qsTr("Battery") } + ] + + ApiKeyValueHelper { id: categoryNames; deviceConnection: theDeviceConnection; apiKey: "ccn" } + ApiKeyValueHelper { id: categoryPowers; deviceConnection: theDeviceConnection; apiKey: "ccp" } + ApiKeyValueHelper { id: phaseCurrents; deviceConnection: theDeviceConnection; apiKey: "cpc" } + ApiKeyValueHelper { id: categoryEnergies; deviceConnection: theDeviceConnection; apiKey: "cec" } + ApiKeyValueHelper { id: currentSensorNames; deviceConnection: theDeviceConnection; apiKey: "isn" } + ApiKeyValueHelper { id: categoryFactors; deviceConnection: theDeviceConnection; apiKey: "ccf" } + ApiKeyValueHelper { id: customCategoryIcons; deviceConnection: theDeviceConnection; apiKey: "ccci" } + ApiKeyValueHelper { id: externalPowers; deviceConnection: theDeviceConnection; apiKey: "ecp" } + + SendMessageHelper { + id: resetCategory + deviceConnection: theDeviceConnection + } + + SettingsCard { + SettingsSectionLabel { text: qsTr("Live data") } + MeasurementRow { label: qsTr("Power"); value: page.categoryPower; unit: qsTr("W") } + MeasurementRow { label: qsTr("Current L1"); value: page.currentAt(0); unit: qsTr("A") } + MeasurementRow { label: qsTr("Current L2"); value: page.currentAt(1); unit: qsTr("A") } + MeasurementRow { label: qsTr("Current L3"); value: page.currentAt(2); unit: qsTr("A") } + MeasurementRow { label: qsTr("Neutral current"); value: page.currentAt(3); unit: qsTr("A") } + MeasurementRow { label: qsTr("Imported energy"); value: page.energyAt(0); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Exported energy"); value: page.energyAt(1); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Imported energy L1"); value: page.energyAt(2); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Exported energy L1"); value: page.energyAt(3); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Imported energy L2"); value: page.energyAt(4); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Exported energy L2"); value: page.energyAt(5); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Imported energy L3"); value: page.energyAt(6); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Exported energy L3"); value: page.energyAt(7); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Imported neutral energy"); value: page.energyAt(8); unit: qsTr("Wh") } + MeasurementRow { label: qsTr("Exported neutral energy"); value: page.energyAt(9); unit: qsTr("Wh") } + } + + Text { + Layout.fillWidth: true + text: qsTr("Category settings") + font.bold: true + } + + IndexedOnOffSwitch { + text: qsTr("Show category") + apiKey: "ccs" + arrayIndex: page.categoryIndex + } + + IndexedEditValueItem { + text: qsTr("Category name") + apiKey: "ccn" + arrayIndex: page.categoryIndex + visible: exists && page.isCustomCategory + editableItem: TextField { + id: categoryNameField + property alias value: categoryNameField.text + } + } + + IndexedEditValueItem { + text: qsTr("Category icon") + apiKey: "ccci" + arrayIndex: page.customCategoryIndex + visible: exists && page.isCustomCategory + valueText: { + for (let i = 0; i < page.iconChoices.length; ++i) { + if (page.iconChoices[i].value === value) + return page.iconChoices[i].text + } + return "—" + } + editableItem: ComboBox { + id: iconCombo + property var value + model: page.iconChoices + textRole: "text" + valueRole: "value" + onValueChanged: { + const selected = indexOfValue(value) + if (selected >= 0) + currentIndex = selected + } + onActivated: value = currentValue + } + } + + IndexedEditValueItem { + text: qsTr("Temporary external power") + apiKey: "ecp" + arrayIndex: page.categoryIndex + editValueTransform: function(value) { return value === null || value === undefined ? 0 : value } + valueText: value === null || value === undefined ? qsTr("Inactive") + : qsTr("%1 W").arg(Qt.locale().toString(Number(value), "f", 3)) + editableItem: DoubleSpinBox { decimals: 3; from: -1000000; to: 1000000; stepSize: 1 } + } + + Text { + Layout.fillWidth: true + visible: externalPowers.exists + text: qsTr("External power is a live input and expires if it is not refreshed by the sender.") + color: "gray" + wrapMode: Text.Wrap + } + + Text { + Layout.fillWidth: true + Layout.topMargin: 8 + text: qsTr("Sensor factors") + font.bold: true + visible: categoryFactors.exists + } + + Text { + Layout.fillWidth: true + text: qsTr("Each factor multiplies that sensor's current and power before adding it to this category. Use 0 to exclude a sensor, 1 to add it unchanged, or -1 to subtract it.") + color: "gray" + wrapMode: Text.Wrap + visible: categoryFactors.exists + } + + Repeater { + model: page.currentSensorCount + + IndexedEditValueItem { + required property int index + text: page.currentSensorName(index) + apiKey: "ccf" + arrayIndex: index + innerArrayIndex: page.categoryIndex + valueText: value === null || value === undefined ? "—" : Qt.locale().toString(Number(value), "f", 3) + editableItem: DoubleSpinBox { decimals: 3; from: -1000; to: 1000; stepSize: 0.001 } + } + } + + SettingsCard { + SettingsSectionLabel { text: qsTr("Reset category") } + + Text { + Layout.fillWidth: true + text: page.isCustomCategory + ? qsTr("Reset this custom category, including its name, icon, factors and energy counters.") + : qsTr("Reset this category's visibility, sensor factors and energy counters to their defaults.") + color: "gray" + wrapMode: Text.Wrap + } + + RowLayout { + Layout.fillWidth: true + + Button { + text: qsTr("Reset category") + enabled: !resetCategory.pending + onClicked: resetConfirmation.open() + } + + BusyIndicator { visible: resetCategory.pending } + RequestStatusText { request: resetCategory } + } + } + + CenteredDialog { + id: resetConfirmation + title: qsTr("Reset category?") + standardButtons: Dialog.Yes | Dialog.No + modal: true + + Label { + width: parent.width + text: qsTr("This resets the configuration and accumulated energy of %1.").arg(page.categoryName()) + wrapMode: Text.Wrap + } + + onAccepted: resetCategory.sendMessage({ + type: "setValue", + key: "ccc", + value: page.categoryIndex + }) + } +} diff --git a/evcharger-app/qml/SensorsConfigurationPage.qml b/evcharger-app/qml/SensorsConfigurationPage.qml index 3fe250b..0f69c41 100644 --- a/evcharger-app/qml/SensorsConfigurationPage.qml +++ b/evcharger-app/qml/SensorsConfigurationPage.qml @@ -1,13 +1,92 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import EVChargerApp NavigationPage { - title: qsTr("Sensors Configuration") + title: qsTr("Sensors and categories") + + ApiKeyValueHelper { + id: voltageNames + deviceConnection: theDeviceConnection + apiKey: "usn" + } + + ApiKeyValueHelper { + id: voltageValues + deviceConnection: theDeviceConnection + apiKey: "usv" + } + + ApiKeyValueHelper { + id: currentNames + deviceConnection: theDeviceConnection + apiKey: "isn" + } + + ApiKeyValueHelper { + id: currentValues + deviceConnection: theDeviceConnection + apiKey: "isv" + } + + ApiKeyValueHelper { + id: categoryNames + deviceConnection: theDeviceConnection + apiKey: "ccn" + } Text { - text: "TODO" + Layout.fillWidth: true + text: qsTr("Measurement") + font.bold: true + } - Layout.fillHeight: true + GeneralOnOffSwitch { + id: measurementService + apiKey: "mea" + text: qsTr("Measurement service enabled") + } + + Text { + Layout.fillWidth: true + visible: measurementService.exists + text: qsTr("Continuous raw measurement can reduce controller performance.") + color: "gray" + wrapMode: Text.Wrap + } + + SettingsCard { + visible: updateRate.exists + + ApiStatusRow { + id: updateRate + apiKey: "mur" + label: qsTr("Measurement update rate") + } + } + + NavigationItem { + iconName: "electric_meter" + title: qsTr("Voltage sensors") + description: qsTr("Live voltages, phase assignment and calibration") + visible: voltageNames.exists || voltageValues.exists + component: Component { VoltageSensorsPage {} } + } + + NavigationItem { + iconName: "speed" + title: qsTr("Current sensors") + description: qsTr("Live current, power, phase, direction and CT ratio") + visible: currentNames.exists || currentValues.exists + component: Component { CurrentSensorsPage {} } + } + + NavigationItem { + iconName: "category" + title: qsTr("Energy categories") + description: qsTr("Power, energy counters and sensor factors") + visible: categoryNames.exists + component: Component { SensorCategoriesPage {} } } } diff --git a/evcharger-app/qml/SettingsTabPage.qml b/evcharger-app/qml/SettingsTabPage.qml index 8d7536b..5881f86 100644 --- a/evcharger-app/qml/SettingsTabPage.qml +++ b/evcharger-app/qml/SettingsTabPage.qml @@ -51,6 +51,22 @@ AnimatedStackView { visible: carApiKeyHelper.exists } NavigationItem { + ApiKeyValueHelper { + id: sensorCategoriesApiKeyHelper + deviceConnection: theDeviceConnection + apiKey: "ccn" + } + ApiKeyValueHelper { + id: voltageSensorsApiKeyHelper + deviceConnection: theDeviceConnection + apiKey: "usv" + } + ApiKeyValueHelper { + id: currentSensorsApiKeyHelper + deviceConnection: theDeviceConnection + apiKey: "isv" + } + iconName: "device_hub" title: qsTr("Sensors Configuration") description: [ @@ -61,7 +77,9 @@ AnimatedStackView { SensorsConfigurationPage { } } - visible: controllerApiKeyHelper.exists + visible: sensorCategoriesApiKeyHelper.exists + || voltageSensorsApiKeyHelper.exists + || currentSensorsApiKeyHelper.exists } NavigationItem { ApiKeyValueHelper { diff --git a/evcharger-app/qml/VoltageSensorsPage.qml b/evcharger-app/qml/VoltageSensorsPage.qml new file mode 100644 index 0000000..fc00d43 --- /dev/null +++ b/evcharger-app/qml/VoltageSensorsPage.qml @@ -0,0 +1,127 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import EVChargerApp + +NavigationPage { + id: page + title: qsTr("Voltage sensors") + + readonly property var phaseChoices: [ + { value: 0, text: qsTr("L1") }, + { value: 1, text: qsTr("L2") }, + { value: 2, text: qsTr("L3") }, + { value: 3, text: qsTr("Neutral") } + ] + readonly property int sensorCount: Math.max(arrayLength(sensorNames.value), arrayLength(sensorValues.value)) + + function arrayLength(value) { + return value !== null && value !== undefined && value.length !== undefined ? value.length : 0 + } + + function sensorName(index) { + if (arrayLength(sensorNames.value) > index) + return sensorNames.value[index] + return qsTr("Voltage sensor %1").arg(index + 1) + } + + function sensorValue(index) { + return arrayLength(sensorValues.value) > index ? sensorValues.value[index] : null + } + + function phaseName(value) { + for (let i = 0; i < phaseChoices.length; ++i) { + if (phaseChoices[i].value === value) + return phaseChoices[i].text + } + return "—" + } + + ApiKeyValueHelper { + id: sensorNames + deviceConnection: theDeviceConnection + apiKey: "usn" + } + + ApiKeyValueHelper { + id: sensorValues + deviceConnection: theDeviceConnection + apiKey: "usv" + } + + Text { + Layout.fillWidth: true + text: qsTr("Live measurements") + font.bold: true + } + + Repeater { + model: page.sensorCount + + SettingsCard { + id: voltageCard + required property int index + readonly property var reading: page.sensorValue(index) + + SettingsSectionLabel { + text: page.sensorName(index) + } + + MeasurementRow { label: qsTr("Voltage L1"); value: voltageCard.reading ? voltageCard.reading.u1 : undefined; unit: qsTr("V") } + MeasurementRow { label: qsTr("Voltage L2"); value: voltageCard.reading ? voltageCard.reading.u2 : undefined; unit: qsTr("V") } + MeasurementRow { label: qsTr("Voltage L3"); value: voltageCard.reading ? voltageCard.reading.u3 : undefined; unit: qsTr("V") } + MeasurementRow { label: qsTr("Neutral voltage"); value: voltageCard.reading ? voltageCard.reading.uN : undefined; unit: qsTr("V") } + MeasurementRow { label: qsTr("Frequency"); value: voltageCard.reading ? voltageCard.reading.fhz : undefined; unit: qsTr("Hz") } + } + } + + Text { + Layout.fillWidth: true + Layout.topMargin: 8 + text: qsTr("Phase assignment") + font.bold: true + } + + Text { + Layout.fillWidth: true + text: qsTr("Assign each physical voltage input to the electrical phase it measures.") + color: "gray" + wrapMode: Text.Wrap + } + + ApiEnumEditItem { + text: qsTr("Voltage input 1") + apiKey: "u1p" + choices: page.phaseChoices + } + ApiEnumEditItem { + text: qsTr("Voltage input 2") + apiKey: "u2p" + choices: page.phaseChoices + } + ApiEnumEditItem { + text: qsTr("Voltage input 3") + apiKey: "u3p" + choices: page.phaseChoices + } + ApiEnumEditItem { + text: qsTr("Voltage input 4") + apiKey: "u4p" + choices: page.phaseChoices + } + + Text { + Layout.fillWidth: true + Layout.topMargin: 8 + text: qsTr("Calibration") + font.bold: true + } + + EditValueItem { + text: qsTr("Voltage calibration factor") + apiKey: "mvc" + visible: exists + valueText: value === null || value === undefined ? "—" : Qt.locale().toString(Number(value), "f", 5) + editableItem: DoubleSpinBox { decimals: 5; from: 0; to: 1000; stepSize: 0.00001 } + } +} diff --git a/evcharger-app/qml/qmldir b/evcharger-app/qml/qmldir index 5b36880..921a4ce 100644 --- a/evcharger-app/qml/qmldir +++ b/evcharger-app/qml/qmldir @@ -51,12 +51,15 @@ EVChargerApp 1.0 HardwareInformationPage.qml EVChargerApp 1.0 HotspotPage.qml EVChargerApp 1.0 InformationsTabPage.qml EVChargerApp 1.0 InformationChartPanel.qml +EVChargerApp 1.0 IndexedEditValueItem.qml +EVChargerApp 1.0 IndexedOnOffSwitch.qml EVChargerApp 1.0 KwhLimitPage.qml EVChargerApp 1.0 LedPage.qml EVChargerApp 1.0 LicensesPage.qml EVChargerApp 1.0 LoadBalancingPage.qml EVChargerApp 1.0 LogicModeButton.qml EVChargerApp 1.0 MainScreen.qml +EVChargerApp 1.0 MeasurementRow.qml EVChargerApp 1.0 MqttPage.qml EVChargerApp 1.0 NamePage.qml EVChargerApp 1.0 NavigationItem.qml @@ -77,6 +80,10 @@ EVChargerApp 1.0 SelectLogicModeItem.qml EVChargerApp 1.0 SelectPhaseSwitchModeItem.qml EVChargerApp 1.0 SendMessageButton.qml EVChargerApp 1.0 SensorsConfigurationPage.qml +EVChargerApp 1.0 CurrentSensorPage.qml +EVChargerApp 1.0 CurrentSensorsPage.qml +EVChargerApp 1.0 SensorCategoriesPage.qml +EVChargerApp 1.0 SensorCategoryPage.qml EVChargerApp 1.0 SetPriceLimitPage.qml EVChargerApp 1.0 SettingsTabPage.qml EVChargerApp 1.0 SetValueHelper.qml @@ -89,6 +96,7 @@ EVChargerApp 1.0 TimePickerLabel.qml EVChargerApp 1.0 TimePicker.qml EVChargerApp 1.0 TitleText.qml EVChargerApp 1.0 VerticalTabButton.qml +EVChargerApp 1.0 VoltageSensorsPage.qml EVChargerApp 1.0 WhiteBox.qml EVChargerApp 1.0 WhiteCheckDelegate.qml EVChargerApp 1.0 WhiteItemDelegate.qml