Import existing sources, this is Day 5 with this app

This commit is contained in:
2024-07-06 00:01:03 +02:00
parent e51db32b30
commit 9fa50be902
141 changed files with 7374 additions and 0 deletions

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "3rdparty/qmsgpack"]
path = 3rdparty/qmsgpack
url = ../../0xFEEDC0DE64/qmsgpack.git
[submodule "3rdparty/QtZeroConf"]
path = 3rdparty/QtZeroConf
url = ../../0xFEEDC0DE64/QtZeroConf.git

2
3rdparty/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,2 @@
add_subdirectory(qmsgpack)
add_subdirectory(QtZeroConf)

1
3rdparty/QtZeroConf vendored Submodule

Submodule 3rdparty/QtZeroConf added at 2fbb52b708

1
3rdparty/qmsgpack vendored Submodule

Submodule 3rdparty/qmsgpack added at 0117422339

23
AboutPage.qml Normal file
View File

@@ -0,0 +1,23 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("About")
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Firmware")
component: "FirmwarePage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Hardware information")
component: "HardwareInformationPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Licenses")
component: "LicensesPage.qml"
}
}

13
AccessPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Access")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

115
AddDeviceScreen.qml Normal file
View File

@@ -0,0 +1,115 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
NavigationPage {
title: qsTr("Setup or add device")
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: localLayout.implicitHeight
color: "white"
radius: 5
ColumnLayout {
id: localLayout
anchors.fill: parent
Text {
text: qsTr("Add via local connection or AP:")
}
RowLayout {
Layout.fillWidth: true
TextField {
id: manualUrl
Layout.fillWidth: true
placeholderText: qsTr("Local url")
text: "ws://10.128.250.181/ws"
// validator: RegularExpressionValidator { regularExpression: /^wss?://.*/ }
onAccepted: connectLocalButton.clicked()
}
Button {
id: connectLocalButton
text: qsTr("Connect")
onClicked: deviceSelected(manualUrl.text, "")
}
}
}
}
Item {
Layout.preferredHeight: 50
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: cloudLayout.implicitHeight
color: "white"
radius: 5
ColumnLayout {
id: cloudLayout
anchors.fill: parent
spacing: 5
Text {
text: qsTr("Add via cloud:")
}
RowLayout {
Layout.fillWidth: true
TextField {
id: cloudSerial
Layout.fillWidth: true
placeholderText: qsTr("Cloud Serial")
text: "000000"
onTextChanged: {
const serial = Number(text);
for (let i = 0; i < cloudUrlsModel.count; ++i) {
const entry = cloudUrlsModel.get(i);
if (serial >= entry.minSerial && serial <= entry.maxSerial) {
cloudType.currentIndex = i;
break;
}
}
}
inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhFormattedNumbersOnly
validator: RegularExpressionValidator { regularExpression: /^[0-9]{6,}$/ }
onAccepted: connectCloudButton.clicked()
}
ComboBox {
id: cloudType
textRole: "text"
valueRole: "url"
model: cloudUrlsModel
}
Button {
id: connectCloudButton
text: qsTr("Connect")
onClicked: deviceSelected(cloudType.currentValue + cloudSerial.text, "")
}
}
}
}
Item {
Layout.fillHeight: true
}
}

18
ApiKeyValueItem.qml Normal file
View File

@@ -0,0 +1,18 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
Text {
property alias apiKey: apiKeyValueHelper.apiKey
property alias exists: apiKeyValueHelper.exists
Layout.fillWidth: true
ApiKeyValueHelper {
id: apiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
}
text: apiKeyValueHelper.value
}

181
ApiSettingsPage.qml Normal file
View File

