diff --git a/experiences/evdash/evdash.pro b/experiences/evdash/evdash.pro
new file mode 100644
index 00000000..05d898d5
--- /dev/null
+++ b/experiences/evdash/evdash.pro
@@ -0,0 +1,29 @@
+TEMPLATE = lib
+CONFIG += staticlib
+TARGET = nymea-app-evdash
+
+QT -= gui
+QT += network websockets quick
+
+include(../../shared.pri)
+
+LIBS += -L$${top_builddir}/libnymea-app/ -lnymea-app
+INCLUDEPATH += $${top_srcdir}/libnymea-app/
+
+android: {
+ LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH}
+ PRE_TARGETDEPS += $$top_builddir/libnymea-app/$${ANDROID_TARGET_ARCH}/libnymea-app_$${ANDROID_TARGET_ARCH}.a
+}
+
+HEADERS += \
+ evdashmanager.h \
+ evdashusers.h \
+ libnymea-app-evdash.h
+
+SOURCES += \
+ evdashmanager.cpp \
+ evdashusers.cpp
+
+android: {
+ DESTDIR = $${ANDROID_TARGET_ARCH}
+}
diff --git a/experiences/evdash/evdashmanager.cpp b/experiences/evdash/evdashmanager.cpp
new file mode 100644
index 00000000..cb960f2b
--- /dev/null
+++ b/experiences/evdash/evdashmanager.cpp
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright (C) 2013 - 2024, nymea GmbH
+* Copyright (C) 2024 - 2025, chargebyte austria GmbH
+*
+* This file is part of nymea-app.
+*
+* nymea-app is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* nymea-app is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with nymea-app. If not, see .
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "evdashmanager.h"
+
+#include
+
+#include
+
+NYMEA_LOGGING_CATEGORY(dcEvDashExperience, "EvDashExperience")
+
+EvDashManager::EvDashManager(QObject *parent)
+ :QObject{parent},
+ m_users{new EvDashUsers(this)}
+{
+
+}
+
+EvDashManager::~EvDashManager()
+{
+ if (m_engine) {
+ m_engine->jsonRpcClient()->unregisterNotificationHandler(this);
+ }
+}
+
+EvDashUsers *EvDashManager::users() const
+{
+ return m_users;
+}
+
+Engine *EvDashManager::engine() const
+{
+ return m_engine;
+}
+
+void EvDashManager::setEngine(Engine *engine)
+{
+ if (m_engine == engine)
+ return;
+
+ if (m_engine)
+ m_engine->jsonRpcClient()->unregisterNotificationHandler(this);
+
+ m_engine = engine;
+ emit engineChanged();
+
+ if (m_engine) {
+ connect(engine, &Engine::destroyed, this, [engine, this]{ if (m_engine == engine) m_engine = nullptr; });
+
+ m_engine->jsonRpcClient()->registerNotificationHandler(this, "EvDash", "notificationReceived");
+ m_engine->jsonRpcClient()->sendCommand("EvDash.GetEnabled", QVariantMap(), this, "getEnabledResponse");
+ m_engine->jsonRpcClient()->sendCommand("EvDash.GetUsers", QVariantMap(), this, "getUsersResponse");
+ }
+}
+
+bool EvDashManager::enabled() const
+{
+ return m_enabled;
+}
+
+int EvDashManager::setEnabled(bool enabled)
+{
+ QVariantMap params;
+ params.insert("enabled", enabled);
+ return m_engine->jsonRpcClient()->sendCommand("EvDash.SetEnabled", params, this, "setEnabledResponse");
+}
+
+int EvDashManager::addUser(const QString &username, const QString &password)
+{
+ QVariantMap params;
+ params.insert("username", username);
+ params.insert("password", password);
+ return m_engine->jsonRpcClient()->sendCommand("EvDash.AddUser", params, this, "addUserResponse");
+}
+
+int EvDashManager::removeUser(const QString &username)
+{
+ QVariantMap params;
+ params.insert("username", username);
+ return m_engine->jsonRpcClient()->sendCommand("EvDash.RemoveUser", params, this, "removeUserResponse");
+}
+
+void EvDashManager::notificationReceived(const QVariantMap &data)
+{
+ QString notification = data.value("notification").toString();
+ QVariantMap params = data.value("params").toMap();
+
+ if (notification == "EvDash.EnabledChanged") {
+ bool enabled = params.value("enabled").toBool();
+ if (m_enabled != enabled) {
+ m_enabled = enabled;
+ emit enabledChanged();
+ }
+ } else if (notification == "EvDash.UserAdded") {
+ m_users->addUser(params.value("username").toString());
+ } else if (notification == "EvDash.UserRemoved") {
+ m_users->removeUser(params.value("username").toString());
+ } else {
+ qCDebug(dcEvDashExperience()) << "Unhandled notification received" << data;
+ }
+}
+
+void EvDashManager::getEnabledResponse(int commandId, const QVariantMap ¶ms)
+{
+ Q_UNUSED(commandId)
+ qCDebug(dcEvDashExperience()) << "Response for GetEnabled request" << commandId << params;
+
+ bool enabled = params.value("enabled").toBool();
+ if (m_enabled != enabled) {
+ m_enabled = enabled;
+ emit enabledChanged();
+ }
+}
+
+void EvDashManager::setEnabledResponse(int commandId, const QVariantMap ¶ms)
+{
+ qCDebug(dcEvDashExperience()) << "Response for SetEnabled request" << commandId << params;
+ QMetaEnum metaEnum = QMetaEnum::fromType();
+ EvDashError error = static_cast(metaEnum.keyToValue(params.value("evDashError").toByteArray().data()));
+ emit setEnabledReply(commandId, error);
+}
+
+void EvDashManager::getUsersResponse(int commandId, const QVariantMap ¶ms)
+{
+ Q_UNUSED(commandId)
+ qCDebug(dcEvDashExperience()) << "Response for GetEnabled request" << commandId << params;
+ m_users->setUsers(params.value("usernames").toStringList());
+}
+
+void EvDashManager::addUserResponse(int commandId, const QVariantMap ¶ms)
+{
+ qCDebug(dcEvDashExperience()) << "Response for AddUser request" << commandId << params;
+ QMetaEnum metaEnum = QMetaEnum::fromType();
+ EvDashError error = static_cast(metaEnum.keyToValue(params.value("evDashError").toByteArray().data()));
+ emit addUserReply(commandId, error);
+}
+
+void EvDashManager::removeUserResponse(int commandId, const QVariantMap ¶ms)
+{
+ qCDebug(dcEvDashExperience()) << "Response for RemoveUser request" << commandId << params;
+ QMetaEnum metaEnum = QMetaEnum::fromType();
+ EvDashError error = static_cast(metaEnum.keyToValue(params.value("evDashError").toByteArray().data()));
+ emit removeUserReply(commandId, error);
+}
+
+
diff --git a/experiences/evdash/evdashmanager.h b/experiences/evdash/evdashmanager.h
new file mode 100644
index 00000000..01ba0332
--- /dev/null
+++ b/experiences/evdash/evdashmanager.h
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright (C) 2013 - 2024, nymea GmbH
+* Copyright (C) 2024 - 2025, chargebyte austria GmbH
+*
+* This file is part of nymea-app.
+*
+* nymea-app is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* nymea-app is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with nymea-app. If not, see .
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef EVDASHMANAGER_H
+#define EVDASHMANAGER_H
+
+#include
+#include
+
+#include "evdashusers.h"
+
+class EvDashManager : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL)
+ Q_PROPERTY(EvDashUsers *users READ users CONSTANT FINAL)
+
+public:
+ enum EvDashError {
+ EvDashErrorNoError = 0,
+ EvDashErrorBackendError,
+ EvDashErrorDuplicateUser,
+ EvDashErrorUserNotFound,
+ EvDashErrorBadPassword
+ };
+ Q_ENUM(EvDashError)
+
+ explicit EvDashManager(QObject *parent = nullptr);
+ ~EvDashManager();
+
+ EvDashUsers *users() const;
+
+ Engine* engine() const;
+ void setEngine(Engine *engine);
+
+ bool enabled() const;
+ int setEnabled(bool enabled);
+
+ Q_INVOKABLE int addUser(const QString &username, const QString &password);
+ Q_INVOKABLE int removeUser(const QString &username);
+
+signals:
+ void engineChanged();
+ void enabledChanged();
+
+ void setEnabledReply(int commandId, EvDashManager::EvDashError error);
+ void addUserReply(int commandId, EvDashManager::EvDashError error);
+ void removeUserReply(int commandId, EvDashManager::EvDashError error);
+
+private slots:
+ void notificationReceived(const QVariantMap &data);
+
+ void getEnabledResponse(int commandId, const QVariantMap ¶ms);
+ void setEnabledResponse(int commandId, const QVariantMap ¶ms);
+
+ void getUsersResponse(int commandId, const QVariantMap ¶ms);
+ void addUserResponse(int commandId, const QVariantMap ¶ms);
+ void removeUserResponse(int commandId, const QVariantMap ¶ms);
+
+private:
+ Engine *m_engine = nullptr;
+ bool m_enabled = false;
+ EvDashUsers *m_users = nullptr;
+
+};
+
+#endif // EVDASHMANAGER_H
diff --git a/experiences/evdash/evdashusers.cpp b/experiences/evdash/evdashusers.cpp
new file mode 100644
index 00000000..e3492804
--- /dev/null
+++ b/experiences/evdash/evdashusers.cpp
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright (C) 2013 - 2024, nymea GmbH
+* Copyright (C) 2024 - 2025, chargebyte austria GmbH
+*
+* This file is part of nymea-app.
+*
+* nymea-app is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* nymea-app is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with nymea-app. If not, see .
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "evdashusers.h"
+
+#include
+
+namespace {
+inline bool localeLess(const QString &lhs, const QString &rhs)
+{
+ return QString::localeAwareCompare(lhs, rhs) < 0;
+}
+}
+
+EvDashUsers::EvDashUsers(QObject *parent)
+ : QAbstractListModel(parent)
+{
+
+}
+
+EvDashUsers::EvDashUsers(const QStringList &data, QObject *parent)
+ : QAbstractListModel(parent), m_data(data)
+{
+
+}
+
+int EvDashUsers::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent)
+ return m_data.size();
+}
+
+QVariant EvDashUsers::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() >= m_data.size())
+ return QVariant();
+
+ if (role == Qt::DisplayRole || role == NameRole)
+ return m_data.at(index.row());
+
+ return QVariant();
+}
+
+QHash EvDashUsers::roleNames() const
+{
+ QHash roles;
+ roles[NameRole] = "name";
+ return roles;
+}
+
+void EvDashUsers::setUsers(const QStringList &users)
+{
+ QStringList sortedUsers = users;
+ sortedUsers.removeDuplicates();
+ std::sort(sortedUsers.begin(), sortedUsers.end(), localeLess);
+
+ if (sortedUsers == m_data)
+ return;
+
+ beginResetModel();
+ m_data = sortedUsers;
+ endResetModel();
+}
+
+void EvDashUsers::addUser(const QString &user)
+{
+ if (user.isEmpty() || m_data.contains(user))
+ return;
+
+ const auto insertIt = std::lower_bound(m_data.begin(), m_data.end(), user, localeLess);
+ const int insertIndex = std::distance(m_data.begin(), insertIt);
+
+ beginInsertRows(QModelIndex(), insertIndex, insertIndex);
+ m_data.insert(insertIndex, user);
+ endInsertRows();
+}
+
+void EvDashUsers::removeUser(const QString &user)
+{
+ int index = m_data.indexOf(user);
+ if (index < 0)
+ return;
+
+ beginRemoveRows(QModelIndex(), index, index);
+ m_data.removeAt(index);
+ endRemoveRows();
+}
diff --git a/experiences/evdash/evdashusers.h b/experiences/evdash/evdashusers.h
new file mode 100644
index 00000000..991294fe
--- /dev/null
+++ b/experiences/evdash/evdashusers.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright (C) 2013 - 2024, nymea GmbH
+* Copyright (C) 2024 - 2025, chargebyte austria GmbH
+*
+* This file is part of nymea-app.
+*
+* nymea-app is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* nymea-app is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with nymea-app. If not, see .
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef EVDASHUSERS_H
+#define EVDASHUSERS_H
+
+#include
+#include
+#include
+
+class EvDashUsers : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ enum Roles { DisplayRole = Qt::UserRole + 1, NameRole };
+
+ explicit EvDashUsers(QObject *parent = nullptr);
+ explicit EvDashUsers(const QStringList &data, QObject *parent = nullptr);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash roleNames() const override;
+
+ void setUsers(const QStringList &users);
+ void addUser(const QString &username);
+ void removeUser(const QString &username);
+
+private:
+ QStringList m_data;
+
+};
+
+
+#endif // EVDASHUSERS_H
diff --git a/experiences/evdash/libnymea-app-evdash.h b/experiences/evdash/libnymea-app-evdash.h
new file mode 100644
index 00000000..040cf3e0
--- /dev/null
+++ b/experiences/evdash/libnymea-app-evdash.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright (C) 2013 - 2024, nymea GmbH
+* Copyright (C) 2024 - 2025, chargebyte austria GmbH
+*
+* This file is part of nymea-app.
+*
+* nymea-app is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* nymea-app is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with nymea-app. If not, see .
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef LIBNYMEA_APP_EVDASH_H
+#define LIBNYMEA_APP_EVDASH_H
+
+#include "evdashmanager.h"
+#include "evdashusers.h"
+
+#include
+
+namespace Nymea {
+
+namespace EvDash {
+
+void registerQmlTypes() {
+ qmlRegisterType("Nymea.EvDash", 1, 0, "EvDashManager");
+ qmlRegisterUncreatableType("Nymea.EvDash", 1, 0, "EvDashUsers", "Get if from the EvDash Manager");
+}
+
+}
+
+}
+
+#endif // LIBNYMEA_APP_EVDASH_H
diff --git a/experiences/experiences.pro b/experiences/experiences.pro
index 298fcbc4..794d26d4 100644
--- a/experiences/experiences.pro
+++ b/experiences/experiences.pro
@@ -1,3 +1,3 @@
TEMPLATE = subdirs
-SUBDIRS += airconditioning
+SUBDIRS += airconditioning evdash
diff --git a/libnymea-app/jsonrpc/jsonrpcclient.cpp b/libnymea-app/jsonrpc/jsonrpcclient.cpp
index 76e15985..0687bd0c 100644
--- a/libnymea-app/jsonrpc/jsonrpcclient.cpp
+++ b/libnymea-app/jsonrpc/jsonrpcclient.cpp
@@ -668,7 +668,10 @@ void JsonRpcClient::helloReply(int /*commandId*/, const QVariantMap ¶ms)
QString name = params.value("name").toString();
m_experiences.clear();
foreach (const QVariant &experience, params.value("experiences").toList()) {
- m_experiences.insert(experience.toMap().value("name").toString(), experience.toMap().value("version").toString());
+ QString experienceName = experience.toMap().value("name").toString();
+ QString experienceVersion = experience.toMap().value("version").toString();
+ m_experiences.insert(experienceName, experienceVersion);
+ qCInfo(dcJsonRpc()) << "Experience available:" << experienceName << experienceVersion;
}
QString protoVersionString = params.value("protocol version").toString();
diff --git a/libnymea-app/usermanager.cpp b/libnymea-app/usermanager.cpp
index 828cdfbe..c9cab670 100644
--- a/libnymea-app/usermanager.cpp
+++ b/libnymea-app/usermanager.cpp
@@ -163,7 +163,7 @@ int UserManager::setUserScopes(const QString &username, int scopes, const QList<
if (m_engine->jsonRpcClient()->ensureServerVersion("8.4")) {
QVariantList thingIds;
foreach (const QUuid &thingId, allowedThingIds)
- thingIds.append(thingId);
+ thingIds.append(thingId.toString());
params.insert("allowedThingIds", thingIds);
}
diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp
index 1acb1832..740bf417 100644
--- a/nymea-app/main.cpp
+++ b/nymea-app/main.cpp
@@ -48,6 +48,7 @@
#include "libnymea-app-core.h"
#include "libnymea-app-airconditioning.h"
+#include "libnymea-app-evdash.h"
#include "stylecontroller.h"
#include "pushnotifications.h"
@@ -145,6 +146,7 @@ int main(int argc, char *argv[])
Nymea::Core::registerQmlTypes();
Nymea::AirConditioning::registerQmlTypes();
+ Nymea::EvDash::registerQmlTypes();
QQmlApplicationEngine *engine = new QQmlApplicationEngine();
diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro
index a16746fa..fa60ddf3 100644
--- a/nymea-app/nymea-app.pro
+++ b/nymea-app/nymea-app.pro
@@ -13,25 +13,29 @@ qtHaveModule(webview) {
}
INCLUDEPATH += $$top_srcdir/libnymea-app \
- $$top_srcdir/experiences/airconditioning
+ $$top_srcdir/experiences/airconditioning \
+ $$top_srcdir/experiences/evdash
+
+linux:!android:LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app \
+ -L$$top_builddir/experiences/airconditioning -lnymea-app-airconditioning \
+ -L$$top_builddir/experiences/evdash -lnymea-app-evdash
-linux:!android: LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app -L$$top_builddir/experiences/airconditioning -lnymea-app-airconditioning
win32:Debug:LIBS += -L$$top_builddir/libnymea-app/debug -lnymea-app \
- -L$$top_builddir/experiences/airconditioning/debug -lnymea-app-airconditioning
-win32:Release:LIBS += -L$$top_builddir/libnymea-app/release -lnymea-app \
- -L$$top_builddir/experiences/airconditioning/release -lnymea-app-airconditioning
+ -L$$top_builddir/experiences/airconditioning/debug -lnymea-app-airconditioning \
+ -L$$top_builddir/experiences/evdash/debug -lnymea-app-evdash
+
+win32:Release:LIBS += -L$$top_builddir/libnymea-app/release -lnymea-app \
+ -L$$top_builddir/experiences/airconditioning/release -lnymea-app-airconditioning \
+ -L$$top_builddir/experiences/evdash/release -lnymea-app-evdash
-win32:Debug:PRE_TARGETDEPS += $$top_builddir/libnymea-app/debug/nymea-app.lib \
- $$top_builddir/experiences/airconditioning/debug/nymea-app-airconditioning.lib
-win32:Release:PRE_TARGETDEPS += $$top_builddir/libnymea-app/release/nymea-app.lib \
- $$top_builddir/experiences/airconditioning/release/nymea-app-airconditioning.lib
win32:CXX_FLAGS += /w
linux:!android:!nozeroconf:LIBS += -lavahi-client -lavahi-common
linux:!android:PRE_TARGETDEPS += $$top_builddir/libnymea-app/libnymea-app.a \
- $$top_builddir/experiences/airconditioning/libnymea-app-airconditioning.a
+ $$top_builddir/experiences/airconditioning/libnymea-app-airconditioning.a \
+ $$top_builddir/experiences/evdash/libnymea-app-evdash.a
HEADERS += \
configuredhostsmodel.h \
@@ -94,7 +98,7 @@ android {
include(../3rdParty/android/android_openssl/openssl.pri)
ANDROID_MIN_SDK_VERSION = 21
- ANDROID_TARGET_SDK_VERSION = 35
+ ANDROID_TARGET_SDK_VERSION = 36
HEADERS += platformintegration/android/platformhelperandroid.h \
platformintegration/android/platformpermissionsandroid.h \
@@ -104,9 +108,11 @@ android {
CORE_LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH}
AIRCONDITIONING_LIBS += -L$${top_builddir}/experiences/airconditioning/$${ANDROID_TARGET_ARCH}
+ EVDASH_LIBS += -L$${top_builddir}/experiences/evdash/$${ANDROID_TARGET_ARCH}
LIBS += $${CORE_LIBS} -lnymea-app_$${ANDROID_TARGET_ARCH} \
- $${AIRCONDITIONING_LIBS} -lnymea-app-airconditioning_$${ANDROID_TARGET_ARCH}
+ $${AIRCONDITIONING_LIBS} -lnymea-app-airconditioning_$${ANDROID_TARGET_ARCH} \
+ $${EVDASH_LIBS} -lnymea-app-evdash_$${ANDROID_TARGET_ARCH}
versioninfo.files = ../version.txt
versioninfo.path = /
diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc
index 4a7c56a7..7900a598 100644
--- a/nymea-app/resources.qrc
+++ b/nymea-app/resources.qrc
@@ -325,5 +325,6 @@
ui/shaders/coloricon.frag.qsb
ui/shaders/brightnesscircle.frag.qsb
ui/shaders/colorizedimage.frag.qsb
+ ui/system/EvDashSettingsPage.qml
diff --git a/nymea-app/ui/SettingsPage.qml b/nymea-app/ui/SettingsPage.qml
index 5f113aa8..c5489b13 100644
--- a/nymea-app/ui/SettingsPage.qml
+++ b/nymea-app/ui/SettingsPage.qml
@@ -143,6 +143,17 @@ Page {
onClicked:pageStack.push(Qt.resolvedUrl("system/PluginsPage.qml"))
}
+
+ SettingsTile {
+ Layout.fillWidth: true
+ iconSource: "qrc:/icons/dashboard.svg"
+ text: qsTr("EV Dash")
+ subText: qsTr("Dashboard settings")
+ visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) &&
+ (engine.jsonRpcClient.experiences.hasOwnProperty("EvDash"))
+ onClicked:pageStack.push(Qt.resolvedUrl("system/EvDashSettingsPage.qml"))
+ }
+
SettingsTile {
Layout.fillWidth: true
iconSource: "qrc:/icons/sdk.svg"
diff --git a/nymea-app/ui/system/EvDashSettingsPage.qml b/nymea-app/ui/system/EvDashSettingsPage.qml
new file mode 100644
index 00000000..af8b67a2
--- /dev/null
+++ b/nymea-app/ui/system/EvDashSettingsPage.qml
@@ -0,0 +1,203 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2025, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free Software
+* Foundation, GNU version 3. This project is distributed in the hope that it
+* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+* Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along with
+* this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+import Nymea 1.0
+import Nymea.EvDash 1.0
+
+import "../components"
+
+SettingsPageBase {
+ id: root
+
+ header: NymeaHeader {
+ text: qsTr("EV Dash")
+ onBackPressed: pageStack.pop()
+
+ HeaderButton {
+ imageSource: Qt.resolvedUrl("qrc:/icons/add.svg")
+ onClicked: {
+ var popup = addUserPopup.createObject(app);
+ popup.open();
+ }
+ }
+ }
+
+ EvDashManager {
+ id: evDashManager
+ engine: _engine
+ }
+
+ Component {
+ id: errorDialog
+ ErrorDialog {}
+ }
+
+
+ Component {
+ id: removeUserPopup
+ NymeaDialog {
+ id: removeUserDialog
+
+ property string username
+
+ headerIcon: "qrc:/icons/dialog-warning-symbolic.svg"
+ title: qsTr("Remove user")
+ text: qsTr("Are you sure you want to remove \"%1\"?").arg(username)
+
+ onAccepted: {
+ evDashManager.removeUser(username);
+ popup.close();
+ }
+ }
+ }
+
+ Connections {
+ target: evDashManager
+ onAddUserReply: {
+ if (error === EvDashManager.EvDashErrorNoError)
+ return
+
+ var text;
+ switch (error) {
+ case EvDashManager.EvDashErrorDuplicateUser:
+ text = qsTr("The given username is already in use. Please choose a different username.");
+ break;
+ case EvDashManager.EvDashErrorBadPassword:
+ text = qsTr("The given password is not valid.");
+ break;
+ default:
+ text = qsTr("Un unexpected error happened when creating the user. We're sorry for this. (Error code: %1)").arg(error);
+ break;
+ }
+
+ var popup = errorDialog.createObject(app, {text: text});
+ popup.open()
+ }
+
+ onRemoveUserReply: {
+ if (error === EvDashManager.EvDashErrorNoError)
+ return
+
+ var text;
+ switch (error) {
+ case EvDashManager.EvDashErrorDuplicateUser:
+ text = qsTr("The given username is already in use. Please choose a different username.");
+ break;
+ case EvDashManager.EvDashErrorBadPassword:
+ text = qsTr("The given password is not valid.");
+ break;
+ default:
+ text = qsTr("Un unexpected error happened when creating the user. We're sorry for this. (Error code: %1)").arg(error);
+ break;
+ }
+
+ var popup = errorDialog.createObject(app, {text: text});
+ popup.open()
+ }
+ }
+
+ Component {
+ id: addUserPopup
+
+ NymeaDialog {
+ id: addUserDialog
+
+ title: qsTr("Create new user")
+ standardButtons: Dialog.NoButton
+
+ Label { text: qsTr("Username") }
+
+ NymeaTextField {
+ id: usernameTextField
+ Layout.fillWidth: true
+ }
+
+ Label { text: qsTr("Password") }
+
+ PasswordTextField {
+ id: passwordTextField
+
+ Layout.fillWidth: true
+ minPasswordLength: 4
+ requireSpecialChar: false
+ requireNumber: false
+ requireUpperCaseLetter: false
+ requireLowerCaseLetter: false
+ }
+
+ Button {
+ Layout.fillWidth: true
+ text: qsTr("Add user")
+ onClicked: {
+ evDashManager.addUser(usernameTextField.text, passwordTextField.password)
+ addUserDialog.close()
+ }
+ }
+
+ Button {
+ Layout.fillWidth: true
+ text: qsTr("Cancel")
+ onClicked: {
+ addUserDialog.close()
+ }
+ }
+ }
+ }
+
+
+ SwitchDelegate {
+ text: qsTr("Dashboard enabled")
+ checked: evDashManager.enabled
+ onCheckedChanged: evDashManager.enabled = checked
+ Layout.fillWidth: true
+ }
+
+ SettingsPageSectionHeader {
+ text: qsTr("Manage users")
+ }
+
+ Repeater {
+ id: usersList
+ model: evDashManager.users
+
+ delegate: NymeaItemDelegate {
+ Layout.fillWidth: true
+ text: model.name
+ onClicked: {
+ var popup = removeUserPopup.createObject(app, {username: model.name});
+ popup.open()
+ }
+ }
+ }
+}