mirror of https://github.com/nymea/nymea.git
Add Zigbee settings and prepare network creation and loading
parent
61d0b96b42
commit
19216b5f04
|
|
@ -32,7 +32,7 @@
|
|||
#include "zigbee/zigbeemanager.h"
|
||||
#include "zigbee/zigbeeadapters.h"
|
||||
|
||||
#include <zigbeeadapter.h>
|
||||
#include <zigbeeuartadapter.h>
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
|
|
@ -41,19 +41,36 @@ ZigbeeHandler::ZigbeeHandler(ZigbeeManager *zigbeeManager, QObject *parent) :
|
|||
m_zigbeeManager(zigbeeManager)
|
||||
{
|
||||
registerEnum<ZigbeeManager::ZigbeeNetworkState>();
|
||||
registerEnum<Zigbee::ZigbeeBackendType>();
|
||||
registerEnum<ZigbeeManager::ZigbeeBackendType>();
|
||||
registerEnum<ZigbeeManager::ZigbeeError>();
|
||||
|
||||
registerObject<ZigbeeAdapter, ZigbeeAdapters>();
|
||||
|
||||
// Network object describing a network instance
|
||||
QVariantMap zigbeeNetworkDescription;
|
||||
zigbeeNetworkDescription.insert("id", enumValueName(Uuid));
|
||||
registerObject("ZigbeeNetwork", zigbeeNetworkDescription);
|
||||
|
||||
QVariantMap params, returns;
|
||||
QString description;
|
||||
|
||||
/* 1. GetNetworkStatus
|
||||
* 2. Setup network if the is no network configured
|
||||
* - GetAvailableAdapters
|
||||
* - Setup network with given UART interface and backend type
|
||||
* -
|
||||
*/
|
||||
// GetAdapters
|
||||
params.clear(); returns.clear();
|
||||
description = "Get the list of available ZigBee adapter candidates in order to set up the zigbee network on the desired serial interface. If an adapter has been recognized correctly as a supported hardware, the backendSuggestionAvailable property will be true and the configurations can be used as they where given, otherwise the user might set the backend type and baud rate manually.";
|
||||
returns.insert("adapters", objectRef<ZigbeeAdapters>());
|
||||
registerMethod("GetAdapters", description, params, returns);
|
||||
|
||||
// AdapterAdded notification
|
||||
params.clear();
|
||||
description = "Emitted whenever a new ZigBee adapter candidate has been detected in the system.";
|
||||
params.insert("adapter", objectRef<ZigbeeAdapter>());
|
||||
registerNotification("AdapterAdded", description, params);
|
||||
|
||||
// AdapterRemoved notification
|
||||
params.clear();
|
||||
description = "Emitted whenever a ZigBee adapter has been removed from the system (i.e. unplugged).";
|
||||
params.insert("adapters", objectRef<ZigbeeAdapter>());
|
||||
registerNotification("AdapterRemoved", description, params);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get the status of the current network.";
|
||||
|
|
@ -71,11 +88,25 @@ ZigbeeHandler::ZigbeeHandler(ZigbeeManager *zigbeeManager, QObject *parent) :
|
|||
returns.insert("networkState", enumRef<ZigbeeManager::ZigbeeNetworkState>());
|
||||
registerMethod("GetNetworkStatus", description, params, returns);
|
||||
|
||||
// GetAvailableAdapters
|
||||
params.clear(); returns.clear();
|
||||
description = "Get the available ZigBee adapters in order to set up the zigbee network on the approriate serial interface. If the adapter has been recognized correctly, the \"backendSuggestionAvailable\" will be true and the configurations can be used as they where given, otherwise the user might set the backend type and baud rate manually.";
|
||||
returns.insert("zigbeeAdapters", objectRef<ZigbeeAdapters>());
|
||||
registerMethod("GetAvailableAdapters", description, params, returns);
|
||||
connect(m_zigbeeManager, &ZigbeeManager::availableAdapterAdded, this, [this](const ZigbeeAdapter &adapter){
|
||||
QVariantMap params;
|
||||
params.insert("adapter", pack(adapter));
|
||||
emit AdapterAdded(params);
|
||||
});
|
||||
|
||||
connect(m_zigbeeManager, &ZigbeeManager::availableAdapterRemoved, this, [this](const ZigbeeAdapter &adapter){
|
||||
QVariantMap params;
|
||||
params.insert("adapter", pack(adapter));
|
||||
emit AdapterRemoved(params);
|
||||
});
|
||||
|
||||
/* 1. GetNetworkStatus
|
||||
* 2. Setup network if the is no network configured
|
||||
* - GetAvailableAdapters
|
||||
* - Setup network with given UART interface and backend type
|
||||
* -
|
||||
*/
|
||||
|
||||
|
||||
// GetNetworkStatus
|
||||
// NetworkStatusChanged
|
||||
|
|
@ -123,16 +154,17 @@ JsonReply *ZigbeeHandler::GetNetworkStatus(const QVariantMap ¶ms)
|
|||
return createReply(ret);
|
||||
}
|
||||
|
||||
JsonReply *ZigbeeHandler::GetAvailableAdapters(const QVariantMap ¶ms)
|
||||
JsonReply *ZigbeeHandler::GetAdapters(const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
|
||||
QVariantMap ret; QVariantList adapterList;
|
||||
QVariantMap returnMap;
|
||||
QVariantList adapterList;
|
||||
foreach (const ZigbeeAdapter &adapter, m_zigbeeManager->availableAdapters()) {
|
||||
adapterList << pack(adapter);
|
||||
}
|
||||
ret.insert("zigbeeAdapters", adapterList);
|
||||
return createReply(ret);
|
||||
returnMap.insert("adapters", adapterList);
|
||||
return createReply(returnMap);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,13 +50,14 @@ public:
|
|||
QString name() const override;
|
||||
|
||||
Q_INVOKABLE JsonReply *GetNetworkStatus(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *GetAvailableAdapters(const QVariantMap ¶ms);
|
||||
|
||||
Q_INVOKABLE JsonReply *GetAdapters(const QVariantMap ¶ms);
|
||||
|
||||
private:
|
||||
ZigbeeManager *m_zigbeeManager = nullptr;
|
||||
|
||||
signals:
|
||||
void AdapterAdded(const QVariantMap ¶ms);
|
||||
void AdapterRemoved(const QVariantMap ¶ms);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ HEADERS += nymeacore.h \
|
|||
cloud/cloudtransport.h \
|
||||
debugreportgenerator.h \
|
||||
platform/platform.h \ \
|
||||
zigbee/zigbeeadapter.h \
|
||||
zigbee/zigbeeadapters.h \
|
||||
zigbee/zigbeemanager.h
|
||||
|
||||
|
|
@ -217,6 +218,7 @@ SOURCES += nymeacore.cpp \
|
|||
cloud/cloudtransport.cpp \
|
||||
debugreportgenerator.cpp \
|
||||
platform/platform.cpp \
|
||||
zigbee/zigbeeadapter.cpp \
|
||||
zigbee/zigbeeadapters.cpp \
|
||||
zigbee/zigbeemanager.cpp
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "zigbeeadapter.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
ZigbeeAdapter::ZigbeeAdapter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString ZigbeeAdapter::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void ZigbeeAdapter::setName(const QString &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
QString ZigbeeAdapter::description() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
void ZigbeeAdapter::setDescription(const QString &description)
|
||||
{
|
||||
m_description = description;
|
||||
}
|
||||
|
||||
QString ZigbeeAdapter::systemLocation() const
|
||||
{
|
||||
return m_systemLocation;
|
||||
}
|
||||
|
||||
void ZigbeeAdapter::setSystemLocation(const QString &systemLocation)
|
||||
{
|
||||
m_systemLocation = systemLocation;
|
||||
}
|
||||
|
||||
bool ZigbeeAdapter::backendSuggestionAvailable() const
|
||||
{
|
||||
return m_backendSuggestionAvailable;
|
||||
}
|
||||
|
||||
void ZigbeeAdapter::setBackendSuggestionAvailable(bool backendSuggestionAvailable)
|
||||
{
|
||||
m_backendSuggestionAvailable = backendSuggestionAvailable;
|
||||
}
|
||||
|
||||
Zigbee::ZigbeeBackendType ZigbeeAdapter::suggestedZigbeeBackendType() const
|
||||
{
|
||||
return m_suggestedZigbeeBackendType;
|
||||
}
|
||||
|
||||
void ZigbeeAdapter::setSuggestedZigbeeBackendType(Zigbee::ZigbeeBackendType backendType)
|
||||
{
|
||||
m_suggestedZigbeeBackendType = backendType;
|
||||
}
|
||||
|
||||
qint32 ZigbeeAdapter::suggestedBaudRate() const
|
||||
{
|
||||
return m_suggestedBaudRate;
|
||||
}
|
||||
|
||||
void ZigbeeAdapter::setSuggestedBaudRate(qint32 baudRate)
|
||||
{
|
||||
m_suggestedBaudRate = baudRate;
|
||||
}
|
||||
|
||||
bool ZigbeeAdapter::operator==(const ZigbeeAdapter &other) const
|
||||
{
|
||||
return m_systemLocation == other.systemLocation()
|
||||
&& m_name == other.name()
|
||||
&& m_description == other.description()
|
||||
&& m_backendSuggestionAvailable == other.backendSuggestionAvailable()
|
||||
&& m_suggestedZigbeeBackendType == other.suggestedZigbeeBackendType()
|
||||
&& m_suggestedBaudRate == other.suggestedBaudRate();
|
||||
}
|
||||
|
||||
|
||||
QDebug operator<<(QDebug debug, const ZigbeeAdapter &adapter)
|
||||
{
|
||||
debug.nospace() << "ZigbeeAdapter(" << adapter.name() << " - " << adapter.description();
|
||||
debug.nospace() << ", " << adapter.systemLocation();
|
||||
if (adapter.backendSuggestionAvailable()) {
|
||||
debug.nospace() << "Suggested backend: " << adapter.suggestedZigbeeBackendType();
|
||||
debug.nospace() << ", " << adapter.suggestedBaudRate();
|
||||
}
|
||||
|
||||
debug.nospace() << ")";
|
||||
return debug.space();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef ZIGBEEADAPTER_H
|
||||
#define ZIGBEEADAPTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
|
||||
#include "zigbee.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
class ZigbeeAdapter
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString name READ name)
|
||||
Q_PROPERTY(QString description READ description)
|
||||
Q_PROPERTY(QString systemLocation READ systemLocation)
|
||||
Q_PROPERTY(bool backendSuggestionAvailable READ backendSuggestionAvailable)
|
||||
Q_PROPERTY(Zigbee::ZigbeeBackendType suggestedZigbeeBackendType READ suggestedZigbeeBackendType)
|
||||
Q_PROPERTY(qint32 suggestedBaudRate READ suggestedBaudRate)
|
||||
|
||||
public:
|
||||
explicit ZigbeeAdapter();
|
||||
|
||||
QString name() const;
|
||||
void setName(const QString &name);
|
||||
|
||||
QString description() const;
|
||||
void setDescription(const QString &description);
|
||||
|
||||
QString systemLocation() const;
|
||||
void setSystemLocation(const QString &systemLocation);
|
||||
|
||||
bool backendSuggestionAvailable() const;
|
||||
void setBackendSuggestionAvailable(bool backendSuggestionAvailable);
|
||||
|
||||
Zigbee::ZigbeeBackendType suggestedZigbeeBackendType() const;
|
||||
void setSuggestedZigbeeBackendType(Zigbee::ZigbeeBackendType backendType);
|
||||
|
||||
qint32 suggestedBaudRate() const;
|
||||
void setSuggestedBaudRate(qint32 baudRate);
|
||||
|
||||
bool operator==(const ZigbeeAdapter &other) const;
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
QString m_description;
|
||||
QString m_systemLocation;
|
||||
|
||||
bool m_backendSuggestionAvailable = false;
|
||||
Zigbee::ZigbeeBackendType m_suggestedZigbeeBackendType = Zigbee::ZigbeeBackendTypeDeconz;
|
||||
qint32 m_suggestedBaudRate = 38400;
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug debug, const ZigbeeAdapter &adapter);
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(nymeaserver::ZigbeeAdapter)
|
||||
|
||||
#endif // ZIGBEEADAPTER_H
|
||||
|
||||
|
|
@ -32,7 +32,8 @@
|
|||
#define ZIGBEEADAPTERS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <zigbeeadapter.h>
|
||||
|
||||
#include "zigbeeadapter.h"
|
||||
|
||||
namespace nymeaserver {
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,39 @@ namespace nymeaserver {
|
|||
|
||||
ZigbeeManager::ZigbeeManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
// Adapter monitor
|
||||
qCDebug(dcZigbee()) << "Initialize the ZigBee manager";
|
||||
m_adapterMonitor = new ZigbeeUartAdapterMonitor(this);
|
||||
if (!m_adapterMonitor->isValid()) {
|
||||
qCWarning(dcZigbee()) << "Could not initialize the ZigBee adapter monitor.";
|
||||
// Lets continue anyways, maybe we can set up existing networks right the way.
|
||||
}
|
||||
|
||||
foreach(const ZigbeeUartAdapter &uartAdapter, m_adapterMonitor->availableAdapters()) {
|
||||
m_adapters.append(createAdapterFromUartAdapter(uartAdapter));
|
||||
}
|
||||
|
||||
connect(m_adapterMonitor, &ZigbeeUartAdapterMonitor::adapterAdded, this, [this](const ZigbeeUartAdapter &uartAdapter){
|
||||
ZigbeeAdapter adapter = createAdapterFromUartAdapter(uartAdapter);
|
||||
m_adapters.append(adapter);
|
||||
emit availableAdapterAdded(adapter);
|
||||
});
|
||||
|
||||
connect(m_adapterMonitor, &ZigbeeUartAdapterMonitor::adapterRemoved, this, [this](const ZigbeeUartAdapter &uartAdapter){
|
||||
foreach (const ZigbeeAdapter &adapter, m_adapters) {
|
||||
if (adapter.systemLocation() == uartAdapter.systemLocation()) {
|
||||
m_adapters.removeAll(adapter);
|
||||
emit availableAdapterRemoved(adapter);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Load zigbee networks from settings
|
||||
NymeaSettings settings(NymeaSettings::SettingsRoleZigbee);
|
||||
|
||||
|
||||
|
||||
// TODO: load platform configuration for networks we know for sure how they work
|
||||
}
|
||||
|
||||
bool ZigbeeManager::available() const
|
||||
|
|
@ -61,7 +93,7 @@ ZigbeeNetwork *ZigbeeManager::zigbeeNetwork() const
|
|||
|
||||
ZigbeeAdapters ZigbeeManager::availableAdapters()
|
||||
{
|
||||
return ZigbeeNetworkManager::availableAdapters();
|
||||
return m_adapters;
|
||||
}
|
||||
|
||||
void ZigbeeManager::createZigbeeNetwork(const QString &serialPort, qint32 baudrate, Zigbee::ZigbeeBackendType backend)
|
||||
|
|
@ -75,10 +107,21 @@ void ZigbeeManager::createZigbeeNetwork(const QString &serialPort, qint32 baudra
|
|||
m_zigbeeNetwork->setSerialPortName(serialPort);
|
||||
m_zigbeeNetwork->setSerialBaudrate(baudrate);
|
||||
m_zigbeeNetwork->setSettingsFileName(NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName());
|
||||
|
||||
m_zigbeeNetwork->startNetwork();
|
||||
|
||||
emit zigbeeNetworkChanged(m_zigbeeNetwork);
|
||||
}
|
||||
|
||||
ZigbeeAdapter ZigbeeManager::createAdapterFromUartAdapter(const ZigbeeUartAdapter &uartAdapter)
|
||||
{
|
||||
ZigbeeAdapter adapter;
|
||||
adapter.setName(uartAdapter.name());
|
||||
adapter.setSystemLocation(uartAdapter.systemLocation());
|
||||
adapter.setDescription(uartAdapter.description());
|
||||
adapter.setBackendSuggestionAvailable(uartAdapter.backendSuggestionAvailable());
|
||||
adapter.setSuggestedZigbeeBackendType(uartAdapter.suggestedZigbeeBackendType());
|
||||
adapter.setSuggestedBaudRate(uartAdapter.suggestedBaudRate());
|
||||
return adapter;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <QObject>
|
||||
|
||||
#include <zigbeenetworkmanager.h>
|
||||
#include <zigbeeuartadaptermonitor.h>
|
||||
|
||||
#include "zigbeeadapters.h"
|
||||
|
||||
|
|
@ -43,6 +44,12 @@ class ZigbeeManager : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ZigbeeBackendType {
|
||||
ZigbeeBackendTypeDconz,
|
||||
ZigbeeBackendTypeNxp
|
||||
};
|
||||
Q_ENUM(ZigbeeBackendType)
|
||||
|
||||
enum ZigbeeNetworkState {
|
||||
ZigbeeNetworkStateOffline,
|
||||
ZigbeeNetworkStateUpdating,
|
||||
|
|
@ -51,6 +58,13 @@ public:
|
|||
};
|
||||
Q_ENUM(ZigbeeNetworkState)
|
||||
|
||||
enum ZigbeeError {
|
||||
ZigbeeErrorNoError,
|
||||
ZigbeeErrorAdapterNotAvailable,
|
||||
ZigbeeErrorAdapterAlreadyInUse
|
||||
};
|
||||
Q_ENUM(ZigbeeError)
|
||||
|
||||
explicit ZigbeeManager(QObject *parent = nullptr);
|
||||
|
||||
bool available() const;
|
||||
|
|
@ -62,9 +76,17 @@ public:
|
|||
void createZigbeeNetwork(const QString &serialPort, qint32 baudrate, Zigbee::ZigbeeBackendType backend);
|
||||
|
||||
private:
|
||||
ZigbeeAdapters m_adapters;
|
||||
ZigbeeUartAdapterMonitor *m_adapterMonitor = nullptr;
|
||||
|
||||
ZigbeeNetwork *m_zigbeeNetwork = nullptr;
|
||||
|
||||
ZigbeeAdapter createAdapterFromUartAdapter(const ZigbeeUartAdapter &uartAdapter);
|
||||
|
||||
signals:
|
||||
void availableAdapterAdded(const ZigbeeAdapter &adapter);
|
||||
void availableAdapterRemoved(const ZigbeeAdapter &adapter);
|
||||
|
||||
void zigbeeNetworkChanged(ZigbeeNetwork *zigbeeNetwork);
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ NymeaSettings::NymeaSettings(const SettingsRole &role, QObject *parent):
|
|||
case SettingsRoleIOConnections:
|
||||
fileName = "ioconnections.conf";
|
||||
break;
|
||||
case SettingsRoleZigbee:
|
||||
fileName = "zigbee.conf";
|
||||
break;
|
||||
}
|
||||
m_settings = new QSettings(basePath + settingsPrefix + fileName, QSettings::IniFormat, this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public:
|
|||
SettingsRoleTags,
|
||||
SettingsRoleMqttPolicies,
|
||||
SettingsRoleIOConnections,
|
||||
SettingsRoleZigbee,
|
||||
};
|
||||
|
||||
explicit NymeaSettings(const SettingsRole &role = SettingsRoleNone, QObject *parent = nullptr);
|
||||
|
|
|
|||
Loading…
Reference in New Issue