@@ -0,0 +1,181 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
NavigationPage {
title: qsTr("API Settings")
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: localApiLayout.implicitHeight
visible: localApi.exists
ColumnLayout {
id: localApiLayout
anchors.fill: parent
GeneralOnOffSwitch {
id: localApi
apiKey: "hai"
text: qsTr("Allow access to local HTTP-Api v2")
}
Button {
text: qsTr("API documentation")
}
RowLayout {
Layout.fillWidth: true
SendMessageHelper {
id: apiToken
deviceConnection: mainScreen.deviceConnection
}
Button {
text: qsTr("Generate token")
onClicked: apiToken.sendMessage({ type: "generateApiToken" })
}
BusyIndicator {
visible: apiToken.pending
}
Text {
Layout.fillWidth: true
visible: apiToken.response
text: {
if (!apiToken.response)
return ""
if (apiToken.response.success)
return qsTr("OK! token: %0").arg(apiToken.response.token)
else
return apiToken.response.message
}
wrapMode: Text.Wrap
}
}
RowLayout {
Layout.fillWidth: true
ApiKeyValueHelper {
id: tokenSetup
deviceConnection: mainScreen.deviceConnection
apiKey: "hatv"
}
visible: tokenSetup.value
SendMessageHelper {
id: abortFirmwareUpdate
deviceConnection: mainScreen.deviceConnection
}
Button {
text: qsTr("Clear API token")
onClicked: abortFirmwareUpdate.sendMessage({ type: "clearApiToken" })
}
BusyIndicator {
visible: abortFirmwareUpdate.pending
}
RequestStatusText {
Layout.fillWidth: true
request: abortFirmwareUpdate
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: cloudApiLayout.implicitHeight
visible: cloudApi.exists
ColumnLayout {
id: cloudApiLayout
anchors.fill: parent
GeneralOnOffSwitch {
id: cloudApi
apiKey: "cae"
text: qsTr("Enable cloud API")
}
ApiKeyValueHelper {
id: cloudApiKey
deviceConnection: mainScreen.deviceConnection
apiKey: "cak"
}
Text {
visible: cloudApiKey.exists
text: qsTr("API key: %0").arg(cloudApiKey.value)
}
Button {
text: qsTr("API documentation")
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: gridApiLayout.implicitHeight
visible: gridApi.exists
ColumnLayout {
id: gridApiLayout
anchors.fill: parent
GeneralOnOffSwitch {
Layout.fillWidth: true
id: gridApi
apiKey: "gme"
text: qsTr("Enable grid API")
}
ApiKeyValueHelper {
id: gridApiKey
deviceConnection: mainScreen.deviceConnection
apiKey: "gmk"
}
Text {
Layout.fillWidth: true
visible: gridApiKey.exists
text: qsTr("API key: %0").arg(gridApiKey.value)
}
Button {
text: qsTr("API documentation")
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: legacyApiLayout.implicitHeight
visible: legacyApi.exists
ColumnLayout {
id: legacyApiLayout
anchors.fill: parent
GeneralOnOffSwitch {
id: legacyApi
apiKey: "hla"
text: qsTr("Allow access to legacy HTTP-Api v1")
}
Button {
text: qsTr("API documentation")
}
}
}
}

35
AppInstance.qml Normal file
View File

@@ -0,0 +1,35 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import EVChargerApp
Loader {
id: loader
sourceComponent: deviceList
function backPressed() {
return loader.item.backPressed()
}
Component {
id: deviceList
DeviceListScreen {
//onDeviceSelected: (url, password) => loader.setSource("DeviceScreen.qml", { url, password })
onDeviceSelected: function(url, password) {
loader.sourceComponent = deviceScreen
loader.item.url = url;
loader.item.password = password;
}
}
}
Component {
id: deviceScreen
DeviceScreen {
onClose: loader.sourceComponent = deviceList
}
}
}

60
AppSettingsPage.qml Normal file
View File

@@ -0,0 +1,60 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
NavigationPage {
title: qsTr("App Settings")
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: gridLayout.implicitHeight
color: "white"
radius: 5
GridLayout {
id: gridLayout
columns: 2
Label {
text: qsTr("Number of app instances:")
font.bold: true
}
SpinBox {
value: theSettings.numberOfAppInstances
onValueModified: theSettings.numberOfAppInstances = value
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: gridLayout2.implicitHeight
color: "white"
radius: 5
GridLayout {
id: gridLayout2
columns: 2
Label {
text: qsTr("solalaweb key:")
font.bold: true
}
Button {
text: qsTr("Select...")
}
Label {
text: qsTr("solalaweb cert:")
font.bold: true
}
Button {
text: qsTr("Select...")
}
}
}
}

39
BaseNavigationPage.qml Normal file
View File

@@ -0,0 +1,39 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
Layout.fillWidth: true
property alias title: titleText.text
default property alias data: columnLayout.data
property alias headerItems: headerLayout.data
ColumnLayout {
id: columnLayout
anchors.fill: parent
RowLayout {
id: headerLayout
Layout.fillWidth: true
Button {
text: qsTr("Back")
visible: stackView.depth > 1
onClicked: stackView.pop()
}
Text {
Layout.fillWidth: true
id: titleText
font.pixelSize: 30
font.bold: true
wrapMode: Text.Wrap
}
}
}
}

158
CMakeLists.txt Normal file
View File

@@ -0,0 +1,158 @@
cmake_minimum_required(VERSION 3.16)
project(evcharger-app LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
#set(CMAKE_CXX_STANDARD 23)
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
#set(CMAKE_CXX_EXTENSIONS ON)
add_compile_options(-std=c++2b)
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif(CCACHE_FOUND)
add_definitions(-DQT_GUI_LIB)
add_subdirectory(3rdparty)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick WebSockets LinguistTools)
qt_standard_project_setup(REQUIRES 6.6 I18N_TRANSLATED_LANGUAGES de)
qt_add_executable(evcharger-app WIN32 MACOSX_BUNDLE
apikeyexistancehelper.cpp
apikeyexistancehelper.h
apikeyvaluehelper.cpp
apikeyvaluehelper.h
appsettings.cpp
appsettings.h
deviceconnection.cpp
deviceconnection.h
devicesmodel.cpp
devicesmodel.h
main.cpp
sendmessagehelper.cpp
sendmessagehelper.h
)
qt6_add_translations(evcharger-app
RESOURCE_PREFIX /EVChargerApp/i18n
TS_FILE_BASE qml
TS_FILE_DIR i18n
)
set_property(TARGET evcharger-app APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android)
if (ANDROID)
include(${ANDROID_SDK_ROOT}/android_openssl/CMakeLists.txt)
set_target_properties(evcharger-app PROPERTIES QT_ANDROID_EXTRA_LIBS "${ANDROID_EXTRA_LIBS}")
endif()
qt_add_qml_module(evcharger-app
URI EVChargerApp
VERSION 1.0
QML_FILES
AboutPage.qml
AccessPage.qml
AddDeviceScreen.qml
ApiKeyValueItem.qml
ApiSettingsPage.qml
AppInstance.qml
AppSettingsPage.qml
BaseNavigationPage.qml
CablePage.qml
CarPage.qml
ChargerTabPage.qml
ChargingConfigurationPage.qml
ChargingSpeedPage.qml
CloudUrlsModel.qml
CloudPage.qml
ConnectingScreen.qml
ConnectionPage.qml
ControllerPage.qml
ControllerTabPage.qml
CurrentLevelsPage.qml
DailyTripPage.qml
DateAndTimePage.qml
DeviceListScreen.qml
DeviceScreen.qml
DisplaySettingsPage.qml
EcoTabPage.qml
EthernetPage.qml
EVChargerApp.qml
FirmwarePage.qml
FlexibleEnergyTariffPage.qml
GeneralOnOffSwitch.qml
GeneralPage.qml
GridPage.qml
GroundCheckPage.qml
HardwareInformationPage.qml
HotspotPage.qml
InformationsTabPage.qml
KwhLimitPage.qml
LedPage.qml
LicensesPage.qml
LoadBalancingPage.qml
MainScreen.qml
NamePage.qml
NavigationItem.qml
NavigationPage.qml
NotificationsPage.qml
OcppPage.qml
PasswordPage.qml
PvSurplusPage.qml
RebootPage.qml
RequestStatusText.qml
SchedulerPage.qml
SecurityPage.qml
SensorsConfigurationPage.qml
SettingsTabPage.qml
SwitchLanguagePage.qml
VerticalTabButton.qml
WiFiErrorsPage.qml
WiFiOnOffSwitch.qml
WiFiPage.qml
RESOURCES
icons/Charger.svg
icons/ChargerV3.svg
icons/ChargerV4.svg
icons/Controller.svg
icons/Alarm.svg
icons/EcoModeFilled.svg
icons/Charts.svg
images/controller.png
images/geminiFlex.png
images/homeFix.png
images/homePlus.png
images/wattpilot.png
images/phoenix.png
images/geminiFix.png
material-icons/add.svg
material-icons/grid_guides.svg
material-icons/settings.svg
ui-icons/MaterialIcons-Regular.ttf
)
target_link_libraries(evcharger-app PUBLIC
Qt6::Core
Qt6::Gui
Qt6::Quick
Qt6::WebSockets
qmsgpack
QtZeroConf
)
install(TARGETS evcharger-app
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_qml_app_script(
TARGET evcharger-app
OUTPUT_SCRIPT deploy_script
MACOS_BUNDLE_POST_BUILD
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})

13
CablePage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Cable")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
CarPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Car")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

157
ChargerTabPage.qml Normal file
View File

@@ -0,0 +1,157 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
Page {
function backPressed() {
return false
}
header: ToolBar {
id: toolBar
background: Rectangle {
color: "lightblue"
}
RowLayout {
anchors.fill: parent
Label {
Layout.fillWidth: true
Layout.fillHeight: true
ApiKeyValueHelper {
id: friendlyName
deviceConnection: mainScreen.deviceConnection
apiKey: "fna"
}
text: friendlyName.value
color: "white"
verticalAlignment: Text.AlignVCenter
}
Button {
Layout.fillHeight: true
text: qsTr("Devices")
onClicked: loader.close()
}
}
}
Flickable {
id: flickable
anchors.fill: parent
contentHeight: columnLayout.implicitHeight
clip: true
ColumnLayout {
id: columnLayout
width: flickable.width
spacing: 5
RowLayout {
Layout.fillWidth: true
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: qsTr("No car connected")
font.pixelSize: 20
font.bold: true
wrapMode: Text.Wrap
}
Text {
Layout.fillWidth: true
text: qsTr("Connect the cable to charge your car")
wrapMode: Text.Wrap
}
}
Image {
Layout.preferredWidth: parent.width / 4
Layout.preferredHeight: paintedHeight
fillMode: Image.PreserveAspectFit
ApiKeyValueHelper {
id: devicetype
deviceConnection: mainScreen.deviceConnection
apiKey: "typ"
}
ApiKeyValueHelper {
id: isgo
deviceConnection: mainScreen.deviceConnection
apiKey: "isgo"
}
source: {
if (devicetype.value == 'go-eCharger_V5' ||
devicetype.value == 'go-eCharger_V4')
{
if (isgo.value)
return "images/geminiFlex.png"
else
return "images/geminiFix.png"
} else if (devicetype.value == 'wattpilot_V2') {
return "images/wattpilot.png"
} else if (devicetype.value == 'go-eCharger' ||
devicetype.value == 'wattpilot') {
return "images/homeFix.png"
} else if (devicetype.value == 'go-eCharger_Phoenix') {
return "images/phoenix.png"
}
return "material-icons/grid_guides.svg"
}
}
}
Button {
Layout.fillWidth: true
text: qsTr("Start")
}
ButtonGroup {
buttons: column.children
}
RowLayout {
id: column
Layout.fillWidth: true
Button {
Layout.fillWidth: true
checked: true
checkable: true
text: qsTr("Eco")
display: AbstractButton.TextUnderIcon
}
Button {
Layout.fillWidth: true
checkable: true
text: qsTr("Basic")
display: AbstractButton.TextUnderIcon
}
Button {
Layout.fillWidth: true
checkable: true
text: qsTr("Daily trip")
display: AbstractButton.TextUnderIcon
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Charging configuration")
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Charging Speed")
component: "ChargingSpeedPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("kWh Limit")
component: "KwhLimitPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Daily Trip")
component: "DailyTripPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Flexible energy tariff")
component: "FlexibleEnergyTariffPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("PV Surplus")
component: "PvSurplusPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Load Balancing")
component: "LoadBalancingPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Scheduler")
component: "SchedulerPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Current Levels")
component: "CurrentLevelsPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Car")
component: "CarPage.qml"
}
}

13
ChargingSpeedPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Charging Speed")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

81
CloudPage.qml Normal file
View File

@@ -0,0 +1,81 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
NavigationPage {
title: qsTr("Cloud")
GeneralOnOffSwitch {
Layout.fillWidth: true
apiKey: "cwe"
text: qsTr("Enable cloud connection")
}
Text {
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Features like flexible energy tariffs, time sync and app connection are unavilable when \"Enable cloud connection\" is disabled")
}
Rectangle {
color: "white"
radius: 5
Layout.fillWidth: true
Layout.preferredHeight: gridLayout.implicitHeight
GridLayout {
id: gridLayout
anchors.fill: parent
columns: 2
Label {
text: qsTr("Trying to connect:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "cws"
}
Label {
text: qsTr("Is connected:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "cwsc"
}
// TODO cwsca
Label {
text: qsTr("Hello received:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "chr"
}
Label {
text: qsTr("Queue size cloud:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "qsc"
}
Label {
text: qsTr("Last error:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "cle"
wrapMode: Text.Wrap
}
}
}
}

42
CloudUrlsModel.qml Normal file
View File

@@ -0,0 +1,42 @@
import QtQuick
ListModel {
ListElement {
text: qsTr("V2")
url: "wss://app.v2.go-e.io/"
manufacturer: "go-e"
deviceType: "has-no-mdns"
minSerial: 0
maxSerial: 49999
}
ListElement {
text: qsTr("Wattpilot")
url: "wss://app.wattpilot.io/app/"
manufacturer: "fronius"
deviceType: "wattpilot"
minSerial: 10000000
maxSerial: 99999999
}
ListElement {
text: qsTr("go-eCharger")
url: "wss://app.v3.go-e.io/"
manufacturer: "go-e"
deviceType: "go-eCharger"
minSerial: 50000
maxSerial: 499999
}
ListElement {
text: qsTr("go-eController")
url: "wss://app.controller.go-e.io/"
manufacturer: "go-e"
deviceType: "controller"
minSerial: 900000
maxSerial: 999999
}
ListElement {
text: qsTr("Solalaweb")
url: "wss://solalaweb.com/"
manufacturer: "go-e"
deviceType: "has-no-mdns"
}
}

49
ConnectingScreen.qml Normal file
View File

@@ -0,0 +1,49 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ColumnLayout {
signal connected
required property DeviceConnection deviceConnection
function backPressed() {
close()
return true
}
Connections {
target: deviceConnection
onFullStatusReceived: connected()
}
Text {
text: qsTr("Trying to reach device...")
}
BusyIndicator {
Layout.fillWidth: true
running: true
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: collectedMessages
clip: true
delegate: Label {
text: message
}
onCountChanged: positionViewAtEnd()
}
Button {
text: qsTr("Cancel")
onClicked: close()
}
}

49
ConnectionPage.qml Normal file
View File

@@ -0,0 +1,49 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
NavigationPage {
title: qsTr("Connection")
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Wi-Fi")
component: "WiFiPage.qml"
visible: wifiStaApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Hotspot")
component: "HotspotPage.qml"
visible: wifiApApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Ethernet")
component: "EthernetPage.qml"
visible: ethernetApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("OCPP")
component: "OcppPage.qml"
visible: ocppApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Cloud")
component: "CloudPage.qml"
visible: cloudApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("API Settings")
component: "ApiSettingsPage.qml"
}
}

13
ControllerPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Controller")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

123
ControllerTabPage.qml Normal file
View File

@@ -0,0 +1,123 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
Page {
function backPressed() {
return false
}
header: ToolBar {
id: toolBar
background: Rectangle {
color: "lightblue"
}
RowLayout {
anchors.fill: parent
Label {
Layout.fillWidth: true
Layout.fillHeight: true
ApiKeyValueHelper {
id: friendlyName
deviceConnection: mainScreen.deviceConnection
apiKey: "fna"
}
text: friendlyName.value
color: "white"
verticalAlignment: Text.AlignVCenter
}
Button {
Layout.fillHeight: true
text: qsTr("Devices")
onClicked: loader.close()
}
}
}
Flickable {
anchors.fill: parent
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Text {
//Layout.fillWidth: true
width: 100
text: qsTr("Connected")
font.pixelSize: 20
font.bold: true
wrapMode: Text.Wrap
}
}
Image {
Layout.fillHeight: true
// width: 200
// height: 200
Layout.preferredWidth: 100
fillMode: Image.PreserveAspectFit
source: "images/controller.png"
}
}
Text {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Controller TODO"
}
Text {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Controller TODO"
}
Text {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Controller TODO"
}
Text {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Controller TODO"
}
Text {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Controller TODO"
}
Text {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Controller TODO"
}
}
}
}

13
CurrentLevelsPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Current Levels")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
DailyTripPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Daily Trip")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
DateAndTimePage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Date and time")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

337
DeviceListScreen.qml Normal file
View File

@@ -0,0 +1,337 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
StackView {
id: stackView
signal deviceSelected(url: string, password: string)
function backPressed() {
if (depth > 1) {
pop()
return true
}
return false
}
// pushEnter: Transition {
// PropertyAnimation {
// property: "opacity"
// from: 0
// to:1
// duration: 200
// }
// }
// pushExit: Transition {
// PropertyAnimation {
// property: "opacity"
// from: 1
// to:0
// duration: 200
// }
// }
// popEnter: Transition {
// PropertyAnimation {
// property: "opacity"
// from: 0
// to:1
// duration: 200
// }
// }
// popExit: Transition {
// PropertyAnimation {
// property: "opacity"
// from: 1
// to:0
// duration: 200
// }
// }
initialItem: BaseNavigationPage {
id: page
title: qsTr("Device list")
CloudUrlsModel {
id: cloudUrlsModel
}
DevicesModel {
id: devicesModel
settings: theSettings
Component.onCompleted: start()
}
headerItems: [
Button {
text: qsTr("App Settings")
icon.source: "material-icons/settings.svg"
display: AbstractButton.IconOnly
onClicked: stackView.push(appSettingsPage)
Component {
id: appSettingsPage
AppSettingsPage {
}
}
}
]
ListView {
id: listView
model: devicesModel
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 10
contentX: -15
ScrollBar.vertical: ScrollBar {
interactive: false
}
footer: Item {
height: 75
}
section.property: "saved"
section.criteria: ViewSection.FullString
section.delegate: Component {
id: sectionHeading
Text {
width: ListView.view.width - 30
height: implicitHeight + 50
bottomPadding: 10
verticalAlignment: Text.AlignBottom
wrapMode: Text.Wrap
required property bool section
text: section ? qsTr("My devices") : qsTr("Found devices")
font.bold: true
font.pixelSize: 20
}
}
delegate: SwipeDelegate {
id: delegate
checkable: true
width: ListView.view.width - 30
required property int index
required property string name
required property string serial
required property string manufacturer
required property string deviceType
required property string friendlyName
required property string password
required property bool saved
required property string hostName
required property string ip
Component.onCompleted: {
background.color = "white"
background.radius = 5
}
swipe.enabled: saved
swipe.right: Label {
id: deleteLabel
text: qsTr("Delete")
color: "white"
verticalAlignment: Label.AlignVCenter
padding: 12
height: parent.height
anchors.right: parent.right
SwipeDelegate.onClicked: {
listView.model.removeRow(delegate.index)
swipe.close()
delegate.checked = false;
}
background: Rectangle {
color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
}
}
contentItem: ColumnLayout {
RowLayout {
Image {
height: parent.height
//Layout.fillHeight: true
source: {
if (delegate.deviceType == 'go-eCharger_V5' ||
delegate.deviceType == 'go-eCharger_V4' ||
delegate.deviceType == 'wattpilot_V2')
{
return "icons/ChargerV4.svg"
} else if (delegate.deviceType == 'go-eCharger' ||
delegate.deviceType == 'wattpilot') {
return "icons/ChargerV3.svg"
} else if (delegate.deviceType == 'go-eCharger_Phoenix') {
return "icons/Charger.svg"
} else if (delegate.deviceType.includes('controller')) {
return "icons/Controller.svg"
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Label {
Layout.fillWidth: true
text: delegate.friendlyName
font.bold: true
elide: Text.ElideRight
}
Label {
Layout.fillWidth: true
text: qsTr("Serial Number %0").arg(delegate.serial);
}
}
}
GridLayout {
id: grid
visible: false
columns: 2
rowSpacing: 10
columnSpacing: 10
Label {
text: qsTr("Manufacturer:")
Layout.leftMargin: 60
}
Label {
text: delegate.manufacturer
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
Label {
text: qsTr("Device Type:")
Layout.leftMargin: 60
}
Label {
text: delegate.deviceType
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
Label {
text: qsTr("Host Name:")
Layout.leftMargin: 60
}
Label {
text: delegate.hostName
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
Label {
text: qsTr("Ip:")
Layout.leftMargin: 60
}
Label {
text: delegate.ip
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
Button {
enabled: delegate.ip != ""
text: qsTr("Connect local")
onClicked: deviceSelected("ws://" + delegate.ip + "/ws", delegate.password)
}
Button {
property var cloudUrl: {
for (let i = 0; i < cloudUrlsModel.count; ++i) {
const entry = cloudUrlsModel.get(i);
if (delegate.manufacturer === entry.manufacturer &&
delegate.deviceType.includes(entry.deviceType)) {
return entry.url;
}
}
return null;
}
enabled: cloudUrl !== null
text: qsTr("Connect cloud")
onClicked: deviceSelected(cloudUrl + delegate.serial, delegate.password)
}
}
}
states: [
State {
name: "expanded"
when: delegate.checked
PropertyChanges {
// TODO: When Qt Design Studio supports generalized grouped properties, change to:
// grid.visible: true
// qmllint disable Quick.property-changes-parsed
target: grid
visible: true
}
}
]
}
}
Button {
parent: page
anchors {
right: parent.right
rightMargin: 10
bottom: parent.bottom
bottomMargin: 25
}
text: qsTr("Add or setup device")
icon.source: "material-icons/add.svg"
font.pointSize: 12
font.bold: true
highlighted: true
Material.accent: Material.Blue
display: listView.contentY < 20 ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly
onClicked: stackView.push(addDeviceScreen)
Component {
id: addDeviceScreen
AddDeviceScreen {
}
}
}
}
}

131
DeviceScreen.qml Normal file
View File

@@ -0,0 +1,131 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
Loader {
id: loader
signal close
property alias url: theDeviceConnection.url
property alias password: theDeviceConnection.password
function backPressed() {
return item.backPressed()
}
ListModel {
id: collectedMessages
function push(message) {
if (collectedMessages.count >= 15)
collectedMessages.remove(0);
collectedMessages.append({message});
}
}
anchors.fill: parent
DeviceConnection {
id: theDeviceConnection
settings: theSettings
onLogMessage: (message) => collectedMessages.push(message)
onAuthRequired: {
passwordError.visible = false;
passwordDialog.open();
}
onAuthError: function(message) {
passwordError.visible = true;
passwordError.text = message;
passwordDialog.open();
}
onAuthImpossible: authImpossibleDialog.open()
}
sourceComponent: Component {
ConnectingScreen {
deviceConnection: theDeviceConnection
anchors.fill: parent
onConnected: loader.sourceComponent = mainScreen;
}
}
Component {
id: mainScreen
MainScreen {
deviceConnection: theDeviceConnection
}
}
Dialog {
id: passwordDialog
x: parent.width / 2 - width / 2
y: parent.height / 2 - height / 2
title: qsTr("Password required")
standardButtons: Dialog.Ok | Dialog.Cancel
focus: true
modal: true
onAccepted: theDeviceConnection.sendAuth(passwordInput.text)
onRejected: loader.close()
contentItem: GridLayout {
property int minimumInputSize: 120
id: grid
columns: 2
Text {
id: passwordError
Layout.columnSpan: 2
color: "red"
}
Label {
text: qsTr("Password:")
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
}
TextField {
id: passwordInput
focus: true
Layout.fillWidth: true
Layout.minimumWidth: grid.minimumInputSize
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
placeholderText: qsTr("Password")
echoMode: TextInput.PasswordEchoOnEdit
onAccepted: passwordDialog.accept();
}
}
}
Dialog {
id: authImpossibleDialog
x: parent.width / 2 - width / 2
y: parent.height / 2 - height / 2
title: qsTr("Authentication impossible!")
standardButtons: Dialog.Ok | Dialog.Cancel
focus: true
modal: true
onAccepted: loader.close()
onRejected: loader.close()
contentItem: Text {
text: qsTr("To use this password remotely a password has to be setup first. This can be done over the AccessPoint.");
}
}
}

13
DisplaySettingsPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Display settings")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

67
EVChargerApp.qml Normal file
View File

@@ -0,0 +1,67 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import EVChargerApp
ApplicationWindow {
id: window
width: 320
height: 480
visible: true
title: qsTr("EVcharger App")
color: "#F3F2F7"
AppSettings {
id: theSettings
}
FontLoader {
id: materialIcons
source: "ui-icons/MaterialIcons-Regular.ttf"
}
SwipeView {
id: view
anchors.fill: parent
currentIndex: indicator.currentIndex
Repeater {
model: theSettings.numberOfAppInstances
AppInstance {
width: view.width
}
}
}
PageIndicator {
id: indicator
count: view.count
currentIndex: view.currentIndex
anchors.top: view.top
anchors.horizontalCenter: parent.horizontalCenter
}
function backPressed() {
if (view.currentItem.backPressed())
return true
if (view.currentIndex > 0) {
view.currentIndex = 0
view.currentIndex = Qt.binding(() => indicator.currentIndex)
return true
}
return false
}
onClosing: (close) => {
if (backPressed())
close.accepted = false
}
}

7
EcoTabPage.qml Normal file
View File

@@ -0,0 +1,7 @@
import QtQuick
Item {
function backPressed() {
return false
}
}

17
EthernetPage.qml Normal file
View File

@@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Ethernet")
GeneralOnOffSwitch {
apiKey: "ee"
}
Text {
text: "TODO"
Layout.fillHeight: true
}
}

247
FirmwarePage.qml Normal file
View File

@@ -0,0 +1,247 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
NavigationPage {
title: qsTr("Firmware")
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: gridLayout.implicitHeight
color: "white"
radius: 5
GridLayout {
id: gridLayout
anchors.fill: parent
columns: 2
Label {
text: qsTr("Running version:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "fwv"
id: runningVersion
}
Label {
text: qsTr("Details:")
font.bold: true
}
Text {
Layout.fillWidth: true
ApiKeyValueHelper {
id: runningVersionDetails
deviceConnection: mainScreen.deviceConnection
apiKey: "apd"
}
text: JSON.stringify(runningVersionDetails.value, null, 4)
}
Label {
text: qsTr("Recommended version:")
font.bold: true
}
ApiKeyValueItem {
id: recommendedVersion
apiKey: "onv"
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: columnLayout.implicitHeight
color: "white"
radius: 5
ColumnLayout {
id: columnLayout
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
Label {
text: qsTr("Update:")
}
ComboBox {
id: firmwareSelector
ApiKeyValueHelper {
id: availableUrls
deviceConnection: mainScreen.deviceConnection
apiKey: "ocu"
}
Layout.fillWidth: true
model: availableUrls.value
delegate: ItemDelegate {
text: modelData
background: Rectangle {
color: recommendedVersion.value == modelData ? "lightgreen" : modelData.includes(runningVersion.value) ? "lightyellow" : "white"
}
width: parent.width
}
}
SendMessageHelper {
id: startFirmwareUpdate
deviceConnection: mainScreen.deviceConnection
}
Button {
text: qsTr("Start")
onClicked: startFirmwareUpdate.sendMessage({
type: "otaCloud",
firmware: firmwareSelector.currentValue
})
}
BusyIndicator {
visible: startFirmwareUpdate.pending
}
RequestStatusText {
request: startFirmwareUpdate
}
}
RowLayout {
Layout.fillWidth: true
SendMessageHelper {
id: abortFirmwareUpdate
deviceConnection: mainScreen.deviceConnection
}
Button {
text: qsTr("Abort")
onClicked: abortFirmwareUpdate.sendMessage({
type: "abortFwUpdate"
})
}
BusyIndicator {
visible: abortFirmwareUpdate.pending
}
RequestStatusText {
request: abortFirmwareUpdate
}
}
RowLayout {
Layout.fillWidth: true
SendMessageHelper {
id: switchAppPartition
deviceConnection: mainScreen.deviceConnection
}
Button {
text: qsTr("Switch partition")
onClicked: switchAppPartition.sendMessage({
type: "switchAppPartition"
})
}
BusyIndicator {
visible: switchAppPartition.pending
}
RequestStatusText {
request: switchAppPartition
}
}
GridLayout {
Layout.fillWidth: true
columns: 2
Label {
text: qsTr("Update status:")
font.bold: true
}
Text {
Layout.fillWidth: true
ApiKeyValueHelper {
id: updateStatus
deviceConnection: mainScreen.deviceConnection
apiKey: "ocs"
}
text: {
switch (updateStatus.value)
{
case 0: return 'Idle'
case 1: return 'Updating'
case 2: return 'Failed'
case 3: return 'Succeeded'
case 4: return 'NotReady'
case 5: return 'Verifying'
default: return updateStatus.value
}
}
}
Label {
text: qsTr("Update progress:")
font.bold: true
}
ColumnLayout {
Text {
Layout.fillWidth: true
ApiKeyValueHelper {
id: updateProgress
deviceConnection: mainScreen.deviceConnection
apiKey: "ocp"
}
ApiKeyValueHelper {
id: updateLength
deviceConnection: mainScreen.deviceConnection
apiKey: "ocl"
}
text: updateProgress.value + ' / ' + updateLength.value
}
ProgressBar {
from: 0
to: updateLength.value ? updateLength.value : 0
value: updateProgress.value
}
}
Label {
text: qsTr("Update message:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "ocm"
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Flexible energy tariff")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

50
GeneralOnOffSwitch.qml Normal file
View File

@@ -0,0 +1,50 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
CheckDelegate {
id: checkDelegate
required property string apiKey
property alias exists: valueHelper.exists
Layout.fillWidth: true
Component.onCompleted: {
background.color = "white"
background.radius = 5
}
ApiKeyValueHelper {
id: valueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: checkDelegate.apiKey
}
SendMessageHelper {
id: valueChanger
deviceConnection: mainScreen.deviceConnection
onResponseChanged: checkDelegate.checked = Qt.binding(function(){ return valueHelper.value; })
}
checked: valueHelper.value
text: valueHelper.value ? qsTr("On") : qsTr("Off")
wrapMode: Text.Wrap
onClicked: {
valueChanger.sendMessage({
type: "setValue",
key: checkDelegate.apiKey,
value: checked
})
}
BusyIndicator {
visible: valueChanger.pending
}
RequestStatusText {
request: valueChanger
}
}

51
GeneralPage.qml Normal file
View File

@@ -0,0 +1,51 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("General")
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Name")
component: "NamePage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Switch Language")
component: "SwitchLanguagePage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Notifications")
component: "NotificationsPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Date and time")
component: "DateAndTimePage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("LED")
component: "LedPage.qml"
visible: ledApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Controller")
component: "ControllerPage.qml"
visible: controllerApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Display settings")
component: "DisplaySettingsPage.qml"
visible: displayApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Reboot")
component: "RebootPage.qml"
}
}

13
GridPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Grid")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
GroundCheckPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Ground check")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

View File

@@ -0,0 +1,58 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
NavigationPage {
title: qsTr("Hardware information")
Rectangle {
color: "white"
radius: 5
Layout.fillWidth: true
Layout.preferredHeight: layout.implicitHeight
GridLayout {
id: layout
columns: 2
anchors.fill: parent
Label {
text: qsTr("Serial Number:")
}
ApiKeyValueItem {
Layout.fillWidth: true
apiKey: "sse"
}
Label {
visible: variant.exists
text: qsTr("Variant:")
}
ApiKeyValueItem {
id: variant
visible: exists
Layout.fillWidth: true
apiKey: "var"
}
Label {
visible: rssi.exists
text: qsTr("RSSI:")
}
ApiKeyValueItem {
id: rssi
visible: exists
Layout.fillWidth: true
apiKey: "rssi"
}
}
}
Item {
Layout.fillHeight: true
}
}

22
HotspotPage.qml Normal file
View File

@@ -0,0 +1,22 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Hotspot")
GeneralOnOffSwitch {
apiKey: "wae"
}
GeneralOnOffSwitch {
apiKey: "wda"
text: qsTr("Disable AP when online")
}
Text {
text: "TODO"
Layout.fillHeight: true
}
}

7
InformationsTabPage.qml Normal file
View File

@@ -0,0 +1,7 @@
import QtQuick
Item {
function backPressed() {
return false
}
}

13
KwhLimitPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("kWh Limit")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
LedPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("LED")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
LicensesPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Licenses")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
LoadBalancingPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Load Balancing")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

211
MainScreen.qml Normal file
View File

@@ -0,0 +1,211 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
ColumnLayout {
id: mainScreen
required property DeviceConnection deviceConnection
function backPressed() {
if (stackLayout.currentItem.item.backPressed())
return true
if (stackLayout.currentIndex != 0) {
stackLayout.currentIndex = 0
stackLayout.currentIndex = Qt.binding(() => tabBar.currentIndex)
return true
}
loader.close()
return true
}
ApiKeyValueHelper {
id: rebootTime
deviceConnection: mainScreen.deviceConnection
apiKey: "rbt"
}
function formatDuration(duration) {
duration = Math.floor(duration)
const wasNegative = duration < 0;
if (wasNegative)
duration = -duration;
const milliseconds = duration%1000;
duration-=milliseconds;
duration/=1000;
const seconds = duration%60;
duration-=seconds;
duration/=60;
const minutes = duration%60;
duration-=minutes;
duration/=60;
const hours = duration%24;
return (wasNegative ? qsTr('%0 ago') : qsTr('in %0'))
.arg(
(hours < 10 ? '0' : '') + hours + ':' +
(minutes < 10 ? '0' : '') + minutes + ':' +
(seconds < 10 ? '0' : '') + seconds + '.' +
(milliseconds < 100 ? '0' : '') + (milliseconds < 10 ? '0' : '') + milliseconds)
;
}
ApiKeyValueHelper {
id: carApiKeyHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "car"
}
ApiKeyValueHelper {
id: controllerApiKeyHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "ccp"
}
ListModel {
id: modelBoth
ListElement {
text: qsTr("Charger")
icon: "icons/ChargerV4.svg"
source: "ChargerTabPage.qml"
}
ListElement {
text: qsTr("Eco")
icon: "icons/EcoModeFilled.svg"
source: "EcoTabPage.qml"
}
ListElement {
text: qsTr("Controller")
icon: "icons/Controller.svg"
source: "ControllerTabPage.qml"
}
ListElement {
text: qsTr("Infromations")
icon: "icons/Charts.svg"
source: "InformationsTabPage.qml"
}
ListElement {
text: qsTr("Settings")
icon: "material-icons/settings.svg"
source: "SettingsTabPage.qml"
}
}
ListModel {
id: modelCharger
ListElement {
text: qsTr("Charger")
icon: "icons/ChargerV4.svg"
source: "ChargerTabPage.qml"
}
ListElement {
text: qsTr("Eco")
icon: "icons/EcoModeFilled.svg"
source: "EcoTabPage.qml"
}
ListElement {
text: qsTr("Infromations")
icon: "icons/Charts.svg"
source: "InformationsTabPage.qml"
}
ListElement {
text: qsTr("Settings")
icon: "material-icons/settings.svg"
source: "SettingsTabPage.qml"
}
}
ListModel {
id: modelController
ListElement {
text: qsTr("Controller")
icon: "icons/Controller.svg"
source: "ControllerTabPage.qml"
}
ListElement {
text: qsTr("Infromations")
icon: "icons/Charts.svg"
source: "InformationsTabPage.qml"
}
ListElement {
text: qsTr("Settings")
icon: "material-icons/settings.svg"
source: "SettingsTabPage.qml"
}
}
ListModel {
id: modelNone
ListElement {
text: qsTr("Infromations")
icon: "icons/Charts.svg"
source: "InformationsTabPage.qml"
}
ListElement {
text: qsTr("Settings")
icon: "material-icons/settings.svg"
source: "SettingsTabPage.qml"
}
}
property ListModel theModel: {
if (carApiKeyHelper.exists) {
if (controllerApiKeyHelper.exists) {
return modelBoth
} else {
return modelCharger
}
} else {
if (controllerApiKeyHelper.exists) {
return modelController
} else {
return modelNone
}
}
}
StackLayout {
id: stackLayout
Layout.fillWidth: true
Layout.fillHeight: true
currentIndex: tabBar.currentIndex
property Item currentItem: children[currentIndex]
Repeater {
model: theModel
delegate: Loader {
source: model.source
}
}
}
TabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: stackLayout.currentIndex
contentHeight: 56
Repeater {
model: theModel
delegate: VerticalTabButton {
text: model.text
//width: tabBar.width / tabBar.count
icon.source: model.icon
}
}
}
}

13
NamePage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Name")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

75
NavigationItem.qml Normal file
View File

@@ -0,0 +1,75 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ItemDelegate {
id: navigationItem
property alias iconSource: icon.source
property alias title: titleText.text
property string description
property string component
Layout.fillWidth: true
//width: parent.width
implicitWidth: row.implicitWidth
implicitHeight: Math.max(row.implicitHeight, 50)
// color: "white"
// radius: 5
// MouseArea {
// anchors.fill: parent
// onClicked: stackView.push(navigationItem.component)
// }
// background: Rectangle {
// color: "white"
// radius: 5
// }
Component.onCompleted: {
background.radius = 5
background.color = 'white'
}
onClicked: stackView.push(navigationItem.component)
RowLayout {
id: row
anchors.fill: parent
Image {
id: icon
Layout.preferredWidth: 32
Layout.preferredHeight: 32
}
ColumnLayout {
Layout.fillWidth: true
Text {
id: titleText
Layout.fillWidth: true
wrapMode: Text.Wrap
font.bold: true
}
Text {
Layout.fillWidth: true
wrapMode: Text.Wrap
text: navigationItem.description
visible: text != ""
}
}
Text {
text: ">"
}
}
}

21
NavigationPage.qml Normal file
View File

@@ -0,0 +1,21 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
BaseNavigationPage {
default property alias data: columnLayout.data
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
contentHeight: columnLayout.height
clip: true
ColumnLayout {
id: columnLayout
width: parent.width - 10
x: 5
spacing: 5
}
}
}

13
NotificationsPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Notifications")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

17
OcppPage.qml Normal file
View File

@@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("OCPP")
GeneralOnOffSwitch {
apiKey: "ocppe"
}
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
PasswordPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Password")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

13
PvSurplusPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("PV Surplus")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

24
README.md Normal file
View File

@@ -0,0 +1,24 @@
# evcharger-app
This app aims to be functional first and pretty second.
The app that is shipped with the products was made in opposite order and has a few problems now.
In contrast to the official go-e app this app has:
* Actual support to connect over cloud to the device
* Actual support to connect locally to the device
* No silly "device type does not match"
* No silly assumptions on what feature set the device has, it looks on the existance of api keys
* It shows the problems that the device is reporting like wifi error log, ocpp error log, cloud error log
* It shows any network communication problems like invalid websocket response received
* API Key Browser
* Support for all future go-e compatible products as it does not hardcode any device types (Sadly it also works with Wattpilots)
* If you enter the incorrect password it will tell you and lock up
* You can also select 06:00 in the scheduler
* You can also select any color you like in all color pickers
Lots of settings pages are still missing but this is a side project after work time and doesnt get much attention from Daniel's side.
I just need an app that just works when I stand in fron of my charger, I can't continue using solalaweb for all basic settings every day.
go-e Support is welcome to ship this app to customers that are facing WiFI, OCPP or any other issues.

13
RebootPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Reboot")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

24
RequestStatusText.qml Normal file
View File

@@ -0,0 +1,24 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
Text {
required property SendMessageHelper request
wrapMode: Text.Wrap
visible: !request.pending && request.response != undefined
text: {
if (request.response == undefined)
return ""
if (request.response.type == "response") {
if (request.response.success)
return "OK"
if ('message' in request.response)
return request.response.message
}
return JSON.stringify(request.response)
}
}

13
SchedulerPage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Scheduler")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

33
SecurityPage.qml Normal file
View File

@@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Security")
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Cable")
component: "CablePage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Access")
component: "AccessPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Password")
component: "PasswordPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Grid")
component: "GridPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Ground check")
component: "GroundCheckPage.qml"
}
}

