Remove flotten-updater
This commit is contained in:
+4
-7
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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};
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 <a href="https://auth.go-e.com/if/user/#/settings;{%22page%22:%22page-tokens%22}">our SSO</a>.</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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user