diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f9d3b0..bf38f94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,10 +15,6 @@ endif(CCACHE_FOUND) add_definitions(-DQT_GUI_LIB) add_subdirectory(3rdparty) -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Quick WebSockets LinguistTools) - -qt_standard_project_setup(REQUIRES 6.6 I18N_TRANSLATED_LANGUAGES de) - add_subdirectory(evcharger-app) if (NOT ANDROID) add_subdirectory(flotten-updater) diff --git a/evcharger-app/CMakeLists.txt b/evcharger-app/CMakeLists.txt index c76c8e6..2559e61 100644 --- a/evcharger-app/CMakeLists.txt +++ b/evcharger-app/CMakeLists.txt @@ -1,3 +1,7 @@ +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Quick WebSockets LinguistTools) + +qt_standard_project_setup(REQUIRES 6.6 I18N_TRANSLATED_LANGUAGES de) + qt_add_executable(evcharger-app WIN32 MACOSX_BUNDLE appsettings.cpp appsettings.h diff --git a/evcharger-app/i18n/qml_de.ts b/evcharger-app/i18n/qml_de.ts index 17c1ceb..16b55a3 100644 --- a/evcharger-app/i18n/qml_de.ts +++ b/evcharger-app/i18n/qml_de.ts @@ -576,92 +576,92 @@ ChargersModel - + Serial - + WS Status - + Status - + Variant - + IsGo - + IsAustralien - + ResetCard - + ConnectedWifi - + Project - + Version - + IDF Version - + Update - + Reboots - + Uptime - + Current Partition - + Car state - + Energy - + Livedata @@ -1687,64 +1687,64 @@ - + Yes - + No - + Select certificate... - + Could not open file! - + Please enter password - + Certificate password: - + Failed to load openssl legacy provider! - + Failed processing certificate! - + Possible reasons: openssl has a problem, the file is corrupt/invalid or the password is incorrect. - + The key seems to be invalid. - + The cert seems to be invalid. - + Could not change active backend @@ -1869,72 +1869,97 @@ - + Serial - + Not yet implemented! - + %0 selected - - Set update url... + + Add new column... - - Start update... + + Remove column %0 + Enter api key + + + + + Api key: + + + + + Set update url... + + + + + Start update... + + + + Reboot... - + Set chargectrl override... - + Set abitrary api key... - + + Open app(s)... + + + + Enter update url... - + Update url: - + Select update release... - + Update release - + Are you sure? Sind Sie sicher? - + Do you really want to reboot selected devices? diff --git a/flotten-updater/CMakeLists.txt b/flotten-updater/CMakeLists.txt index e8f8ce5..a15daf9 100644 --- a/flotten-updater/CMakeLists.txt +++ b/flotten-updater/CMakeLists.txt @@ -1,4 +1,8 @@ -add_executable(flotten-updater +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Quick WebSockets LinguistTools) + +qt_standard_project_setup(REQUIRES 6.6 I18N_TRANSLATED_LANGUAGES de) + +qt_add_executable(flotten-updater WIN32 MACOSX_BUNDLE chargerconnection.cpp chargerconnection.h chargersmodel.cpp @@ -22,6 +26,12 @@ add_executable(flotten-updater setarbitraryapikeydialog.ui ) +qt_add_resources(flotten-updater + PREFIX / + FILES + goe-root-ca.pem +) + find_package(OpenSSL) if (OpenSSL_Found) target_compile_definitions(flotten-updater HAS_OPENSSL) @@ -35,12 +45,13 @@ target_link_libraries(flotten-updater PUBLIC Qt6::Core Qt6::Gui Qt6::Widgets + Qt6::Quick Qt6::WebSockets goecommon ) -qt_add_resources(flotten-updater - PREFIX / - FILES - goe-root-ca.pem +install(TARGETS flotten-updater + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/flotten-updater/chargerconnection.cpp b/flotten-updater/chargerconnection.cpp index 8801ab6..6140dc4 100644 --- a/flotten-updater/chargerconnection.cpp +++ b/flotten-updater/chargerconnection.cpp @@ -346,6 +346,11 @@ QString ChargerConnection::livedataText() const return {}; } +QVariant ChargerConnection::getApiKey(const QString &apiKey) const +{ + return m_fullStatus.value(apiKey); +} + void ChargerConnection::sendMessage(const QJsonDocument &doc) { sendMessage(QString::fromUtf8(doc.toJson())); @@ -384,6 +389,7 @@ void ChargerConnection::init() connect(this, &ChargerConnection::carStateChanged, model, &ChargersModel::carStateChanged); connect(this, &ChargerConnection::energyChanged, model, &ChargersModel::energyChanged); connect(this, &ChargerConnection::livedataChanged, model, &ChargersModel::livedataChanged); + connect(this, &ChargerConnection::apiKeyChanged, model, &ChargersModel::apiKeyChanged); } else qWarning() << "unexpected parent"; @@ -396,7 +402,7 @@ void ChargerConnection::init() auto caCerts = QSslCertificate::fromPath(":/goe-root-ca.pem"); if (caCerts.empty()) qFatal("could not parse root ca"); - for (const auto &caCert : qAsConst(caCerts)) + for (const auto &caCert : std::as_const(caCerts)) qDebug() << caCert.issuerDisplayName(); sslConfig.setCaCertificates(std::move(caCerts)); } @@ -411,7 +417,7 @@ void ChargerConnection::init() connect(&m_websocket, &QWebSocket::stateChanged, this, &ChargerConnection::stateChanged); connect(&m_websocket, &QWebSocket::textMessageReceived, this, &ChargerConnection::textMessageReceived); connect(&m_websocket, &QWebSocket::binaryMessageReceived, this, &ChargerConnection::binaryMessageReceived); - connect(&m_websocket, qOverload(&QWebSocket::error), this, &ChargerConnection::error); + connect(&m_websocket, &QWebSocket::errorOccurred, this, &ChargerConnection::errorOccurred); connect(&m_websocket, &QWebSocket::peerVerifyError, this, &ChargerConnection::peerVerifyError); connect(&m_websocket, &QWebSocket::sslErrors, this, &ChargerConnection::sslErrors); connect(&m_websocket, &QWebSocket::alertReceived, this, &ChargerConnection::alertReceived); @@ -465,6 +471,8 @@ void ChargerConnection::maintainStatus(const QJsonObject &msg, bool forceChange) energyChanged = true; else if (iter.key() == "nrg") livedataChanged = true; + + emit apiKeyChanged(iter.key()); } if (variantChanged) @@ -611,7 +619,7 @@ void ChargerConnection::binaryMessageReceived(const QByteArray &message) qDebug() << "called" << message; } -void ChargerConnection::error(QAbstractSocket::SocketError error) +void ChargerConnection::errorOccurred(QAbstractSocket::SocketError error) { qDebug() << "called" << QMetaEnum::fromType().valueToKey(error) << m_websocket.errorString(); } diff --git a/flotten-updater/chargerconnection.h b/flotten-updater/chargerconnection.h index d211111..78de5a0 100644 --- a/flotten-updater/chargerconnection.h +++ b/flotten-updater/chargerconnection.h @@ -66,6 +66,8 @@ public: QString livedataText() const; + QVariant getApiKey(const QString &apiKey) const; + void sendMessage(const QJsonDocument &doc); void sendMessage(const QJsonObject &obj); void sendMessage(const QString &msg); @@ -90,6 +92,7 @@ signals: void carStateChanged(); void energyChanged(); void livedataChanged(); + void apiKeyChanged(const QString &apiKey); private: void init(); @@ -101,7 +104,7 @@ private slots: void stateChanged(QAbstractSocket::SocketState state); void textMessageReceived(const QString &message); void binaryMessageReceived(const QByteArray &message); - void error(QAbstractSocket::SocketError error); + void errorOccurred(QAbstractSocket::SocketError error); void peerVerifyError(const QSslError &error); void sslErrors(const QList &errors); void alertReceived(QSsl::AlertLevel level, QSsl::AlertType type, const QString &description); diff --git a/flotten-updater/chargersmodel.cpp b/flotten-updater/chargersmodel.cpp index 83206e7..10c6fc4 100644 --- a/flotten-updater/chargersmodel.cpp +++ b/flotten-updater/chargersmodel.cpp @@ -2,10 +2,12 @@ #include #include +#include #include #include "chargerconnection.h" +#include "flottenupdatersettings.h" namespace { enum { @@ -31,10 +33,13 @@ enum { }; } -ChargersModel::ChargersModel(const QSslKey &key, const QSslCertificate &cert, QObject *parent) : +ChargersModel::ChargersModel(FlottenUpdaterSettings &settings, const QSslKey &key, + const QSslCertificate &cert, QObject *parent) : QAbstractTableModel{parent}, + m_settings{settings}, m_key{key}, - m_cert{cert} + m_cert{cert}, + m_customColumns{settings.customColumns()} { constexpr const char *serials[] { "096850", "10000003", @@ -49,6 +54,7 @@ ChargersModel::ChargersModel(const QSslKey &key, const QSslCertificate &cert, QO "000043", "000044", "000047", "000050", "900001", "900103", "900104", "900105", "900107", "900108", "900113", "900117", "900118", "900123", "900126", "900127", "91028339", "91028457", "91028482", "91028368", "91028336", "91028374", "91028371", "91028452", "91028481", "91028455", "91028334", "91028456", "91028351", "91028367", "91028346", "91028459", "91028366", "91028335", "91028483", "91028372", "91028337", "91028338", "91008954", "91008978", "91008282", "91009008", "91008953", "91009000", "91009024", "91048840", "91048873", "91045590", "91045593", "91045586", "91048874", "91048879", "91048882", "91048878", "91048860", "91048865", "91048853", "91048864", "91048867", "91021261", "91021260", "91021379", "91021135", "91021266", "91021259", "91021374", "91021381", "91021258", "91021275", "91021380", "91021382", "91021377", "91021240", "91021371", "91021376", "91021255", "91021378", "91021383", "91021354", "91021195", "91021370", "91021278", "91021234", "91021256", "91021257", "91021360", "91021248", "91021363", "91021270", "91021267", "91021239", "91021193", "91021268", "91028339", "91028457", "91028482", "91028368", "91036167", "91028374", "91028371", "91028452", "91028481", "91028455", "91028334", "91028456", "91028351", "91028367", "91028346", "91028459", "91028366", "91028335", "91028483", "91028372", "91028337", "91028338", + "91100000", "91100001", "91100002", "91100003", "91100004", "91100005", "91100006", "91100007", "91100008", "91100009", "91100010", "91100011", "91100012", "91100013", "91100014", "91100015", "91100016", "91100017", "91100018", "91100019", "91100020", "91100021", "91100022", "91100023", "91100024", "91100025", "91100026", "91100027", "91100028", "91100029", "91100030", "91100031" }; for (const auto &serial : serials) { @@ -68,7 +74,7 @@ int ChargersModel::rowCount(const QModelIndex &parent) const int ChargersModel::columnCount(const QModelIndex &parent) const { - return NumberOfColumns; + return NumberOfColumns + m_customColumns.size(); } QVariant ChargersModel::data(const QModelIndex &index, int role) const @@ -248,6 +254,22 @@ QVariant ChargersModel::data(const QModelIndex &index, int role) const } return {}; } + + if (index.column() >= NumberOfColumns && index.column() - NumberOfColumns < m_customColumns.size()) + switch (role) + { + case Qt::DisplayRole: + { + auto variant = charger.getApiKey(m_customColumns.at(index.column() - NumberOfColumns)); + auto str = variant.toString(); + if (str.isEmpty()) + str = QJsonDocument::fromVariant(variant).toJson(QJsonDocument::Compact); + return str; + } + case Qt::EditRole: + return charger.getApiKey(m_customColumns.at(index.column() - NumberOfColumns)); + } + return {}; } @@ -280,8 +302,14 @@ QVariant ChargersModel::headerData(int section, Qt::Orientation orientation, int case ColumnEnergy: return tr("Energy"); case ColumnLivedata: return tr("Livedata"); } + + if (section >= NumberOfColumns && section - NumberOfColumns < m_customColumns.size()) + return m_customColumns[section - NumberOfColumns]; + return {}; } + + return {}; } void ChargersModel::addClient(const QString &serial) @@ -312,6 +340,30 @@ std::shared_ptr ChargersModel::getCharger(QModelIndex i return m_chargers.at(index.row()); } +void ChargersModel::addCustomColumn(const QString &apiKey) +{ + beginInsertColumns({}, NumberOfColumns + m_customColumns.size(), NumberOfColumns + m_customColumns.size()); + m_customColumns.push_back(apiKey); + endInsertColumns(); + m_settings.setCustomColumns(m_customColumns); +} + +bool ChargersModel::customColumnRemovable(int section) +{ + return section >= NumberOfColumns && section - NumberOfColumns < m_customColumns.size(); +} + +void ChargersModel::removeCustomColumn(int section) +{ + if (section < NumberOfColumns || section - NumberOfColumns >= m_customColumns.size()) + return; + + beginRemoveColumns({}, section, section); + m_customColumns.erase(std::next(std::begin(m_customColumns), section - NumberOfColumns)); + endRemoveColumns(); + m_settings.setCustomColumns(m_customColumns); +} + void ChargersModel::connectAll() { for (auto &charger : m_chargers) @@ -409,6 +461,13 @@ void ChargersModel::livedataChanged() columnChanged(ColumnLivedata, {Qt::DisplayRole, Qt::EditRole}); } +void ChargersModel::apiKeyChanged(const QString &apiKey) +{ + for (auto iter = std::cbegin(m_customColumns); iter != std::cend(m_customColumns); iter++) + if (*iter == apiKey) + columnChanged(NumberOfColumns + std::distance(std::cbegin(m_customColumns), iter), {Qt::DisplayRole, Qt::EditRole}); +} + void ChargersModel::columnChanged(int column, const QList &roles) { auto charger = qobject_cast(sender()); @@ -430,5 +489,5 @@ void ChargersModel::columnChanged(int column, const QList &roles) auto row = std::distance(std::cbegin(m_chargers), iter); auto index = createIndex(row, column); - dataChanged(index, index, roles); + emit dataChanged(index, index, roles); } diff --git a/flotten-updater/chargersmodel.h b/flotten-updater/chargersmodel.h index 0415277..30b447b 100644 --- a/flotten-updater/chargersmodel.h +++ b/flotten-updater/chargersmodel.h @@ -8,6 +8,7 @@ class QSslKey; class QSslCertificate; +class FlottenUpdaterSettings; class ChargerConnection; class ChargersModel : public QAbstractTableModel @@ -15,7 +16,8 @@ class ChargersModel : public QAbstractTableModel Q_OBJECT public: - explicit ChargersModel(const QSslKey &key, const QSslCertificate &cert, QObject *parent = nullptr); + explicit ChargersModel(FlottenUpdaterSettings &settings, const QSslKey &key, + const QSslCertificate &cert, QObject *parent = nullptr); ~ChargersModel() override; int rowCount(const QModelIndex &parent) const override; @@ -28,6 +30,10 @@ public: std::shared_ptr getCharger(QModelIndex index); std::shared_ptr getCharger(QModelIndex index) const; + void addCustomColumn(const QString &apiKey); + bool customColumnRemovable(int section); + void removeCustomColumn(int section); + public slots: void connectAll(); void disconnectAll(); @@ -49,11 +55,15 @@ public slots: void carStateChanged(); void energyChanged(); void livedataChanged(); + void apiKeyChanged(const QString &apiKey); private: + FlottenUpdaterSettings &m_settings; const QSslKey &m_key; const QSslCertificate &m_cert; void columnChanged(int column, const QList &roles = QList()); std::vector> m_chargers; + + QStringList m_customColumns; }; diff --git a/flotten-updater/flottenupdatersettings.cpp b/flotten-updater/flottenupdatersettings.cpp index 4c7985a..c1ff6b6 100644 --- a/flotten-updater/flottenupdatersettings.cpp +++ b/flotten-updater/flottenupdatersettings.cpp @@ -19,3 +19,13 @@ void FlottenUpdaterSettings::setPrivateCert(const QByteArray &cert) { setValue("privateCert", cert); } + +QStringList FlottenUpdaterSettings::customColumns() const +{ + return value("customColumns").toStringList(); +} + +void FlottenUpdaterSettings::setCustomColumns(const QStringList &customColumns) +{ + setValue("customColumns", customColumns); +} diff --git a/flotten-updater/flottenupdatersettings.h b/flotten-updater/flottenupdatersettings.h index f344e2a..89c7fc8 100644 --- a/flotten-updater/flottenupdatersettings.h +++ b/flotten-updater/flottenupdatersettings.h @@ -14,4 +14,7 @@ public: QByteArray privateCert() const; void setPrivateCert(const QByteArray &cert); + + QStringList customColumns() const; + void setCustomColumns(const QStringList &customColumns); }; diff --git a/flotten-updater/mainwindow.cpp b/flotten-updater/mainwindow.cpp index 8858e5d..c023dd3 100644 --- a/flotten-updater/mainwindow.cpp +++ b/flotten-updater/mainwindow.cpp @@ -21,7 +21,7 @@ MainWindow::MainWindow(FlottenUpdaterSettings &settings, const QSslKey &key, QMainWindow{parent}, m_ui{std::make_unique()}, m_settings{settings}, - m_model{std::make_unique(key, cert, this)}, + m_model{std::make_unique(settings, key, cert, this)}, m_proxyModel{std::make_unique(this)} { m_ui->setupUi(this); @@ -35,6 +35,8 @@ MainWindow::MainWindow(FlottenUpdaterSettings &settings, const QSslKey &key, connect(m_ui->pushButtonAdd, &QAbstractButton::pressed, this, &MainWindow::doAdd); connect(m_ui->pushButtonRemove, &QAbstractButton::pressed, this, &MainWindow::doRemove); connect(m_ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::selectionChanged); + m_ui->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui->treeView->header(), &QTreeView::customContextMenuRequested, this, &MainWindow::headerContextMenuRequested); connect(m_ui->treeView, &QTreeView::customContextMenuRequested, this, &MainWindow::contextMenuRequested); } @@ -60,6 +62,37 @@ void MainWindow::selectionChanged() m_ui->statusbar->showMessage(tr("%0 selected").arg(m_ui->treeView->selectionModel()->selectedRows().count())); } +void MainWindow::headerContextMenuRequested(const QPoint &pos) +{ + QMenu menu; + + auto addColumnAction = menu.addAction(tr("Add new column...")); + + QAction *removeColumnAction{}; + auto header = m_ui->treeView->header(); + const auto section = header->logicalIndexAt(pos); + if (section >= 0) + { + removeColumnAction = menu.addAction(tr("Remove column %0").arg(header->model()->headerData(section, Qt::Horizontal).toString())); + if (!m_model->customColumnRemovable(section)) + removeColumnAction->setEnabled(false); + } + + if (auto selectedAction = menu.exec(header->mapToGlobal(pos)); selectedAction == addColumnAction) + { + bool ok{}; + auto apiKey = QInputDialog::getText(this, tr("Enter api key"), tr("Api key:"), QLineEdit::Normal, {}, &ok); + if (!ok) + return; + + m_model->addCustomColumn(apiKey); + } + else if (removeColumnAction && selectedAction == removeColumnAction) + { + m_model->removeCustomColumn(section); + } +} + void MainWindow::contextMenuRequested(const QPoint &pos) { auto selectedRows = m_ui->treeView->selectionModel()->selectedRows(); @@ -84,6 +117,7 @@ void MainWindow::contextMenuRequested(const QPoint &pos) auto actionReboot = menu.addAction(tr("Reboot...")); auto actionSetChargectrlOverride = menu.addAction(tr("Set chargectrl override...")); auto actionSetAbitraryApiKey = menu.addAction(tr("Set abitrary api key...")); + auto actionOpenApps = menu.addAction(tr("Open app(s)...")); if (const auto selected = menu.exec(m_ui->treeView->viewport()->mapToGlobal(pos)); selected == actionSetUpdateUrl) { bool ok{}; @@ -186,4 +220,8 @@ void MainWindow::contextMenuRequested(const QPoint &pos) RequestDialog{std::move(msg), std::move(chargers), this}.exec(); } } + else if (selected == actionOpenApps) + { + + } } diff --git a/flotten-updater/mainwindow.h b/flotten-updater/mainwindow.h index e75a2a1..0798564 100644 --- a/flotten-updater/mainwindow.h +++ b/flotten-updater/mainwindow.h @@ -25,6 +25,7 @@ private slots: void doAdd(); void doRemove(); void selectionChanged(); + void headerContextMenuRequested(const QPoint &pos); void contextMenuRequested(const QPoint &pos); private: diff --git a/goecommon/CMakeLists.txt b/goecommon/CMakeLists.txt index 3095b6a..b27c584 100644 --- a/goecommon/CMakeLists.txt +++ b/goecommon/CMakeLists.txt @@ -1,10 +1,16 @@ -add_library(goecommon +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Quick WebSockets LinguistTools) + +qt_standard_project_setup(REQUIRES 6.6 I18N_TRANSLATED_LANGUAGES de) + +qt_add_library(goecommon goesettings.cpp goesettings.h ) target_link_libraries(goecommon Qt6::Core + Qt6::Gui + Qt6::Widgets ) target_include_directories(goecommon PUBLIC