View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Sensors Configuration")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

137
SettingsTabPage.qml Normal file
View File

@@ -0,0 +1,137 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
StackView {
id: stackView
function backPressed() {
if (depth > 1) {
pop()
return true
}
return false
}
initialItem: NavigationPage {
title: qsTr("Settings")
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Charging configuration")
description: [
qsTr("Charging Speed"),
qsTr("kWh Limit"),
qsTr("Daily Trip"),
qsTr("Flexible energy tariff"),
qsTr("PV Surplus")
].join(" • ")
component: "ChargingConfigurationPage.qml"
visible: carApiKeyHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Security")
description: [
qsTr("Cable"),
qsTr("Access"),
qsTr("Password"),
qsTr("Grid"),
qsTr("Ground check")
].join(" • ")
component: "SecurityPage.qml"
visible: carApiKeyHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Sensors Configuration")
description: [
qsTr("Sensors"),
qsTr("Categories")
].join(" • ")
component: "SensorsConfigurationPage.qml"
visible: controllerApiKeyHelper.exists
}
NavigationItem {
ApiKeyValueHelper {
id: wifiStaApiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "wen"
}
ApiKeyValueHelper {
id: wifiApApiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "wae"
}
ApiKeyValueHelper {
id: ethernetApiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "ee"
}
ApiKeyValueHelper {
id: ocppApiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "ocppe"
}
ApiKeyValueHelper {
id: cloudApiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "cwe"
}
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Connection")
description: [
wifiStaApiKeyValueHelper.exists ? qsTr("Wi-Fi") : null,
wifiApApiKeyValueHelper.exists ? qsTr("Hotspot") : null,
ethernetApiKeyValueHelper.exists ? qsTr("Ethernet") : null,
ocppApiKeyValueHelper.exists ? qsTr("OCPP") : null,
cloudApiKeyValueHelper.exists ? qsTr("Cloud") : null,
qsTr("API Settings")
].filter(Boolean).join(" • ")
component: "ConnectionPage.qml"
}
NavigationItem {
ApiKeyValueHelper {
id: ledApiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "lbr"
}
ApiKeyValueHelper {
id: controllerApiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "ccd"
}
ApiKeyValueHelper {
id: displayApiKeyValueHelper
deviceConnection: mainScreen.deviceConnection
apiKey: "ccd"
}
iconSource: "material-icons/grid_guides.svg"
title: qsTr("General")
description: [
qsTr("Name"),
qsTr("Switch Language"),
qsTr("Notifications"),
qsTr("Date and time"),
ledApiKeyValueHelper.exists ? qsTr("LED") : null,
controllerApiKeyValueHelper.exists ? qsTr("Controller") : null,
displayApiKeyValueHelper.exists ? qsTr("Display settings") : null
].filter(Boolean).join(" • ")
component: "GeneralPage.qml"
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("About")
description: [
qsTr("Firmware"),
qsTr("Hardware information"),
qsTr("Licenses")
].join(" • ")
component: "AboutPage.qml"
}
}
}

