mirror of https://github.com/nymea/nymea.git
490 lines
23 KiB
C++
490 lines
23 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* *
|
|
* Copyright (C) 2015-2018 Simon Stürz <simon.stuerz@guh.io> *
|
|
* Copyright (C) 2014 Michael Zanetti <michael_zanetti@gmx.net> *
|
|
* *
|
|
* This file is part of nymea. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Lesser General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2.1 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library 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 *
|
|
* Lesser General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Lesser General Public *
|
|
* License along with this library; If not, see *
|
|
* <http://www.gnu.org/licenses/>. *
|
|
* *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
/*!
|
|
\class DevicePlugin
|
|
\brief This is the base class interface for device plugins.
|
|
|
|
\ingroup devices
|
|
\inmodule libnymea
|
|
|
|
*/
|
|
|
|
|
|
/*! \fn void DevicePlugin::configValueChanged(const ParamTypeId ¶mTypeId, const QVariant &value);
|
|
This signal is emitted when the \l{Param} with a certain \a paramTypeId of a \l{Device} configuration changed the \a value.
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::autoDevicesAppeared(const DeviceDescriptors &deviceDescriptors)
|
|
A plugin emits this signal when new \l{Device} appeared. For instance a plugin connected to a bridge might detect
|
|
that new devices have been connected to the bridge. Emitting this signal will cause those devices to be added
|
|
to the nymea system and setupDevice will be called for them.
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::autoDeviceDisappeared(const DeviceId &id)
|
|
A plugin emits this signal when a device with the given \a id and which was created by \l{DevicePlugin::autoDevicesAppeared}
|
|
has been removed from the system. When emitting this signal, nymea will remove the device from the system and with it all the
|
|
associated rules and child devices. Because of this, this signal should only be emitted when it's certain that the given device
|
|
will never return to be available any more.
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::emitEvent(const Event &event)
|
|
To produce a new event in the system, a plugin creates a new \l{Event} and emits this signal it with that \a event.
|
|
Usually events are emitted in response to incoming data or other other events happening. Find a configured
|
|
\l{Device} from the \l{DeviceManager} and get its \l{EventType}{EventTypes}, then
|
|
create a \l{Event} complying to that \l{EventType} and emit it here.
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::init()
|
|
This will be called after constructing the DevicePlugin. Override this to do any
|
|
initialisation work you need to do.
|
|
*/
|
|
|
|
#include "deviceplugin.h"
|
|
#include "devicemanager.h"
|
|
#include "deviceutils.h"
|
|
#include "loggingcategories.h"
|
|
#include "devicediscoveryinfo.h"
|
|
#include "devicesetupinfo.h"
|
|
#include "devicepairinginfo.h"
|
|
#include "deviceactioninfo.h"
|
|
#include "browseresult.h"
|
|
#include "browseritemresult.h"
|
|
#include "browseractioninfo.h"
|
|
#include "browseritemactioninfo.h"
|
|
|
|
#include "nymeasettings.h"
|
|
|
|
#include "hardware/radio433/radio433.h"
|
|
#include "network/upnp/upnpdiscovery.h"
|
|
|
|
#include <QDebug>
|
|
#include <QFileInfo>
|
|
#include <QFile>
|
|
#include <QDir>
|
|
#include <QCoreApplication>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QStandardPaths>
|
|
|
|
/*! DevicePlugin constructor. DevicePlugins will be instantiated by the DeviceManager, its \a parent. */
|
|
DevicePlugin::DevicePlugin(QObject *parent):
|
|
QObject(parent)
|
|
{
|
|
}
|
|
|
|
/*! Virtual destructor... */
|
|
DevicePlugin::~DevicePlugin()
|
|
{
|
|
|
|
}
|
|
|
|
/*! Returns the name of this DevicePlugin. */
|
|
QString DevicePlugin::pluginName() const
|
|
{
|
|
return m_metaData.pluginName();
|
|
}
|
|
|
|
/*! Returns the displayName of this DevicePlugin, to be shown to the user, translated. */
|
|
QString DevicePlugin::pluginDisplayName() const
|
|
{
|
|
return m_metaData.pluginDisplayName();
|
|
}
|
|
|
|
/*! Returns the id of this DevicePlugin.
|
|
* When implementing a plugin, generate a new uuid and return it here. Always return the
|
|
* same uuid and don't change it or configurations can't be matched any more. */
|
|
PluginId DevicePlugin::pluginId() const
|
|
{
|
|
return m_metaData.pluginId();
|
|
}
|
|
|
|
/*! Returns the list of \l{Vendor}{Vendors} supported by this DevicePlugin. */
|
|
Vendors DevicePlugin::supportedVendors() const
|
|
{
|
|
return m_metaData.vendors();
|
|
}
|
|
|
|
/*! Return a list of \l{DeviceClass}{DeviceClasses} describing all the devices supported by this plugin.
|
|
If a DeviceClass has an invalid parameter it will be ignored.
|
|
*/
|
|
DeviceClasses DevicePlugin::supportedDevices() const
|
|
{
|
|
return m_metaData.deviceClasses();
|
|
}
|
|
|
|
/*! Override this if your plugin supports Device with DeviceClass::CreationMethodAuto.
|
|
This will be called at startup, after the configured devices have been loaded.
|
|
This is the earliest time you should start emitting autoDevicesAppeared(). If you
|
|
are monitoring some hardware/service for devices to appear, start monitoring now.
|
|
If you are building the devices based on a static list, you may emit
|
|
autoDevicesAppeard() in here.
|
|
*/
|
|
void DevicePlugin::startMonitoringAutoDevices()
|
|
{
|
|
|
|
}
|
|
|
|
/*! A plugin must reimplement this if it supports a DeviceClass with createMethod \l{DeviceManager}{CreateMethodDiscovery}.
|
|
When the nymea system needs to discover available devices, this will be called on the plugin. The plugin implementation
|
|
is set to discover devices for the \l{DeviceClassId} given in the \a info object.
|
|
When devices are discovered, they should be added to the info object by calling \l{DeviceDiscoveryInfo::addDeviceDescriptor}.
|
|
Once the discovery is complete a plugin must finish it by calling \l{DeviceDiscoveryInfo::finish} using \l{Device::DeviceErrorNoError}
|
|
in case of success, or a matching error code otherwise. An optional display message can be passed optionally which might be shown
|
|
to the user, indicating more details about the error. The displayMessage must be made translatable by wrapping it in a QT_TR_NOOP()
|
|
statement.
|
|
|
|
A discovery might be cancelled by nymea. In which case the \l{DeviceDiscoveryInfo::aborted} signal will be emitted.
|
|
The info object must not be accessed after \l{DeviceDiscoveryInfo::destroyed} is emitted.
|
|
*/
|
|
void DevicePlugin::discoverDevices(DeviceDiscoveryInfo *info)
|
|
{
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! This will be called when a new device is created. The plugin can do a setup of the device by reimplementing this method.
|
|
The passed \a info object will contain the information about the new \l{Device}. When the setup is completed, a plugin
|
|
must finish it by calling \l{DeviceSetupInfo::finish} on the \a info object. In case of success, \{Device::DeviceErrorNoError}
|
|
must be used, or an appropriate \l{Device::DeviceError} in case of failure. An optional display message can be passed optionally which might be shown
|
|
to the user, indicating more details about the error. The displayMessage must be made translatable by wrapping it in a QT_TR_NOOP()
|
|
statement.
|
|
|
|
A setup might be cancelled by nymea. In which case the \{DeviceSetupInfo::aborted} signal will be emitted.
|
|
The info object must not be accessed after \l{DeviceSetupInfo::destroyed} is emitted.
|
|
*/
|
|
void DevicePlugin::setupDevice(DeviceSetupInfo *info)
|
|
{
|
|
info->finish(Device::DeviceErrorNoError);
|
|
}
|
|
|
|
/*! This will be called when a new \a device was added successfully and the device setup is finished. A plugin can optionally
|
|
trigger additional code to operate on the device by reimplementing this method.*/
|
|
void DevicePlugin::postSetupDevice(Device *device)
|
|
{
|
|
Q_UNUSED(device)
|
|
}
|
|
|
|
/*! This will be called when a \a device removed. The plugin has the chance to do some teardown.
|
|
The device is still valid during this call, but already removed from the system.
|
|
The device will be deleted as soon as this method returns.
|
|
*/
|
|
void DevicePlugin::deviceRemoved(Device *device)
|
|
{
|
|
Q_UNUSED(device)
|
|
}
|
|
|
|
/*! This method will be called to initiate a pairing. The plugin can do a initialisation for an upcoming pairing process.
|
|
Depending on the setupMethod of a device class, different actions may be required here.
|
|
SetupMethodDisplayPin should trigger the device to display a pin that will be entered in the client.
|
|
SetupMethodOAuth should generate the OAuthUrl which will be opened on the client to allow the user logging in and obtain
|
|
the OAuth code.
|
|
SetupMethodEnterPin, SetupMethodPushButton and SetupMethodUserAndPassword will typically not require to do anything here.
|
|
It is not required to reimplement this method for those setup methods, however, a Plugin reimplementing it must call
|
|
\l{DevicePairingInfo::finish}{finish()} on the \l{DevicePairingInfo} object and can provide an optional displayMessage which
|
|
might be presented to the user. Those strings need to be wrapped in QT_TR_NOOP() in order to be translatable for the client's
|
|
locale.
|
|
*/
|
|
void DevicePlugin::startPairing(DevicePairingInfo *info)
|
|
{
|
|
DeviceClass deviceClass = m_metaData.deviceClasses().findById(info->deviceClassId());
|
|
if (!deviceClass.isValid()) {
|
|
info->finish(Device::DeviceErrorDeviceClassNotFound);
|
|
return;
|
|
}
|
|
switch (deviceClass.setupMethod()) {
|
|
case DeviceClass::SetupMethodJustAdd:
|
|
info->finish(Device::DeviceErrorSetupMethodNotSupported);
|
|
return;
|
|
case DeviceClass::SetupMethodEnterPin:
|
|
case DeviceClass::SetupMethodPushButton:
|
|
case DeviceClass::SetupMethodUserAndPassword:
|
|
info->finish(Device::DeviceErrorNoError);
|
|
return;
|
|
case DeviceClass::SetupMethodDisplayPin:
|
|
case DeviceClass::SetupMethodOAuth:
|
|
// Those need to be handled by the plugin or it'll fail anyways.
|
|
qCWarning(dcDevice()) << "StartPairing called but Plugin does not reimplement it.";
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
}
|
|
|
|
/*! Confirms the pairing of the given \a info. \a username and \a secret are filled in depending on the setupmethod of the device class.
|
|
\a username will be used for SetupMethodUserAndPassword. \a secret will be used for SetupMethodUserAndPassword, SetupMethodDisplayPin
|
|
and SetupMethodOAuth.
|
|
Once the pairing is completed, the plugin implementation should call the info's finish() method reporting about the status of
|
|
the pairing operation. The optional displayMessage needs to be wrapped in QT_TR_NOOP in order to be translatable to the client's
|
|
locale.
|
|
*/
|
|
void DevicePlugin::confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret)
|
|
{
|
|
Q_UNUSED(username)
|
|
Q_UNUSED(secret)
|
|
|
|
qCWarning(dcDeviceManager) << "Plugin does not implement pairing.";
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! This will be called to execute actions on the device. The given \a info object contains
|
|
information about the target \l{Device} and the \l{Action} to be executed.
|
|
|
|
When the execution is completed, a plugin must finish it by calling \l{DeviceActionInfo::finish} on the \a info
|
|
object. In case of success, \l{Device::DeviceErrorNoError} must be used, or an appropriate \l{Device::DeviceError}
|
|
in case of failure. An optional display message can be passed which might be shown
|
|
to the user, indicating more details about the error. The displayMessage must be made translatable by wrapping it in a QT_TR_NOOP()
|
|
statement.
|
|
|
|
An action execution might be cancelled by nymea. In which case the \l{DeviceActionInfo::aborted} signal will be emitted.
|
|
The info object must not be accessed after \l{DeviceActionInfo::destroyed} is emitted.
|
|
*/
|
|
void DevicePlugin::executeAction(DeviceActionInfo *info)
|
|
{
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! A plugin must implement this if devices support browsing ("browsable" being true in the metadata JSON).
|
|
When the system calls this method, the \a result must be filled with entries from the browser using
|
|
\l{BrowseResult::addItems}. The \a info object will contain information about which device and which item/node
|
|
should be browsed. If the itemId is empty it means that the root node of the file system should be returned otherwise
|
|
all the children of the given item/node should be returned.
|
|
|
|
|
|
Each item in the result set shall be uniquely identifiable using its \l{BrowserItem::id}{id} property.
|
|
The system might call this method again, with an itemId returned in a previous query, provided
|
|
that item's \l{BrowserItem::browsable} property is true. In this case all children of the given
|
|
item shall be returned. All browser \l{BrowserItem::displayName} properties shall be localized
|
|
using the given locale in the \a info object.
|
|
|
|
If a returned item's \l{BrowserItem::executable} property is set to true, the system might call \l{DevicePlugin::executeBrowserAction}
|
|
for this itemId.
|
|
|
|
|
|
An item might have additional actions which must be defined in the plugin metadata JSON as "browserItemActionTypes". Such actions
|
|
might be context actions to items in a browser. For instance, a file browser might add copy/cut/paste actions to an item. The
|
|
system might call \l{DevicePlugin::executeBrowserItemAction} on such items.
|
|
|
|
When done, the browse result must be completed by calling \l{BrowserResult::finish} with \l{Device::DeviceErrorNoError}
|
|
in case of success or an appropriate \l{Device::DeviceError} otherwise. An optional display message can be passed which might be shown
|
|
to the user, indicating more details about the error. The displayMessage must be made translatable by wrapping it in a QT_TR_NOOP()
|
|
statement.
|
|
*/
|
|
void DevicePlugin::browseDevice(BrowseResult *result)
|
|
{
|
|
qCWarning(dcDevice()) << "Device claims" << result->device()->deviceClass().name() << "to be browsable but plugin does not reimplement browseDevice!";
|
|
result->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! A plugin must implement this if devices support browsing ("browsable" being true in the metadata JSON).
|
|
When the system calls this method, the \a result must be filled with a single item identified by the \l{DeviceItemResult::itemId}
|
|
using \l{BrowseResult::addItem}. The \a result object will contain information about which device and which item/node
|
|
should be browsed. If the itemId is empty it means that the root node of the file system should be returned.
|
|
|
|
Each item in the result set shall be uniquely identifiable using its \l{BrowserItem::id}{id} property. The system might
|
|
call this method again, with an itemId returned in a previous query, provided that item's \l{BrowserItem::browsable}
|
|
property is true. In this case all children of the given item shall be returned. All browser \l{BrowserItem::displayName} properties shall be localized
|
|
using the given locale in the \a info object.
|
|
|
|
|
|
When done, the browse result must be completed by calling \l{BrowserItemResult::finish} with \l{Device::DeviceErrorNoError}
|
|
in case of success or an appropriate \l{Device::DeviceError} otherwise. An optional display message can be passed which might be shown
|
|
to the user, indicating more details about the error. The displayMessage must be made translatable by wrapping it in a QT_TR_NOOP()
|
|
statement.
|
|
*/
|
|
void DevicePlugin::browserItem(BrowserItemResult *result)
|
|
{
|
|
qCWarning(dcDevice()) << "Device claims" << result->device()->deviceClass().name() << "to be browsable but plugin does not reimplement browserItem!";
|
|
result->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! This will be called to execute browser items on the device. For instance, a file browser might execute a file here.
|
|
The given \a info object contains information about the target \l{Device} and the \l{BrowserAction} to be executed.
|
|
|
|
When the execution is completed, a plugin must finish it by calling \l{BrowserActionInfo::finish} on the \a info
|
|
object. In case of success, \{Device::DeviceErrorNoError} must be used, or an appropriate \l{Device::DeviceError}
|
|
in case of failure. An optional display message can be passed which might be shown
|
|
to the user, indicating more details about the error. The displayMessage must be made translatable by wrapping it in a QT_TR_NOOP()
|
|
statement.
|
|
|
|
An action execution might be cancelled by nymea. In which case the \{BrowserActionInfo::aborted} signal will be emitted.
|
|
The info object must not be accessed after \l{BrowsereActionInfo::destroyed} is emitted.
|
|
*/
|
|
void DevicePlugin::executeBrowserItem(BrowserActionInfo *info)
|
|
{
|
|
qCWarning(dcDevice()) << "Device claims" << info->device()->deviceClass().name() << "to be browsable but plugin does not reimplement browserItem!";
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! This will be called to execute browser item actions on the device. For instance a file browser might have
|
|
"browserItemActionTypes" defined in the JSON in order to support context options like copy/cut/paste.
|
|
The given \a info object contains information about the target \l{Device} and the \l{BrowserItemAction} to be executed.
|
|
|
|
When the execution is completed, a plugin must finish it by calling \l{BrowserItemActionInfo::finish} on the \a info
|
|
object. In case of success, \l{Device::DeviceErrorNoError} must be used, or an appropriate \l{Device::DeviceError}
|
|
in case of failure. An optional display message can be passed which might be shown
|
|
to the user, indicating more details about the error. The displayMessage must be made translatable by wrapping it in a QT_TR_NOOP()
|
|
statement.
|
|
|
|
An action execution might be cancelled by nymea. In which case the \{BrowserItemActionInfo::aborted} signal will be emitted.
|
|
The info object must not be accessed after \l{BrowserItemActionInfo::destroyed} is emitted.
|
|
*/
|
|
void DevicePlugin::executeBrowserItemAction(BrowserItemActionInfo *info)
|
|
{
|
|
qCWarning(dcDevice()) << "Device claims" << info->device()->deviceClass().name() << "to be browsable but plugin does not reimplement browserItemAction!";
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! Returns the configuration description of this DevicePlugin as a list of \l{ParamType}{ParamTypes}. */
|
|
ParamTypes DevicePlugin::configurationDescription() const
|
|
{
|
|
return m_metaData.pluginSettings();
|
|
}
|
|
|
|
void DevicePlugin::initPlugin(const PluginMetadata &metadata, DeviceManager *deviceManager, HardwareManager *hardwareManager)
|
|
{
|
|
m_metaData = metadata;
|
|
m_deviceManager = deviceManager;
|
|
m_hardwareManager = hardwareManager;
|
|
m_storage = new QSettings(NymeaSettings::settingsPath() + "/pluginconfig-" + pluginId().toString().remove(QRegExp("[{}]")) + ".conf", QSettings::IniFormat, this);
|
|
}
|
|
|
|
/*! Returns a map containing the plugin configuration.
|
|
When implementing a new plugin, override this and fill in the empty configuration if your plugin requires any.
|
|
*/
|
|
ParamList DevicePlugin::configuration() const
|
|
{
|
|
return m_config;
|
|
}
|
|
|
|
/*! Use this to retrieve the values for your parameters. Values might not be set
|
|
at the time when your plugin is loaded, but will be set soon after. Listen to
|
|
configurationValueChanged() to know when something changes.
|
|
When implementing a new plugin, specify in configurationDescription() what you want to see here.
|
|
Returns the config value of a \l{Param} with the given \a paramTypeId of this DevicePlugin.
|
|
*/
|
|
QVariant DevicePlugin::configValue(const ParamTypeId ¶mTypeId) const
|
|
{
|
|
return m_config.paramValue(paramTypeId);
|
|
}
|
|
|
|
/*! Will be called by the DeviceManager to set a plugin's \a configuration. */
|
|
Device::DeviceError DevicePlugin::setConfiguration(const ParamList &configuration)
|
|
{
|
|
foreach (const Param ¶m, configuration) {
|
|
qCDebug(dcDeviceManager()) << "* Set plugin configuration" << param;
|
|
Device::DeviceError result = setConfigValue(param.paramTypeId(), param.value());
|
|
if (result != Device::DeviceErrorNoError)
|
|
return result;
|
|
|
|
}
|
|
return Device::DeviceErrorNoError;
|
|
}
|
|
|
|
/*! Can be called in the DevicePlugin to set a plugin's \l{Param} with the given \a paramTypeId and \a value. */
|
|
Device::DeviceError DevicePlugin::setConfigValue(const ParamTypeId ¶mTypeId, const QVariant &value)
|
|
{
|
|
bool found = false;
|
|
foreach (const ParamType ¶mType, configurationDescription()) {
|
|
if (paramType.id() == paramTypeId) {
|
|
found = true;
|
|
Device::DeviceError result = DeviceUtils::verifyParam(paramType, Param(paramTypeId, value));
|
|
if (result != Device::DeviceErrorNoError)
|
|
return result;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
qCWarning(dcDeviceManager()) << QString("Could not find plugin parameter with the id %1.").arg(paramTypeId.toString());
|
|
return Device::DeviceErrorInvalidParameter;
|
|
}
|
|
|
|
if (m_config.hasParam(paramTypeId)) {
|
|
if (!m_config.setParamValue(paramTypeId, value)) {
|
|
qCWarning(dcDeviceManager()) << "Could not set param value" << value << "for param with id" << paramTypeId.toString();
|
|
return Device::DeviceErrorInvalidParameter;
|
|
}
|
|
} else {
|
|
m_config.append(Param(paramTypeId, value));
|
|
}
|
|
|
|
emit configValueChanged(paramTypeId, value);
|
|
return Device::DeviceErrorNoError;
|
|
}
|
|
|
|
bool DevicePlugin::isBuiltIn() const
|
|
{
|
|
return m_metaData.isBuiltIn();
|
|
}
|
|
|
|
/*! Returns a list of all configured devices belonging to this plugin. */
|
|
Devices DevicePlugin::myDevices() const
|
|
{
|
|
QList<Device*> ret;
|
|
foreach (Device *device, m_deviceManager->configuredDevices()) {
|
|
if (device->pluginId() == pluginId()) {
|
|
ret.append(device);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*! Returns the pointer to the main \l{HardwareManager} of this server. */
|
|
HardwareManager *DevicePlugin::hardwareManager() const
|
|
{
|
|
return m_hardwareManager;
|
|
}
|
|
|
|
/*! Returns a pointer to a QSettings object which is reserved for this plugin.
|
|
The plugin can store arbitrary data in this.
|
|
*/
|
|
QSettings* DevicePlugin::pluginStorage() const
|
|
{
|
|
return m_storage;
|
|
}
|
|
|
|
void DevicePlugin::setMetaData(const PluginMetadata &metaData)
|
|
{
|
|
m_metaData = metaData;
|
|
}
|
|
|
|
DevicePlugins::DevicePlugins()
|
|
{
|
|
|
|
}
|
|
|
|
DevicePlugins::DevicePlugins(const QList<DevicePlugin *> &other): QList<DevicePlugin *>(other)
|
|
{
|
|
|
|
}
|
|
|
|
DevicePlugin *DevicePlugins::findById(const PluginId &id) const
|
|
{
|
|
foreach (DevicePlugin *plugin, *this) {
|
|
if (plugin->pluginId() == id) {
|
|
return plugin;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|