Remove flotten-updater

This commit is contained in:
2025-11-06 16:07:19 +01:00
parent 587d61ce55
commit 217007154c
27 changed files with 4 additions and 3046 deletions
+4 -7
View File
@@ -1,10 +1,10 @@
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)
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)
@@ -16,7 +16,4 @@ add_definitions(-DQT_GUI_LIB)
add_subdirectory(3rdparty)
add_subdirectory(evcharger-app)
if (NOT ANDROID)
add_subdirectory(flotten-updater)
endif()
add_subdirectory(goecommon)
-60
View File
@@ -1,60 +0,0 @@
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
addserialsrangedialog.h
addserialsrangedialog.cpp
addserialsrangedialog.ui
deviceconnection.cpp
deviceconnection.h
devicesmodel.cpp
devicesmodel.h
flottenupdatersettings.cpp
flottenupdatersettings.h
importcredentialsdialog.cpp
importcredentialsdialog.h
importcredentialsdialog.ui
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
requestdialog.cpp
requestdialog.h
requestdialog.ui
requestmodel.cpp
requestmodel.h
setarbitraryapikeydialog.cpp
setarbitraryapikeydialog.h
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)
target_link_libraries(flotten-updater PUBLIC
OpenSSL::SSL
OpenSSL::Crypto
)
endif()
target_link_libraries(flotten-updater PUBLIC
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Quick
Qt6::WebSockets
goecommon
)
install(TARGETS flotten-updater
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
-61
View File
@@ -1,61 +0,0 @@
#include "addserialsrangedialog.h"
#include "ui_addserialsrangedialog.h"
AddSerialsRangeDialog::AddSerialsRangeDialog(QWidget *parent) :
QDialog{parent},
m_ui{std::make_unique<Ui::AddSerialsRangeDialog>()}
{
m_ui->setupUi(this);
connect(m_ui->spinBoxFrom, &QSpinBox::valueChanged,
this, &AddSerialsRangeDialog::updatePreview);
connect(m_ui->spinBoxTo, &QSpinBox::valueChanged,
this, &AddSerialsRangeDialog::updatePreview);
updatePreview();
}
AddSerialsRangeDialog::~AddSerialsRangeDialog() = default;
void AddSerialsRangeDialog::accept()
{
if (from() > to())
return;
QDialog::accept();
}
int AddSerialsRangeDialog::from() const
{
return m_ui->spinBoxFrom->value();
}
int AddSerialsRangeDialog::to() const
{
return m_ui->spinBoxTo->value();
}
void AddSerialsRangeDialog::updatePreview()
{
const auto from = this->from();
const auto to = this->to();
QString text;
QColor color;
if (from > to)
{
text = tr("???");
color = Qt::red;
}
else
{
const auto count = to - from + 1;
text = tr("%0 serials").arg(count);
color = count > 100 ? Qt::yellow : Qt::green;
}
m_ui->labelCountDisplay->setText(text);
QPalette palette = m_ui->labelCountDisplay->palette();
palette.setColor(m_ui->labelCountDisplay->backgroundRole(), color);
palette.setColor(m_ui->labelCountDisplay->foregroundRole(), color);
m_ui->labelCountDisplay->setPalette(palette);
}
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include <QDialog>
#include <memory>
namespace Ui { class AddSerialsRangeDialog; }
class AddSerialsRangeDialog : public QDialog
{
Q_OBJECT
public:
explicit AddSerialsRangeDialog(QWidget *parent = nullptr);
~AddSerialsRangeDialog();
void accept() override;
int from() const;
int to() const;
private:
void updatePreview();
private:
const std::unique_ptr<Ui::AddSerialsRangeDialog> m_ui;
};
-109
View File
@@ -1,109 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddSerialsRangeDialog</class>
<widget class="QDialog" name="AddSerialsRangeDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>194</width>
<height>140</height>
</rect>
</property>
<property name="windowTitle">
<string>Add serials range</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="labelFrom">
<property name="text">
<string>From:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelTo">
<property name="text">
<string>To:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelCount">
<property name="text">
<string>Count:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelCountDisplay"/>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBoxFrom">
<property name="maximum">
<number>2147483647</number>
</property>
<property name="value">
<number>999930</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinBoxTo">
<property name="maximum">
<number>2147483647</number>
</property>
<property name="value">
<number>999940</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AddSerialsRangeDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>96</x>
<y>118</y>
</hint>
<hint type="destinationlabel">
<x>96</x>
<y>69</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AddSerialsRangeDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>96</x>
<y>118</y>
</hint>
<hint type="destinationlabel">
<x>96</x>
<y>69</y>
</hint>
</hints>
</connection>
</connections>
</ui>
-656
View File
@@ -1,656 +0,0 @@
#include "deviceconnection.h"
#include <QSslConfiguration>
#include <QSslCertificate>
#include <QSslKey>
#include <QFile>
#include <QDebug>
#include <QMetaEnum>
#include <QColor>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <utility>
#include "devicesmodel.h"
namespace {
const constexpr QColor red{255, 150, 150};
const constexpr QColor yellow{255, 255, 150};
const constexpr QColor green{150, 255, 150};
const constexpr QColor cyan{150, 255, 255};
const constexpr QColor blue{150, 150, 255};
const constexpr QColor magenta{255, 150, 255};
}
DeviceConnection::DeviceConnection(const QByteArray &username, const QByteArray &password, QString &&serial, QObject *parent) :
QObject{parent},
m_serial(std::move(serial)),
m_username{username},
m_password{password}
{
init();
}
DeviceConnection::DeviceConnection(const QByteArray &username, const QByteArray &password, const QString &serial, QObject *parent) :
QObject{parent},
m_serial(serial),
m_username{username},
m_password{password}
{
init();
}
void DeviceConnection::start()
{
QNetworkRequest request{QString("wss://solalaweb.com/%0").arg(m_serial)};
QString credentials = m_username + ':' + m_password;
QString base64 = credentials.toUtf8().toBase64();
request.setRawHeader("Authorization", "Basic " + base64.toUtf8());
m_websocket.open(request);
}
void DeviceConnection::stop()
{
m_websocket.close();
}
QString DeviceConnection::wsStatusText() const
{
return QMetaEnum::fromType<QAbstractSocket::SocketState>().valueToKey(m_websocket.state());
}
QBrush DeviceConnection::wsStatusBackground() const
{
switch (m_websocket.state())
{
case QAbstractSocket::UnconnectedState: return red;
case QAbstractSocket::HostLookupState:
case QAbstractSocket::ConnectingState: return yellow;
case QAbstractSocket::ConnectedState: return green;
case QAbstractSocket::ClosingState: return red;
}
return {};
}
QString DeviceConnection::statusText() const
{
switch (m_status)
{
case Status::Unknown: return tr("Unknown");
case Status::Ok: return tr("Ok");
case Status::Offline: return tr("Offline");
}
return QString::number(std::to_underlying(m_status));
}
QBrush DeviceConnection::statusBackground() const
{
switch (m_status)
{
case Status::Unknown: return yellow;
case Status::Ok: return green;
case Status::Offline: return red;
}
return {};
}
QString DeviceConnection::variantText() const
{
if (!m_fullStatus.contains("var"))
return {};
return m_fullStatus.value("var").toString();
}
QBrush DeviceConnection::variantBackground() const
{
if (!m_fullStatus.contains("var"))
return {};
switch (m_fullStatus.value("var").toInt())
{
case 11: return blue;
case 22: return yellow;
}
return red;
}
QString DeviceConnection::isGoText() const
{
if (!m_fullStatus.contains("isgo"))
return {};
return m_fullStatus.value("isgo").toBool() ? "yes" : "no";
}
QBrush DeviceConnection::isGoBackground() const
{
if (!m_fullStatus.contains("isgo"))
return {};
return m_fullStatus.value("isgo").toBool() ? blue : yellow;
}
QString DeviceConnection::isAustralienText() const
{
if (!m_fullStatus.contains("aus"))
return {};
return m_fullStatus.value("aus").toBool() ? "yes" : "no";
}
QBrush DeviceConnection::isAustralienBackground() const
{
if (!m_fullStatus.contains("aus"))
return {};
return m_fullStatus.value("aus").toBool() ? yellow : blue;
}
QString DeviceConnection::resetCardText() const
{
if (!m_fullStatus.contains("frci"))
return {};
return m_fullStatus.value("frci").toBool() ? "yes" : "no";
}
QBrush DeviceConnection::resetCardBackground() const
{
if (!m_fullStatus.contains("frci"))
return {};
return m_fullStatus.value("frci").toBool() ? green : red;
}
QString DeviceConnection::connectedWifiText() const
{
if (!m_fullStatus.contains("ccw"))
return {};
const auto &apd = m_fullStatus.value("ccw").toJsonObject();
if (!apd.contains("ssid"))
return {};
return apd.value("ssid").toString();
}
QString DeviceConnection::projectText() const
{
if (!m_fullStatus.contains("apd"))
return {};
const auto &apd = m_fullStatus.value("apd").toJsonObject();
if (!apd.contains("project_name"))
return {};
return apd.value("project_name").toString();
}
QString DeviceConnection::versionText() const
{
if (!m_fullStatus.contains("apd"))
return {};
const auto &apd = m_fullStatus.value("apd").toJsonObject();
if (!apd.contains("version"))
return {};
return apd.value("version").toString();
}
QString DeviceConnection::idfVersionText() const
{
if (!m_fullStatus.contains("apd"))
return {};
const auto &apd = m_fullStatus.value("apd").toJsonObject();
if (!apd.contains("idf_ver"))
return {};
return apd.value("idf_ver").toString();
}
QString DeviceConnection::updateText() const
{
if (!m_fullStatus.contains("ocs"))
return {};
const QStringList updateStates { tr("Idle"), tr("Updating"), tr("Failed"), tr("Succeeded"), tr("NotReady"), tr("Verifying") };
const auto ocs = m_fullStatus.value("ocs").toInt();
if (ocs >= 0 && ocs < updateStates.size())
{
if (ocs == 1)
{
std::optional<int> progress;
if (m_fullStatus.contains("ocp"))
progress = m_fullStatus.value("ocp").toInt();
std::optional<int> total;
if (m_fullStatus.contains("ocl"))
total = m_fullStatus.value("ocl").toInt();
if (progress && total)
return (tr("%0 %1%").arg(updateStates.at(ocs)).arg(*progress ? *progress * 100 / *total : 0));
else if (progress)
return tr("%0 %1/?").arg(updateStates.at(ocs)).arg(*progress);
else if (total)
return tr("%0 ?/%1").arg(updateStates.at(ocs)).arg(*total);
}
return updateStates.at(ocs);
}
return QString::number(ocs);
}
QBrush DeviceConnection::updateBackground() const
{
if (!m_fullStatus.contains("ocs"))
return {};
const auto ocs = m_fullStatus.value("ocs").toInt();
switch (ocs)
{
case 1: return yellow;
case 2: return red;
case 3: return green;
case 4: return red;
case 5: return yellow;
}
return {};
}
std::optional<qulonglong> DeviceConnection::uptime() const
{
if (!m_fullStatus.contains("rbt"))
return {};
return m_fullStatus.value("rbt").toULongLong();
}
QString DeviceConnection::uptimeText() const
{
if (!m_fullStatus.contains("rbt"))
return {};
return QString::number(m_fullStatus.value("rbt").toULongLong());
}
QString DeviceConnection::currentPartition() const
{
if (!m_fullStatus.contains("otap"))
return {};
const auto &otap = m_fullStatus.value("otap").toJsonObject();
if (!otap.contains("label"))
return {};
return otap.value("label").toString();
}
std::optional<int> DeviceConnection::reboots() const
{
if (!m_fullStatus.contains("rbc"))
return {};
return m_fullStatus.value("rbc").toInt();
}
QString DeviceConnection::rebootsText() const
{
if (!m_fullStatus.contains("rbc"))
return {};
return QString::number(m_fullStatus.value("rbc").toInt());
}
QString DeviceConnection::carStateText() const
{
if (!m_fullStatus.contains("car"))
return {};
const QStringList carStates { tr("Unknown"), tr("Idle"), tr("Charging"), tr("WaitCar"), tr("Complete"), tr("Error") };
const auto car = m_fullStatus.value("car").toInt();
if (car >= 0 && car < carStates.size())
return carStates.at(car);
return QString::number(car);
}
std::optional<double> DeviceConnection::energy() const
{
if (!m_fullStatus.contains("eto"))
return {};
return m_fullStatus.value("eto").toDouble();
}
QString DeviceConnection::energyText() const
{
if (!m_fullStatus.contains("eto"))
return {};
return QString("%0kWh").arg(m_fullStatus.value("eto").toDouble() / 1000.);
}
QString DeviceConnection::livedataText() const
{
if (!m_fullStatus.contains("nrg"))
return {};
const auto nrg = m_fullStatus.value("nrg").toList();
if (nrg.size() > 3)
return QString{"%0V %1V %2V %3V"}
.arg(nrg.at(0).toDouble())
.arg(nrg.at(1).toDouble())
.arg(nrg.at(2).toDouble())
.arg(nrg.at(3).toDouble());
return {};
}
QVariant DeviceConnection::getApiKey(const QString &apiKey) const
{
return m_fullStatus.value(apiKey);
}
void DeviceConnection::sendMessage(const QJsonDocument &doc)
{
sendMessage(QString::fromUtf8(doc.toJson()));
}
void DeviceConnection::sendMessage(const QJsonObject &obj)
{
sendMessage(QJsonDocument{obj});
}
void DeviceConnection::sendMessage(const QString &msg)
{
qDebug() << msg << m_websocket.errorString();
if (const auto written = m_websocket.sendTextMessage(msg); written != msg.size())
qCritical() << "sending message failed" << written << "(expected:" << msg.size() << ')';
}
QString DeviceConnection::errorString() const
{
return m_websocket.errorString();
}
void DeviceConnection::init()
{
if (auto model = qobject_cast<DevicesModel*>(parent()))
{
connect(this, &DeviceConnection::wsStatusChanged, model, &DevicesModel::wsStatusChanged);
connect(this, &DeviceConnection::statusChanged, model, &DevicesModel::statusChanged);
connect(this, &DeviceConnection::variantChanged, model, &DevicesModel::variantChanged);
connect(this, &DeviceConnection::isGoChanged, model, &DevicesModel::isGoChanged);
connect(this, &DeviceConnection::isAustralienChanged, model, &DevicesModel::isAustralienChanged);
connect(this, &DeviceConnection::resetCardChanged, model, &DevicesModel::resetCardChanged);
connect(this, &DeviceConnection::connectedWifiChanged, model, &DevicesModel::connectedWifiChanged);
connect(this, &DeviceConnection::projectChanged, model, &DevicesModel::projectChanged);
connect(this, &DeviceConnection::versionChanged, model, &DevicesModel::versionChanged);
connect(this, &DeviceConnection::idfVersionChanged, model, &DevicesModel::idfVersionChanged);
connect(this, &DeviceConnection::updateChanged, model, &DevicesModel::updateChanged);
connect(this, &DeviceConnection::uptimeChanged, model, &DevicesModel::uptimeChanged);
connect(this, &DeviceConnection::currentPartitionChanged, model, &DevicesModel::currentPartitionChanged);
connect(this, &DeviceConnection::rebootsChanged, model, &DevicesModel::rebootsChanged);
connect(this, &DeviceConnection::carStateChanged, model, &DevicesModel::carStateChanged);
connect(this, &DeviceConnection::energyChanged, model, &DevicesModel::energyChanged);
connect(this, &DeviceConnection::livedataChanged, model, &DevicesModel::livedataChanged);
connect(this, &DeviceConnection::apiKeyChanged, model, &DevicesModel::apiKeyChanged);
}
else
qWarning() << "unexpected parent";
//{
// auto sslConfig = m_websocket.sslConfiguration();
// sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
// //sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
// {
// auto caCerts = QSslCertificate::fromPath(":/goe-root-ca.pem");
// if (caCerts.empty())
// qFatal("could not parse root ca");
// for (const auto &caCert : std::as_const(caCerts))
// qDebug() << caCert.issuerDisplayName();
// sslConfig.setCaCertificates(std::move(caCerts));
// }
// sslConfig.setPrivateKey(m_key);
// sslConfig.setLocalCertificate(m_cert);
// m_websocket.setSslConfiguration(sslConfig);
// m_websocket.ignoreSslErrors();
//}
connect(&m_websocket, &QWebSocket::connected, this, &DeviceConnection::connectedSignal);
connect(&m_websocket, &QWebSocket::disconnected, this, &DeviceConnection::disconnectedSignal);
connect(&m_websocket, &QWebSocket::errorOccurred, this, &DeviceConnection::errorOccurredSignal);
connect(&m_websocket, &QWebSocket::connected, this, &DeviceConnection::connected);
connect(&m_websocket, &QWebSocket::disconnected, this, &DeviceConnection::disconnected);
connect(&m_websocket, &QWebSocket::stateChanged, this, &DeviceConnection::stateChanged);
connect(&m_websocket, &QWebSocket::textMessageReceived, this, &DeviceConnection::textMessageReceived);
connect(&m_websocket, &QWebSocket::binaryMessageReceived, this, &DeviceConnection::binaryMessageReceived);
connect(&m_websocket, &QWebSocket::errorOccurred, this, &DeviceConnection::errorOccurred);
connect(&m_websocket, &QWebSocket::peerVerifyError, this, &DeviceConnection::peerVerifyError);
connect(&m_websocket, &QWebSocket::sslErrors, this, &DeviceConnection::sslErrors);
connect(&m_websocket, &QWebSocket::alertReceived, this, &DeviceConnection::alertReceived);
connect(&m_websocket, &QWebSocket::handshakeInterruptedOnError, this, &DeviceConnection::handshakeInterruptedOnError);
}
void DeviceConnection::maintainStatus(const QJsonObject &msg, bool forceChange)
{
bool variantChanged{forceChange};
bool isGoChanged{forceChange};
bool isAustralienChanged{forceChange};
bool resetCardChanged{forceChange};
bool connectedWifiChanged{forceChange};
bool versionChanged{forceChange};
bool updateChanged{forceChange};
bool uptimeChanged{forceChange};
bool currentPartitionChanged{forceChange};
bool rebootsChanged{forceChange};
bool carStateChanged{forceChange};
bool energyChanged{forceChange};
bool livedataChanged{forceChange};
const auto status = msg.value("status").toObject();
for (auto iter = std::cbegin(status); iter != std::cend(status); iter++)
{
m_fullStatus[iter.key()] = iter.value().toVariant();
if (iter.key() == "var")
variantChanged = true;
else if (iter.key() == "isgo")
isGoChanged = true;
else if (iter.key() == "aus")
isAustralienChanged = true;
else if (iter.key() == "frci")
resetCardChanged = true;
else if (iter.key() == "ccw")
connectedWifiChanged = true;
else if (iter.key() == "apd")
versionChanged = true;
else if (iter.key() == "ocs" || iter.key() == "ocp" || iter.key() == "ocl")
updateChanged = true;
else if (iter.key() == "rbt")
uptimeChanged = true;
else if (iter.key() == "otap")
currentPartitionChanged = true;
else if (iter.key() == "rbc")
rebootsChanged = true;
else if (iter.key() == "car")
carStateChanged = true;
else if (iter.key() == "eto")
energyChanged = true;
else if (iter.key() == "nrg")
livedataChanged = true;
emit apiKeyChanged(iter.key());
}
if (variantChanged)
emit this->variantChanged();
if (isGoChanged)
emit this->isGoChanged();
if (isAustralienChanged)
emit this->isAustralienChanged();
if (resetCardChanged)
emit this->resetCardChanged();
if (connectedWifiChanged)
emit this->connectedWifiChanged();
if (versionChanged)
{
emit this->projectChanged();
emit this->versionChanged();
emit this->idfVersionChanged();
}
if (updateChanged)
emit this->updateChanged();
if (uptimeChanged)
emit this->uptimeChanged();
if (currentPartitionChanged)
emit this->currentPartitionChanged();
if (rebootsChanged)
emit this->rebootsChanged();
if (carStateChanged)
emit this->carStateChanged();
if (energyChanged)
emit this->energyChanged();
if (livedataChanged)
emit this->livedataChanged();
}
void DeviceConnection::connected()
{
qDebug() << "called";
}
void DeviceConnection::disconnected()
{
qDebug() << "called";
}
void DeviceConnection::stateChanged(QAbstractSocket::SocketState state)
{
// qDebug() << "called" << state;
if (state != QAbstractSocket::ConnectedState)
{
if (m_status != Status::Unknown)
{
m_status = Status::Unknown;
emit statusChanged();
}
}
emit wsStatusChanged();
}
void DeviceConnection::textMessageReceived(const QString &message)
{
// qDebug() << "called" << message;
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error != QJsonParseError::NoError)
{
qWarning() << m_serial << "could not parse json:" << error.errorString();
return;
}
if (!doc.isObject())
{
qWarning() << m_serial << "json is not an object";
return;
}
const auto msgObj = doc.object();
if (!msgObj.contains("type"))
{
qWarning() << "msg does not contain a type";
return;
}
const auto typeVal = msgObj.value("type");
if (!typeVal.isString())
{
qWarning() << "msg type is not a string";
return;
}
const auto type = typeVal.toString();
bool response{};
if (msgObj.contains("requestId"))
{
response = true;
qDebug() << "response to a request" << message;
emit responseReceived(msgObj.value("requestId").toString(), msgObj);
}
if (type == "hello")
{
// qDebug() << "hello received" << msgObj;
m_status = Status::Ok;
emit statusChanged();
}
else if (type == "offline")
{
// qDebug() << "offline received" << msgObj;
m_status = Status::Offline;
emit statusChanged();
}
else if (type == "fullStatus")
{
bool forceChange{false};
if (m_receivedDelta)
{
m_receivedDelta = false;
m_fullStatus.clear();
forceChange = true;
}
maintainStatus(msgObj, forceChange);
}
else if (type == "deltaStatus")
{
m_receivedDelta = true;
maintainStatus(msgObj, false);
}
else if (!response)
qWarning() << "unknown message type" << msgObj;
}
void DeviceConnection::binaryMessageReceived(const QByteArray &message)
{
qDebug() << "called" << message;
}
void DeviceConnection::errorOccurred(QAbstractSocket::SocketError error)
{
qDebug() << "called" << QMetaEnum::fromType<QAbstractSocket::SocketError>().valueToKey(error) << m_websocket.errorString();
}
void DeviceConnection::peerVerifyError(const QSslError &error)
{
qDebug() << "called" << error;
}
void DeviceConnection::sslErrors(const QList<QSslError> &errors)
{
qDebug() << "called" << errors;
}
void DeviceConnection::alertReceived(QSsl::AlertLevel level, QSsl::AlertType type, const QString &description)
{
qDebug() << "called" << std::to_underlying(level) << std::to_underlying(type) << description;
}
void DeviceConnection::handshakeInterruptedOnError(const QSslError &error)
{
qDebug() << "called" << error;
}
-135
View File
@@ -1,135 +0,0 @@
#pragma once
#include <QObject>
#include <QWebSocket>
#include <QBrush>
#include <QVariantMap>
class QSslKey;
class QSslCertificate;
class QJsonObject;
class DeviceConnection : public QObject
{
Q_OBJECT
public:
explicit DeviceConnection(const QByteArray &username, const QByteArray &password, QString &&serial, QObject *parent = nullptr);
explicit DeviceConnection(const QByteArray &username, const QByteArray &password, const QString &serial, QObject *parent = nullptr);
void start();
void stop();
const QString &serial() const { return m_serial; }
QString wsStatusText() const;
QBrush wsStatusBackground() const;
QString statusText() const;
QBrush statusBackground() const;
QString variantText() const;
QBrush variantBackground() const;
QString isGoText() const;
QBrush isGoBackground() const;
QString isAustralienText() const;
QBrush isAustralienBackground() const;
QString resetCardText() const;
QBrush resetCardBackground() const;
QString connectedWifiText() const;
QString projectText() const;
QString versionText() const;
QString idfVersionText() const;
QString updateText() const;
QBrush updateBackground() const;
std::optional<qulonglong> uptime() const;
QString uptimeText() const;
QString currentPartition() const;
std::optional<int> reboots() const;
QString rebootsText() const;
QString carStateText() const;
std::optional<double> energy() const;
QString energyText() const;
QString livedataText() const;
QVariant getApiKey(const QString &apiKey) const;
void sendMessage(const QJsonDocument &doc);
void sendMessage(const QJsonObject &obj);
void sendMessage(const QString &msg);
QString errorString() const;
signals:
void connectedSignal();
void disconnectedSignal();
void errorOccurredSignal(QAbstractSocket::SocketError error);
void responseReceived(const QString &requestId, const QJsonObject &msg);
void wsStatusChanged();
void statusChanged();
void variantChanged();
void isGoChanged();
void isAustralienChanged();
void resetCardChanged();
void connectedWifiChanged();
void projectChanged();
void versionChanged();
void idfVersionChanged();
void updateChanged();
void uptimeChanged();
void currentPartitionChanged();
void rebootsChanged();
void carStateChanged();
void energyChanged();
void livedataChanged();
void apiKeyChanged(const QString &apiKey);
private:
void init();
void maintainStatus(const QJsonObject &msg, bool forceChange);
private slots:
void connected();
void disconnected();
void stateChanged(QAbstractSocket::SocketState state);
void textMessageReceived(const QString &message);
void binaryMessageReceived(const QByteArray &message);
void errorOccurred(QAbstractSocket::SocketError error);
void peerVerifyError(const QSslError &error);
void sslErrors(const QList<QSslError> &errors);
void alertReceived(QSsl::AlertLevel level, QSsl::AlertType type, const QString &description);
void handshakeInterruptedOnError(const QSslError &error);
private:
const QString m_serial;
const QByteArray m_username;
const QByteArray m_password;
enum Status {
Unknown,
Ok,
Offline
};
Status m_status{Status::Unknown};
QWebSocket m_websocket;
QVariantMap m_fullStatus;
bool m_receivedDelta{false};
};
-523
View File
@@ -1,523 +0,0 @@
#include "devicesmodel.h"
#include <QDebug>
#include <QBrush>
#include <QJsonDocument>
#include <algorithm>
#include <iterator>
#include "deviceconnection.h"
#include "flottenupdatersettings.h"
namespace {
enum {
ColumnSerial,
ColumnWsStatus,
ColumnStatus,
ColumnVariant,
ColumnIsGo,
ColumnIsAustralien,
ColumnResetCard,
ColumnConnectedWifi,
ColumnProject,
ColumnVersion,
ColumnIdfVersion,
ColumnUpdate,
ColumnReboots,
ColumnUptime,
ColumnCurrentPartition,
ColumnCarState,
ColumnEnergy,
ColumnLivedata,
NumberOfColumns
};
}
DevicesModel::DevicesModel(FlottenUpdaterSettings &settings, const QByteArray &username,
const QByteArray &password, QObject *parent) :
QAbstractTableModel{parent},
m_settings{settings},
m_username{username},
m_password{password},
m_customColumns{settings.customColumns()}
{
for (const auto &serial : m_settings.serials())
{
if (std::none_of(std::cbegin(m_devices), std::cend(m_devices), [&serial](auto &ptr){
return ptr->serial() == serial;
}))
m_devices.emplace_back(std::make_shared<DeviceConnection>(m_username, m_password, serial, this));
}
}
DevicesModel::~DevicesModel() = default;
int DevicesModel::rowCount(const QModelIndex &parent) const
{
return m_devices.size();
}
int DevicesModel::columnCount(const QModelIndex &parent) const
{
return NumberOfColumns + m_customColumns.size();
}
QVariant DevicesModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_devices.size())
{
qWarning() << "row out of bounds";
return {};
}
const DeviceConnection &device = **std::next(std::begin(m_devices), index.row());
switch (index.column())
{
case ColumnSerial:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.serial();
}
return {};
case ColumnWsStatus:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.wsStatusText();
case Qt::BackgroundRole:
return device.wsStatusBackground();
}
return {};
case ColumnStatus:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.statusText();
case Qt::BackgroundRole:
return device.statusBackground();
}
return {};
case ColumnVariant:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.variantText();
case Qt::BackgroundRole:
return device.variantBackground();
}
return {};
case ColumnIsGo:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.isGoText();
case Qt::BackgroundRole:
return device.isGoBackground();
}
return {};
case ColumnIsAustralien:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.isAustralienText();
case Qt::BackgroundRole:
return device.isAustralienBackground();
}
return {};
case ColumnResetCard:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.resetCardText();
case Qt::BackgroundRole:
return device.resetCardBackground();
}
return {};
case ColumnConnectedWifi:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.connectedWifiText();
}
return {};
case ColumnProject:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.projectText();
}
return {};
case ColumnVersion:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.versionText();
}
return {};
case ColumnIdfVersion:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.idfVersionText();
}
return {};
case ColumnUpdate:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.updateText();
case Qt::BackgroundRole:
return device.updateBackground();
}
return {};
case ColumnUptime:
switch (role)
{
case Qt::DisplayRole:
return device.uptimeText();
case Qt::EditRole:
if (const auto uptime = device.uptime(); uptime)
return *uptime;
}
return {};
case ColumnCurrentPartition:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.currentPartition();
}
return {};
case ColumnReboots:
switch (role)
{
case Qt::DisplayRole:
return device.rebootsText();
case Qt::EditRole:
if (const auto reboots = device.reboots(); reboots)
return *reboots;
}
return {};
case ColumnCarState:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.carStateText();
}
return {};
case ColumnEnergy:
switch (role)
{
case Qt::DisplayRole:
return device.energyText();
case Qt::EditRole:
if (const auto energy = device.energy(); energy)
return *energy;
}
return {};
case ColumnLivedata:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return device.livedataText();
}
return {};
}
if (index.column() >= NumberOfColumns && index.column() - NumberOfColumns < m_customColumns.size())
switch (role)
{
case Qt::DisplayRole:
{
auto variant = device.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 device.getApiKey(m_customColumns.at(index.column() - NumberOfColumns));
}
return {};
}
QVariant DevicesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole && role != Qt::EditRole)
return {};
switch (orientation)
{
case Qt::Horizontal:
switch (section)
{
case ColumnSerial: return tr("Serial");
case ColumnWsStatus: return tr("WS Status");
case ColumnStatus: return tr("Status");
case ColumnVariant: return tr("Variant");
case ColumnIsGo: return tr("IsGo");
case ColumnIsAustralien: return tr("IsAustralien");
case ColumnResetCard: return tr("ResetCard");
case ColumnConnectedWifi: return tr("ConnectedWifi");
case ColumnProject: return tr("Project");
case ColumnVersion: return tr("Version");
case ColumnIdfVersion: return tr("IDF Version");
case ColumnUpdate: return tr("Update");
case ColumnReboots: return tr("Reboots");
case ColumnUptime: return tr("Uptime");
case ColumnCurrentPartition: return tr("Current Partition");
case ColumnCarState: return tr("Car state");
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 {};
}
bool DevicesModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (row < 0 || std::size_t(row) >= m_devices.size())
{
qWarning() << "unexpected row" << row;
return false;
}
if (count < 0 || std::size_t(count) > m_devices.size() - row)
{
qWarning() << "unexpected row+count" << count << row;
return false;
}
beginRemoveRows({}, row, row + count - 1);
auto begin = std::next(std::begin(m_devices), row);
auto end = std::next(begin, count);
m_devices.erase(begin, end);
endRemoveRows();
return true;
}
bool DevicesModel::addClient(const QString &serial)
{
if (std::any_of(std::cbegin(m_devices), std::cend(m_devices),
[&](const auto &device){ return device->serial() == serial; }))
{
qWarning() << "duplicate serial";
return false;
}
beginInsertRows({}, m_devices.size(), m_devices.size());
auto clientPtr = std::make_shared<DeviceConnection>(m_username, m_password, serial, this);
auto client = clientPtr.get();
m_devices.emplace_back(std::move(clientPtr));
endInsertRows();
client->start();
return true;
}
std::shared_ptr<DeviceConnection> DevicesModel::getDevice(QModelIndex index)
{
Q_ASSERT(!index.parent().isValid());
Q_ASSERT(index.row() >= 0 && index.row() < m_devices.size());
auto device = m_devices.at(index.row());
Q_ASSERT(device);
return device;
}
std::shared_ptr<const DeviceConnection> DevicesModel::getDevice(QModelIndex index) const
{
Q_ASSERT(!index.parent().isValid());
return m_devices.at(index.row());
}
void DevicesModel::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 DevicesModel::customColumnRemovable(int section)
{
return section >= NumberOfColumns && section - NumberOfColumns < m_customColumns.size();
}
void DevicesModel::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);
}
QStringList DevicesModel::serials() const
{
QStringList serials;
serials.resize(m_devices.size());
std::transform(std::cbegin(m_devices), std::cend(m_devices),
std::begin(serials), [](const auto &device){ return device->serial(); });
return serials;
}
void DevicesModel::connectAll()
{
for (auto &device : m_devices)
device->start();
}
void DevicesModel::disconnectAll()
{
for (auto &device : m_devices)
device->stop();
}
void DevicesModel::wsStatusChanged()
{
columnChanged(ColumnWsStatus, {Qt::DisplayRole, Qt::EditRole, Qt::BackgroundRole});
}
void DevicesModel::statusChanged()
{
columnChanged(ColumnStatus, {Qt::DisplayRole, Qt::EditRole, Qt::BackgroundRole});
}
void DevicesModel::variantChanged()
{
columnChanged(ColumnVariant, {Qt::DisplayRole, Qt::EditRole, Qt::BackgroundRole});
}
void DevicesModel::isGoChanged()
{
columnChanged(ColumnIsGo, {Qt::DisplayRole, Qt::EditRole, Qt::BackgroundRole});
}
void DevicesModel::isAustralienChanged()
{
columnChanged(ColumnIsAustralien, {Qt::DisplayRole, Qt::EditRole, Qt::BackgroundRole});
}
void DevicesModel::resetCardChanged()
{
columnChanged(ColumnResetCard, {Qt::DisplayRole, Qt::EditRole, Qt::BackgroundRole});
}
void DevicesModel::connectedWifiChanged()
{
columnChanged(ColumnConnectedWifi, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::projectChanged()
{
columnChanged(ColumnProject, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::versionChanged()
{
columnChanged(ColumnVersion, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::idfVersionChanged()
{
columnChanged(ColumnIdfVersion, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::updateChanged()
{
columnChanged(ColumnUpdate, {Qt::DisplayRole, Qt::EditRole, Qt::BackgroundRole});
}
void DevicesModel::uptimeChanged()
{
columnChanged(ColumnUptime, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::currentPartitionChanged()
{
columnChanged(ColumnCurrentPartition, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::rebootsChanged()
{
columnChanged(ColumnReboots, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::carStateChanged()
{
columnChanged(ColumnCarState, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::energyChanged()
{
columnChanged(ColumnEnergy, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::livedataChanged()
{
columnChanged(ColumnLivedata, {Qt::DisplayRole, Qt::EditRole});
}
void DevicesModel::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 DevicesModel::columnChanged(int column, const QList<int> &roles)
{
auto device = qobject_cast<DeviceConnection*>(sender());
if (!device)
{
qWarning() << "unknown sender" << sender();
return;
}
auto iter = std::find_if(std::cbegin(m_devices), std::cend(m_devices), [&device](const std::shared_ptr<DeviceConnection> &ptr){ return device == ptr.get(); });
if (iter == std::cend(m_devices))
{
qWarning() << "unknown sender" << device;
for (const auto &device : m_devices)
qDebug() << device.get();
return;
}
auto row = std::distance(std::cbegin(m_devices), iter);
auto index = createIndex(row, column);
emit dataChanged(index, index, roles);
}
-74
View File
@@ -1,74 +0,0 @@
#pragma once
#include <QAbstractTableModel>
#include <vector>
#include <memory>
class QSslKey;
class QSslCertificate;
class FlottenUpdaterSettings;
class DeviceConnection;
class DevicesModel : public QAbstractTableModel
{
Q_OBJECT
using base = QAbstractTableModel;
public:
explicit DevicesModel(FlottenUpdaterSettings &settings, const QByteArray &username,
const QByteArray &password, QObject *parent = nullptr);
~DevicesModel() override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
bool removeRows(int row, int count, const QModelIndex &parent) override;
bool addClient(const QString &serial);
std::shared_ptr<DeviceConnection> getDevice(QModelIndex index);
std::shared_ptr<const DeviceConnection> getDevice(QModelIndex index) const;
void addCustomColumn(const QString &apiKey);
bool customColumnRemovable(int section);
void removeCustomColumn(int section);
QStringList serials() const;
public slots:
void connectAll();
void disconnectAll();
void wsStatusChanged();
void statusChanged();
void variantChanged();
void isGoChanged();
void isAustralienChanged();
void resetCardChanged();
void connectedWifiChanged();
void projectChanged();
void versionChanged();
void idfVersionChanged();
void updateChanged();
void uptimeChanged();
void currentPartitionChanged();
void rebootsChanged();
void carStateChanged();
void energyChanged();
void livedataChanged();
void apiKeyChanged(const QString &apiKey);
private:
FlottenUpdaterSettings &m_settings;
const QByteArray m_username;
const QByteArray m_password;
void columnChanged(int column, const QList<int> &roles = QList<int>());
std::vector<std::shared_ptr<DeviceConnection>> m_devices;
QStringList m_customColumns;
};
@@ -1,41 +0,0 @@
#include "flottenupdatersettings.h"
QByteArray FlottenUpdaterSettings::username() const
{
return value("username").toByteArray();
}
void FlottenUpdaterSettings::setUsername(const QByteArray &username)
{
setValue("username", username);
}
QByteArray FlottenUpdaterSettings::password() const
{
return value("password").toByteArray();
}
void FlottenUpdaterSettings::setPassword(const QByteArray &password)
{
setValue("password", password);
}
QStringList FlottenUpdaterSettings::customColumns() const
{
return value("customColumns").toStringList();
}
void FlottenUpdaterSettings::setCustomColumns(const QStringList &customColumns)
{
setValue("customColumns", customColumns);
}
QStringList FlottenUpdaterSettings::serials() const
{
return value("serials").toStringList();
}
void FlottenUpdaterSettings::setSerials(const QStringList &serials)
{
setValue("serials", serials);
}
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include <QSettings>
class FlottenUpdaterSettings : public QSettings
{
Q_OBJECT
public:
using QSettings::QSettings;
QByteArray username() const;
void setUsername(const QByteArray &key);
QByteArray password() const;
void setPassword(const QByteArray &cert);
QStringList customColumns() const;
void setCustomColumns(const QStringList &customColumns);
QStringList serials() const;
void setSerials(const QStringList &serials);
};
-32
View File
@@ -1,32 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFlTCCA32gAwIBAgIUcZ/DDQS4/ix7lYlys+2zMAZnHwowDQYJKoZIhvcNAQEN
BQAwZTELMAkGA1UEBhMCQVQxETAPBgNVBAgMCEthZXJudGVuMRQwEgYDVQQHDAtG
ZWxka2lyY2hlbjESMBAGA1UECgwJZ28tZSBHbWJIMRkwFwYDVQQDDBBnby1lIElP
VCBST09UIENBMCAXDTIwMTEyOTE5MTU0MFoYDzIwOTAxMTEyMTkxNTQwWjBlMQsw
CQYDVQQGEwJBVDERMA8GA1UECAwIS2Flcm50ZW4xFDASBgNVBAcMC0ZlbGRraXJj
aGVuMRIwEAYDVQQKDAlnby1lIEdtYkgxGTAXBgNVBAMMEGdvLWUgSU9UIFJPT1Qg
Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDIPhF0MflVFuGUtyI0
UjyQpjAQStP9k4Wlx+czab6vPXbDbdVEXxXPhoHBeW0oT0g8UuOtMsdfGy3RsNCd
MLR8KiwqysaYeDmHWvVG2Bmpc8+BUm3p2zg63MMCrjp4Syfytgg4hORZjAvbRgDv
Jmh+EgjtD0nakyhBWwCkbDqQN0AXumvSaWRgvcDppI94i4aFGeg8xUBRLzsHbdHY
CuP4hCM/75FqIbOvUuXAMC/FfUxGAt/evxrQRoqyLzF0MrPvzQUTWaOuVH4WPxRM
zDm7kEBUmFL/nYcxkVjLoGqhDAjiMX1Xo4IgsUA0zUVHNHPOKyc7P2E2jgeVDe7o
WCjifoO0OQXiS/uaJmz+33dFe5hBQmKPFEit45ijrsieo1hBasUWHToaa/o/iArp
iSjKTy7YzLFKhvFVZs5iBOOc0K2KglYSBii7d7cT2wArfGpTtuA+eBthIPvM432a
KuW0IGl1BwzjlqIwnSCNd43s4KXg3sUgzW4GtIYSjI897nLlBeJ8Y5zsuEWPRaDG
FVLQJQ1lEwo740CFRSCEkGKdmKMT5TmuA4aApAT0xl8R06lbri9mY0BFWyLrGf3q
rrJO8LDcgGm5fIch+XlNb6H7nf6nP2AJOJRNha9ziNkjDI8jaKlBGveEzw6a+O4X
npsTdKS6hGJ8V74PvlDsZg5nUwIDAQABozswOTAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBhjAWBgNVHREEDzANggtpb3QuZ28tZS5jbzANBgkqhkiG9w0B
AQ0FAAOCAgEAsuGAB7wTy3+9/7/DfBWUkPHvLmR2MmkfBljt/cxFz6pFAVe+QDnb
9EmUWOmWjw7i2rvs5mvcErynj2/R+VDFyorufXMnfm7BXGl/68xIvRszpJe2snPq
cfhC48R7JX7jJcm5xMHkAg8oc3ziRH634/Lo/KXkRhSfxbbujpdAeEhO68ekLKuq
1V8RuJ+MNspLsIrn366kN6pPAQWUM0TUTAs2W0cIDgVx4pnRQd31ccBiCoRcNaer
5mA85KdTkzF0E3/jLuvB4BZRKI0apTg+/hO2a3XQhjbdAKw8C8rYu3Db+t6KRnsa
tyl2Nxeh7nfmfQgdgKZ0zA4osjREzObycZYdUgBFQOf67pU7TCxpzk3iflLSGL92
AXRpcf3oSw3DLm+fAgaNVVHeSQ3ipB0YLGmf4l/105DAL6o4jTGFcsbPkkrtC8rT
E5+lJYyTQzvb6gLsBLKBTrpcC2BNli5f5tmZJdKOrpY9lb8XFO3W31OTl71FdwlE
KtJrht8K/9xvAUzYHXeTORl0gR7B/yNzSg4p8nJOhSJD9ktUrXErFLu4S2GMqcBM
U/YOJkFc93cSZf0TKM3TaUUslHYFKruBKH8sd15UhijnixED111F63Kwcar87QES
8ipCcGWUtufRHKaUhmswP7lbR5hbQJwIpyBg+FfzcrPUAFrXdllxe4A=
-----END CERTIFICATE-----
@@ -1,89 +0,0 @@
#include "importcredentialsdialog.h"
#include "ui_importcredentialsdialog.h"
#include <QMessageBox>
#include <QPushButton>
#include "deviceconnection.h"
ImportCredentialsDialog::ImportCredentialsDialog(QWidget *parent) :
QDialog{parent},
m_ui{std::make_unique<Ui::ImportCredentialsDialog>()}
{
m_ui->setupUi(this);
}
ImportCredentialsDialog::~ImportCredentialsDialog() = default;
QByteArray ImportCredentialsDialog::username() const
{
return m_ui->lineEditUsername->text().toUtf8();
}
QByteArray ImportCredentialsDialog::password() const
{
return m_ui->lineEditPassword->text().toUtf8();
}
void ImportCredentialsDialog::accept()
{
if (username().isEmpty() ||
password().isEmpty())
{
QMessageBox::warning(this, tr("Please fill out all fields!"), tr("Please fill out all fields!"));
return;
}
m_ui->lineEditUsername->setEnabled(false);
m_ui->lineEditPassword->setEnabled(false);
if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Save))
button->setEnabled(false);
m_testConnection = std::make_unique<DeviceConnection>(username(), password(), "123456");
connect(m_testConnection.get(), &DeviceConnection::connectedSignal,
this, &ImportCredentialsDialog::connected);
connect(m_testConnection.get(), &DeviceConnection::disconnectedSignal,
this, &ImportCredentialsDialog::disconnected);
connect(m_testConnection.get(), &DeviceConnection::errorOccurredSignal,
this, &ImportCredentialsDialog::errorOccurred);
m_testConnection->start();
}
void ImportCredentialsDialog::connected()
{
qDebug() << "called";
QDialog::accept();
finishTestConnection();
}
void ImportCredentialsDialog::disconnected()
{
qDebug() << "called";
finishTestConnection();
}
void ImportCredentialsDialog::errorOccurred(QAbstractSocket::SocketError error)
{
Q_ASSERT(m_testConnection);
QMessageBox::warning(this,
tr("Problem verifying credentials!"),
QString("%0\n\n%1")
.arg(QMetaEnum::fromType<QAbstractSocket::SocketError>().valueToKey(error))
.arg(m_testConnection->errorString())
);
}
void ImportCredentialsDialog::finishTestConnection()
{
Q_ASSERT(m_testConnection);
m_ui->lineEditUsername->setEnabled(true);
m_ui->lineEditPassword->setEnabled(true);
if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Save))
button->setEnabled(true);
m_testConnection.release()->deleteLater();
}
-35
View File
@@ -1,35 +0,0 @@
#pragma once
#include <QDialog>
#include <QAbstractSocket>
#include <memory>
namespace Ui { class ImportCredentialsDialog; }
class DeviceConnection;
class ImportCredentialsDialog : public QDialog
{
Q_OBJECT
public:
explicit ImportCredentialsDialog(QWidget *parent = nullptr);
~ImportCredentialsDialog();
QByteArray username() const;
QByteArray password() const;
public slots:
void accept() override;
private slots:
void connected();
void disconnected();
void errorOccurred(QAbstractSocket::SocketError error);
private:
void finishTestConnection();
const std::unique_ptr<Ui::ImportCredentialsDialog> m_ui;
std::unique_ptr<DeviceConnection> m_testConnection;
};
-114
View File
@@ -1,114 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImportCredentialsDialog</class>
<widget class="QDialog" name="ImportCredentialsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>442</width>
<height>164</height>
</rect>
</property>
<property name="windowTitle">
<string>Import Credentials</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="labelTitle">
<property name="text">
<string>Please enter your credentials to access solalaweb.com:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Please create an app password in &lt;a href=&quot;https://auth.go-e.com/if/user/#/settings;{%22page%22:%22page-tokens%22}&quot;&gt;our SSO&lt;/a&gt;.</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextInteractionFlag::TextBrowserInteraction</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="labelUsername">
<property name="text">
<string>Username:</string>
</property>
<property name="buddy">
<cstring>lineEditUsername</cstring>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelPassword">
<property name="text">
<string>App-Password:</string>
</property>
<property name="buddy">
<cstring>lineEditPassword</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEditUsername"/>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEditPassword"/>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ImportCredentialsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ImportCredentialsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
-63
View File
@@ -1,63 +0,0 @@
#include <QApplication>
#include <QMessageBox>
#include <QDebug>
#include "flottenupdatersettings.h"
#include "importcredentialsdialog.h"
#include "mainwindow.h"
int main(int argc, char *argv[])
{
qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} "
"["
"%{if-debug}D%{endif}"
"%{if-info}I%{endif}"
"%{if-warning}W%{endif}"
"%{if-critical}C%{endif}"
"%{if-fatal}F%{endif}"
"] "
"%{function}(): "
"%{message}"));
QApplication app{argc, argv};
QCoreApplication::setOrganizationName("feedc0de");
QCoreApplication::setOrganizationDomain("brunner.ninja");
QCoreApplication::setApplicationName("flotten-updater");
#ifdef Q_OS_WIN
if (QSslSocket::availableBackends().contains("schannel"))
QSslSocket::setActiveBackend("schannel");
#endif
FlottenUpdaterSettings settings;
QByteArray username = settings.username();
QByteArray password = settings.password();
if (username.isEmpty())
goto loadCredentials;
if (password.isEmpty())
goto loadCredentials;
goto showMainWindow;
{
loadCredentials:
ImportCredentialsDialog dialog;
if (dialog.exec() != QDialog::Accepted)
return 0;
username = dialog.username();
password = dialog.password();
settings.setUsername(username);
settings.setPassword(password);
}
showMainWindow:
MainWindow mainWindow{settings, username, password};
mainWindow.show();
return app.exec();
}
-321
View File
@@ -1,321 +0,0 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QInputDialog>
#include <QSortFilterProxyModel>
#include <QMenu>
#include <QInputDialog>
#include <QMessageBox>
#include <QJsonObject>
#include <QJsonArray>
#include <algorithm>
#include <utility>
#include "flottenupdatersettings.h"
#include "devicesmodel.h"
#include "requestdialog.h"
#include "setarbitraryapikeydialog.h"
#include "addserialsrangedialog.h"
MainWindow::MainWindow(FlottenUpdaterSettings &settings, const QByteArray &username,
const QByteArray &password, QWidget *parent) :
QMainWindow{parent},
m_ui{std::make_unique<Ui::MainWindow>()},
m_settings{settings},
m_model{std::make_unique<DevicesModel>(settings, username, password, this)},
m_proxyModel{std::make_unique<QSortFilterProxyModel>(this)}
{
m_ui->setupUi(this);
m_proxyModel->setSourceModel(m_model.get());
m_proxyModel->setSortRole(Qt::EditRole);
m_ui->treeView->setModel(m_proxyModel.get());
connect(m_ui->pushButtonConnectAll, &QAbstractButton::pressed, m_model.get(), &DevicesModel::connectAll);
connect(m_ui->pushButtonDisconnectAll, &QAbstractButton::pressed, m_model.get(), &DevicesModel::disconnectAll);
connect(m_ui->pushButtonAdd, &QAbstractButton::pressed, this, &MainWindow::doAdd);
connect(m_ui->pushButtonRemove, &QAbstractButton::pressed, this, &MainWindow::doRemove);
connect(m_ui->pushButtonAddRange, &QAbstractButton::pressed, this, &MainWindow::doAddRange);
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);
selectionChanged();
}
MainWindow::~MainWindow() = default;
void MainWindow::doAdd()
{
bool ok{};
const auto serial = QInputDialog::getText(this, tr("Serial"), tr("Serial"), QLineEdit::Normal, {}, &ok);
if (!ok)
return;
if (m_model->addClient(serial))
m_settings.setSerials(m_model->serials());
else
QMessageBox::warning(this, tr("Error while adding serial!"), tr("Error while adding serial!"));
}
void MainWindow::doRemove()
{
auto selectedRows = m_ui->treeView->selectionModel()->selectedRows();
if (selectedRows.isEmpty())
return;
// map proxied indices to those from the model
std::transform(std::begin(selectedRows), std::end(selectedRows), std::begin(selectedRows),
[&](const QModelIndex &index){ return m_proxyModel->mapToSource(index); });
removeRows(std::move(selectedRows));
}
void MainWindow::doAddRange()
{
AddSerialsRangeDialog dialog{this};
if (dialog.exec() != QDialog::Accepted)
return;
int succeeded{}, failed{};
for (auto serial = dialog.from(); serial <= dialog.to(); serial++)
{
if (m_model->addClient(QString::number(serial)))
succeeded++;
else
{
qWarning() << "adding serial" << serial << "failed";
failed++;
}
}
if (succeeded > 0)
m_settings.setSerials(m_model->serials());
if (failed > 0)
QMessageBox::warning(this, tr("Error while adding!"), tr("%0 rows could not be adding!").arg(failed));
}
void MainWindow::selectionChanged()
{
auto count = m_ui->treeView->selectionModel()->selectedRows().count();
m_ui->pushButtonRemove->setEnabled(count > 0);
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();
if (selectedRows.isEmpty())
return;
// map proxied indices to those from the model
std::transform(std::begin(selectedRows), std::end(selectedRows), std::begin(selectedRows),
[&](const QModelIndex &index){ return m_proxyModel->mapToSource(index); });
// get all the devices for selected indices
std::vector<std::shared_ptr<DeviceConnection>> devices;
devices.reserve(selectedRows.size());
std::transform(std::begin(selectedRows), std::end(selectedRows), std::back_inserter(devices),
[&](const QModelIndex &index){ auto device = m_model->getDevice(index); Q_ASSERT(device); return device; });
Q_ASSERT(std::all_of(std::begin(devices), std::end(devices), [](const auto &device)->bool{ return device.get(); }));
QMenu menu;
auto actionSetUpdateUrl = menu.addAction(tr("Set update url..."));
auto actionStartUpdate = menu.addAction(tr("Start update..."));
auto actionReboot = menu.addAction(tr("Reboot..."));
auto actionSetChargectrlOverride = menu.addAction(tr("Set chargectrl override..."));
auto actionSetAbitraryApiKey = menu.addAction(tr("Set abitrary api key..."));
auto resetNvsKey = menu.addAction(tr("Reset nvs key..."));
auto actionOpenApps = menu.addAction(tr("Open app(s)..."));
auto actionRemove = menu.addAction(tr("Remove..."));
if (const auto selected = menu.exec(m_ui->treeView->viewport()->mapToGlobal(pos)); selected == actionSetUpdateUrl)
{
bool ok{};
const auto url = QInputDialog::getText(this, tr("Enter update url..."), tr("Update url:"), QLineEdit::Normal, {}, &ok);
if (!ok)
return;
QJsonObject msg {
{ "type", "setValue" },
{ "key", "ocu" },
{ "value", url },
{ "sudo", true }
};
RequestDialog{std::move(msg), std::move(devices), this}.exec();
}
else if (selected == actionStartUpdate)
{
QString branch;
{
QInputDialog dialog{this};
dialog.setWindowTitle(tr("Select update release..."));
dialog.setInputMode(QInputDialog::TextInput);
dialog.setLabelText(tr("Update release"));
dialog.setComboBoxEditable(true);
dialog.setComboBoxItems({QStringLiteral("__default")});
if (dialog.exec() != QDialog::Accepted)
return;
branch = dialog.textValue();
}
QJsonObject msg {
{ "type", "setValue" },
{ "key", "oct" },
{ "value", branch },
{ "sudo", true }
};
RequestDialog{std::move(msg), std::move(devices), this}.exec();
}
else if (selected == actionReboot)
{
if (const auto result = QMessageBox::question(this, tr("Are you sure?"), tr("Do you really want to reboot selected devices?")); result == QMessageBox::Yes)
{
QJsonObject msg {
{ "type", "setValue" },
{ "key", "rst" },
{ "value", 1 },
{ "sudo", true }
};
RequestDialog{std::move(msg), std::move(devices), this}.exec();
}
}
else if (selected == actionSetChargectrlOverride)
{
QJsonObject msg {
{ "type", "setValue" },
{ "key", "stao" },
{ "value", QJsonObject {
{ "car", 2 },
{ "error", 0 },
{ "phases", QJsonArray {
true,
true,
true,
true,
true,
true
} },
{ "temperature", 30 },
{ "voltageL1", 230 },
{ "voltageL2", 230 },
{ "voltageL3", 230 },
{ "voltageN", 0 },
{ "ampereL1", 32 },
{ "ampereL2", 32 },
{ "ampereL3", 32 },
{ "powerL1", 7360 },
{ "powerL2", 7360 },
{ "powerL3", 7360 },
{ "powerN", 0 },
{ "powerTotal", 22080 },
{ "powerFactorL1", 90 },
{ "powerFactorL2", 90 },
{ "powerFactorL3", 90 },
{ "powerFactorN", 0 }
} },
{ "sudo", true }
};
RequestDialog{std::move(msg), std::move(devices), this}.exec();
}
else if (selected == actionSetAbitraryApiKey)
{
SetArbitraryApiKeyDialog dialog{this};
if (dialog.exec() == QDialog::Accepted)
{
QJsonObject msg {
{ "type", "setValue" },
{ "key", dialog.apiKey() },
{ "value", dialog.value() }
};
if (dialog.sudo())
msg["sudo"] = true;
RequestDialog{std::move(msg), std::move(devices), this}.exec();
}
}
else if (selected == resetNvsKey)
{
bool ok{};
auto nvsKey = QInputDialog::getText(this, tr("Please input nvs key to be reset"), tr("Nvs key"), QLineEdit::Normal, {}, &ok);
if (!ok)
return;
QJsonObject msg {
{ "type", "nvsReset" },
{ "key", nvsKey },
{ "sudo", true }
};
RequestDialog{std::move(msg), std::move(devices), this}.exec();
}
else if (selected == actionOpenApps)
{
}
else if (selected == actionRemove)
{
removeRows(std::move(selectedRows));
}
}
void MainWindow::removeRows(QModelIndexList &&indexes)
{
if (QMessageBox::question(
this,
tr("Confirm deletion of devices"),
tr("Do you really want to remove %0 devices?").arg(indexes.count())
) != QMessageBox::Yes)
return;
int succeeded{}, failed{};
std::sort(indexes.begin(), indexes.end(),
[](const QModelIndex &a, const QModelIndex &b) {
return a.row() > b.row();
});
for (const QModelIndex &index : std::as_const(indexes))
if (m_model->removeRows(index.row(), 1, index.parent()))
succeeded++;
else
{
failed++;
qWarning() << "removing row" << index.row() << "failed";
}
if (succeeded > 0)
m_settings.setSerials(m_model->serials());
if (failed > 0)
QMessageBox::warning(this, tr("Error while removing!"), tr("%0 rows could not be removed!").arg(failed));
}
-40
View File
@@ -1,40 +0,0 @@
#pragma once
#include <QMainWindow>
#include <QAbstractItemModel>
#include <memory>
namespace Ui { class MainWindow; }
class QSslKey;
class QSslCertificate;
class QItemSelection;
class DevicesModel;
class QSortFilterProxyModel;
class FlottenUpdaterSettings;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(FlottenUpdaterSettings &settings, const QByteArray &username,
const QByteArray &password, QWidget *parent = nullptr);
~MainWindow() override;
private slots:
void doAdd();
void doRemove();
void doAddRange();
void selectionChanged();
void headerContextMenuRequested(const QPoint &pos);
void contextMenuRequested(const QPoint &pos);
private:
void removeRows(QModelIndexList &&indexes);
const std::unique_ptr<Ui::MainWindow> m_ui;
FlottenUpdaterSettings &m_settings;
const std::unique_ptr<DevicesModel> m_model;
const std::unique_ptr<QSortFilterProxyModel> m_proxyModel;
};
-92
View File
@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pushButtonConnectAll">
<property name="text">
<string>Connect All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonDisconnectAll">
<property name="text">
<string>Disconnect All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonAdd">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonRemove">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonAddRange">
<property name="text">
<string>Add range</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="treeView">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::ExtendedSelection</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
-19
View File
@@ -1,19 +0,0 @@
#include "requestdialog.h"
#include "ui_requestdialog.h"
#include <QJsonDocument>
#include <QJsonObject>
#include "deviceconnection.h"
#include "requestmodel.h"
RequestDialog::RequestDialog(QJsonObject &&msg, std::vector<std::shared_ptr<DeviceConnection>> &&devices, QWidget *parent) :
QDialog{parent},
m_ui{std::make_unique<Ui::RequestDialog>()},
m_model{std::make_unique<RequestModel>(std::move(msg), std::move(devices), this)}
{
m_ui->setupUi(this);
m_ui->treeView->setModel(m_model.get());
}
RequestDialog::~RequestDialog() = default;
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include <QDialog>
#include <memory>
class QJsonObject;
class DeviceConnection;
class RequestModel;
namespace Ui { class RequestDialog; }
class RequestDialog : public QDialog
{
Q_OBJECT
public:
explicit RequestDialog(QJsonObject &&msg, std::vector<std::shared_ptr<DeviceConnection>> &&devices, QWidget *parent = nullptr);
~RequestDialog();
private:
const std::unique_ptr<Ui::RequestDialog> m_ui;
const std::unique_ptr<RequestModel> m_model;
};
-71
View File
@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RequestDialog</class>
<widget class="QDialog" name="RequestDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Pending requests...</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
<item>
<widget class="QTreeView" name="treeView">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RequestDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RequestDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
-177
View File
@@ -1,177 +0,0 @@
#include "requestmodel.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QRandomGenerator>
#include <algorithm>
#include <utility>
#include "deviceconnection.h"
namespace {
enum {
ColumnSerial,
ColumnRequestId,
ColumnStatus,
ColumnMessage,
NumberOfColumns
};
QString getRandomString();
}
RequestModel::RequestModel(QJsonObject &&msg, std::vector<std::shared_ptr<DeviceConnection>> &&devices, QObject *parent) :
QAbstractTableModel{parent}
{
m_requests.reserve(devices.size());
for (auto &device : devices)
{
Request request {
.device = std::move(device),
.requestId = getRandomString()
};
connect(request.device.get(), &DeviceConnection::responseReceived, this, &RequestModel::responseReceived);
{
QJsonObject msg2 = msg;
msg2["requestId"] = request.requestId;
request.device->sendMessage(msg2);
}
m_requests.emplace_back(std::move(request));
}
}
RequestModel::~RequestModel() = default;
int RequestModel::rowCount(const QModelIndex &parent) const
{
return m_requests.size();
}
int RequestModel::columnCount(const QModelIndex &parent) const
{
return NumberOfColumns;
}
QVariant RequestModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_requests.size())
{
qWarning() << "row out of bounds";
return {};
}
const Request &request = *std::next(std::begin(m_requests), index.row());
switch (index.column())
{
case ColumnSerial:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return request.device->serial();
}
return {};
case ColumnRequestId:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return request.requestId;
}
return {};
case ColumnStatus:
switch (role)
{
case Qt::DisplayRole:
switch (request.status)
{
case Request::Status::Pending: return tr("Pending");
case Request::Status::Failed: return tr("Failed");
case Request::Status::Succeeded: return tr("Succeeded");
}
return QString::number(std::to_underlying(request.status));
case Qt::EditRole:
return std::to_underlying(request.status);
}
return {};
case ColumnMessage:
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return request.message;
}
return {};
}
return {};
}
QVariant RequestModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole && role != Qt::EditRole)
return {};
switch (orientation)
{
case Qt::Horizontal:
switch (section)
{
case ColumnSerial: return tr("Serial");
case ColumnRequestId: return tr("RequestId");
case ColumnStatus: return tr("Status");
case ColumnMessage: return tr("Message");
}
return {};
}
return {};
}
void RequestModel::responseReceived(const QString &requestId, const QJsonObject &msg)
{
qDebug() << requestId << msg;
auto iter = std::find_if(std::begin(m_requests), std::end(m_requests),
[&requestId](const auto &request){ return request.requestId == requestId; });
if (iter == std::end(m_requests))
{
qWarning() << "unknown request id" << requestId;
return;
}
const auto row = std::distance(std::begin(m_requests), iter);
if (msg.value("success").toBool())
{
iter->status = Request::Status::Succeeded;
emit dataChanged(index(row, ColumnStatus), index(row, ColumnStatus), { Qt::DisplayRole, Qt::EditRole });
}
else
{
iter->status = Request::Status::Failed;
iter->message = msg.value("message").toString();
emit dataChanged(index(row, ColumnStatus), index(row, ColumnMessage), { Qt::DisplayRole, Qt::EditRole });
}
}
namespace {
QString getRandomString()
{
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
const int randomStringLength = 12; // assuming you want random strings of 12 characters
QString randomString;
for(int i = 0; i<randomStringLength; ++i)
{
int index = QRandomGenerator::global()->bounded(possibleCharacters.length());
QChar nextChar = possibleCharacters.at(index);
randomString.append(nextChar);
}
return randomString;
}
}
-37
View File
@@ -1,37 +0,0 @@
#pragma once
#include <QObject>
#include <QAbstractTableModel>
#include <memory>
class QJsonObject;
class DeviceConnection;
class RequestModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit RequestModel(QJsonObject &&msg, std::vector<std::shared_ptr<DeviceConnection>> &&devices, QObject *parent = nullptr);
~RequestModel() override;
// QAbstractItemModel interface
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
private slots:
void responseReceived(const QString &requestId, const QJsonObject &msg);
private:
struct Request {
std::shared_ptr<DeviceConnection> device;
QString requestId;
enum class Status { Pending, Failed, Succeeded };
Status status{Status::Pending};
QString message;
};
std::vector<Request> m_requests;
};
@@ -1,61 +0,0 @@
#include "setarbitraryapikeydialog.h"
#include "ui_setarbitraryapikeydialog.h"
#include <QJsonDocument>
#include <QJsonParseError>
#include <QMessageBox>
#include <QJsonArray>
#include <QJsonObject>
SetArbitraryApiKeyDialog::SetArbitraryApiKeyDialog(QWidget *parent) :
QDialog{parent},
m_ui{std::make_unique<Ui::SetArbitraryApiKeyDialog>()}
{
m_ui->setupUi(this);
}
SetArbitraryApiKeyDialog::~SetArbitraryApiKeyDialog() = default;
QString SetArbitraryApiKeyDialog::apiKey() const
{
return m_ui->lineEditApiKey->text();
}
QJsonValue SetArbitraryApiKeyDialog::value() const
{
return m_value;
}
bool SetArbitraryApiKeyDialog::sudo() const
{
return m_ui->checkBoxSudo->isChecked();
}
void SetArbitraryApiKeyDialog::accept()
{
QJsonParseError error;
const auto doc = QJsonDocument::fromJson("[" + m_ui->plainTextEditValue->toPlainText().toUtf8() + "]", &error);
if (error.error != QJsonParseError::NoError)
{
QMessageBox::warning(this, tr("Could not parse JSON"), tr("Could not parse JSON") + "\n\n" + error.errorString());
return;
}
if (!doc.isArray())
{
QMessageBox::warning(this, tr("Unexpected not array!"), tr("Unexpected not array!"));
return;
}
const auto array = doc.array();
if (array.size() != 1)
{
QMessageBox::warning(this, tr("Unexpected array length!"), tr("Unexpected array length: %0!").arg(array.size()));
return;
}
m_value = array.first();
QDialog::accept();
}
@@ -1,28 +0,0 @@
#pragma once
#include <memory>
#include <QDialog>
#include <QJsonValue>
namespace Ui { class SetArbitraryApiKeyDialog; }
class SetArbitraryApiKeyDialog : public QDialog
{
Q_OBJECT
public:
explicit SetArbitraryApiKeyDialog(QWidget *parent = nullptr);
~SetArbitraryApiKeyDialog() override;
QString apiKey() const;
QJsonValue value() const;
bool sudo() const;
protected:
void accept() override;
private:
const std::unique_ptr<Ui::SetArbitraryApiKeyDialog> m_ui;
QJsonValue m_value;
};
-127
View File
@@ -1,127 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SetArbitraryApiKeyDialog</class>
<widget class="QDialog" name="SetArbitraryApiKeyDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>285</width>
<height>224</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="labelApiKey">
<property name="text">
<string>ApiKey:</string>
</property>
<property name="buddy">
<cstring>lineEditApiKey</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelValue">
<property name="text">
<string>Value:</string>
</property>
<property name="buddy">
<cstring>plainTextEditValue</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEditApiKey"/>
</item>
<item row="1" column="1">
<widget class="QPlainTextEdit" name="plainTextEditValue"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelSudo">
<property name="text">
<string>Sudo:</string>
</property>
<property name="buddy">
<cstring>checkBoxSudo</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="checkBoxSudo">
<property name="text">
<string>Enabled</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SetArbitraryApiKeyDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SetArbitraryApiKeyDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>