13
SwitchLanguagePage.qml Normal file
View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
title: qsTr("Switch Language")
Text {
text: "TODO"
Layout.fillHeight: true
}
}

42
VerticalTabButton.qml Normal file
View File

@@ -0,0 +1,42 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
TabButton {
id: control
contentItem: Item {
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
//anchors.horizontalCenter: parent.horizontalCenter
spacing: 5
Text {
Layout.fillWidth: true
text: control.text
font: control.font
horizontalAlignment: Text.AlignHCenter
}
RowLayout {
Layout.fillHeight: true
Item {
Layout.fillWidth: true
}
Image {
source: control.icon.source
fillMode: Image.PreserveAspectFit
width: 32
height: 32
}
Item {
Layout.fillWidth: true
}
}
}
}
}

86
WiFiErrorsPage.qml Normal file
View File

@@ -0,0 +1,86 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
BaseNavigationPage {
property ApiKeyValueHelper wifiErrorLog
title: qsTr("Wi-Fi Errors")
// Item {
// id: theAboveList
// // component exists only to complete the example supplied in the question
// }
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: trues
model: wifiErrorLog.value
spacing: 5
clip: true
verticalLayoutDirection: ListView.BottomToTop
// anchors
// {
// left: parent.left
// top: theAboveList.bottom
// right: parent.right
// bottom: parent.bottom
// }
// header: Item {}
// onContentHeightChanged: {
// if (contentHeight < height) {
// headerItem.height += (height - contentHeight)
// }
// currentIndex = count-1
// positionViewAtEnd()
// }
delegate: Rectangle {
required property var modelData
property var theModelData: modelData
color: "white"
radius: 5
width: listView.width
height: gridLayout.implicitHeight
property var properties: [ qsTr("Timestamp:"), qsTr("SSID:"), qsTr("BSSID:"), qsTr("Reason:") ]
GridLayout {
id: gridLayout
anchors.fill: parent
Repeater {
model: properties
Text {
required property int index
required property string modelData
text: modelData
Layout.row: index
Layout.column: 0
}
}
Repeater {
model: properties
Text {
Layout.fillWidth: true
required property int index
text: index == 0 ? formatDuration((theModelData[index] > 5.184e+9 ? theModelData[index] / 1000 : theModelData[index]) - rebootTime.value) : theModelData[index]
Layout.row: index
Layout.column: 1
}
}
}
}
}
}

78
WiFiOnOffSwitch.qml Normal file
View File

@@ -0,0 +1,78 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
CheckDelegate {
id: checkDelegate
Layout.fillWidth: true
Component.onCompleted: {
background.color = "white"
background.radius = 5
}
ApiKeyValueHelper {
id: staEnabled
deviceConnection: mainScreen.deviceConnection
apiKey: "wen"
}
SendMessageHelper {
id: staEnabledChanger
deviceConnection: mainScreen.deviceConnection
}
checked: staEnabled.value
text: staEnabled.value ? qsTr("On") : qsTr("Off")
onClicked: {
if (checked)
staEnabledChanger.sendMessage({
type: "setValue",
key: "wen",
value: checked
})
else {
checked = true
disableStaDialog.open()
}
}
BusyIndicator {
visible: staEnabledChanger.pending
}
RequestStatusText {
request: staEnabledChanger
}
Dialog {
id: disableStaDialog
x: window.width / 2 - width / 2
y: window.height / 2 - height / 2
width: Math.min(implicitWidth, window.width - 20)
title: qsTr("Do you really want to disable Wi-Fi?")
standardButtons: Dialog.Ok | Dialog.Cancel
focus: true
modal: true
onAccepted: {
checkDelegate.checked = false
staEnabledChanger.sendMessage({
type: "setValue",
key: "wen",
value: false
})
}
onRejected: checkDelegate.checked = Qt.binding(function() { return staEnabled.value })
contentItem: Text {
text: qsTr("This action could make your device unreachable from your local homenetwork or the cloud!");
wrapMode: Text.Wrap
}
}
}

64
WiFiPage.qml Normal file
View File

@@ -0,0 +1,64 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
NavigationPage {
title: qsTr("Wi-Fi")
WiFiOnOffSwitch {
}
GridLayout {
columns: 2
Label {
text: qsTr("Status:")
}
Text {
Layout.fillWidth: true
ApiKeyValueHelper {
id: staStatus
deviceConnection: mainScreen.deviceConnection
apiKey: "wst"
}
text: {
switch (staStatus.value)
{
case 0: return 'IDLE_STATUS'
case 1: return 'NO_SSID_AVAIL'
case 2: return 'SCAN_COMPLETED'
case 3: return 'CONNECTED'
case 4: return 'CONNECT_FAILED'
case 5: return 'CONNECTION_LOST'
case 6: return 'DISCONNECTED'
case 7: return 'CONNECTING'
case 8: return 'DISCONNECTING'
case 9: return 'NO_SHIELD'
case 10: return 'WAITING_FOR_IP'
default: return staStatus.value
}
}
}
}
Button {
ApiKeyValueHelper {
id: wifiErrorLog
deviceConnection: mainScreen.deviceConnection
apiKey: "wsl"
}
text: qsTr("(%0) Wi-Fi Errors").arg(wifiErrorLog.value == null ? 0 : wifiErrorLog.value.length)
onClicked: stackView.push("WiFiErrorsPage.qml", {wifiErrorLog} )
enabled: wifiErrorLog.value != null
}
Item {
Layout.fillHeight: true
}
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ninja.brunner.evcharger" android:installLocation="auto" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:versionName="-- %%INSERT_VERSION_NAME%% --">
<!-- %%INSERT_PERMISSIONS -->
<!-- %%INSERT_FEATURES -->
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/>
<application android:name="org.qtproject.qt.android.bindings.QtApplication" android:hardwareAccelerated="true" android:label="EVcharger App" android:requestLegacyExternalStorage="true" android:allowBackup="true" android:fullBackupOnly="false" android:icon="@drawable/icon">
<activity android:name="org.qtproject.qt.android.bindings.QtActivity" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:launchMode="singleTop" android:screenOrientation="unspecified" android:exported="true" android:label="EVcharger App" android:theme="@style/splashScreenTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.arguments" android:value="-- %%INSERT_APP_ARGUMENTS%% --"/>
<meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/splashscreen_port"/>
<meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/splashscreen_land"/>
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
<meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/splashscreen_port"/>
<meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/splashscreen_land"/>
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
<meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/splashscreen_port"/>
<meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/splashscreen_land"/>
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
<meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/splashscreen_port"/>
<meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/splashscreen_land"/>
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
<meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/splashscreen_port"/>
<meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/splashscreen_land"/>
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
<meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/splashscreen_port"/>
<meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/splashscreen_land"/>
</activity>
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.qtprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths"/>
</provider>
</application>
</manifest>

84
android/build.gradle Normal file
View File

@@ -0,0 +1,84 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.4.0'
}
}
repositories {
google()
mavenCentral()
}
apply plugin: qtGradlePluginType
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.core:core:1.13.1'
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
* - qtGradlePluginType - whether to build an app or a library
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
namespace androidPackageName
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
// Extract native libraries from the APK
packagingOptions.jniLibs.useLegacyPackaging true
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qtAndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion qtMinSdkVersion
targetSdkVersion qtTargetSdkVersion
ndk.abiFilters = qtTargetAbiList.split(",")
}
}

18
android/gradle.properties Normal file
View File

@@ -0,0 +1,18 @@
# Project-wide Gradle settings.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# Enable building projects in parallel
org.gradle.parallel=true
# Gradle caching allows reusing the build artifacts from a previous
# build with the same inputs. However, over time, the cache size will
# grow. Uncomment the following line to enable it.
#org.gradle.caching=true
#org.gradle.configuration-cache=true
# Allow AndroidX usage
android.useAndroidX=true

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
android/gradlew vendored Executable file
View File

@@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
android/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#ffffff"/>
</shape>
</item>
<item>
<bitmap android:src="@drawable/logo" android:gravity="center"/>
</item>
</layer-list>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#ffffff"/>
</shape>
</item>
<item>
<bitmap android:src="@drawable/logo_land" android:gravity="center"/>
</item>
</layer-list>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#ffffff"/>
</shape>
</item>
<item>
<bitmap android:src="@drawable/logo_port" android:gravity="center"/>
</item>
</layer-list>

Some files were not shown because too many files have changed in this diff Show More