mirror of https://github.com/nymea/nymea.git
459 lines
20 KiB
C++
459 lines
20 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::devicesDiscovered(const DeviceClassId &deviceClassId, const QList<DeviceDescriptor> &devices);
|
|
Emit this signal when the discovery of a \a deviceClassId of this DevicePlugin is finished. The \a devices parameter describes the
|
|
list of \l{DeviceDescriptor}{DeviceDescriptors} of all discovered \l{Device}{Devices}.
|
|
Note: During a discovery a plugin should always return the full result set. So even if a device is already known to the system and
|
|
a later discovery finds the device again, it should be included in the result set but the DeviceDescriptor's deviceId should be set
|
|
to the device ID.
|
|
\sa discoverDevices()
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::pairingFinished(const PairingTransactionId &pairingTransactionId, Device::DeviceSetupStatus status);
|
|
This signal is emitted when the pairing of a \a pairingTransactionId is finished.
|
|
The \a status of the will be described as \l{Device::DeviceError}{DeviceError}.
|
|
\sa confirmPairing()
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::deviceSetupFinished(Device *device, Device::DeviceSetupStatus status);
|
|
This signal is emitted when the setup of a \a device in this DevicePlugin is finished. The \a status parameter describes the
|
|
\l{Device::DeviceError}{DeviceError} that occurred.
|
|
*/
|
|
|
|
/*! \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::actionExecutionFinished(const ActionId &id, Device::DeviceError status)
|
|
This signal is to be emitted when you previously have returned \l{DeviceManager}{DeviceErrorAsync}
|
|
in a call of executeAction(). The \a id refers to the executed \l{Action}. The \a status of the \l{Action}
|
|
execution will be described as \l{Device::DeviceError}{DeviceError}.
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList<DeviceDescriptor> &deviceDescriptors)
|
|
This signal is emitted when a new \l{Device} of certain \a deviceClassId appeared. The description of the \l{Device}{Devices}
|
|
will be in \a deviceDescriptors. This signal can only emitted from devices with the \l{DeviceClass}{CreateMethodAuto}.
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::autoDeviceDisappeared(const DeviceId &id)
|
|
Emit this signal when a device with the given \a id and which was created by \l{DevicePlugin::autoDevicesAppeared} has been removed from the system.
|
|
Be careful with this, as this will completely remove the device from the system and with it all the associated rules. Only
|
|
emit this if you are sure that a device will never come back. This signal should not be emitted for child auto devices
|
|
when the parent who created them is removed. The system will automatically remove all child devices in such a case.
|
|
*/
|
|
|
|
/*! \fn void DevicePlugin::emitEvent(const Event &event)
|
|
To produce a new event in the system, create a new \l{Event} and emit it with \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>
|
|
|
|
/*! 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()
|
|
{
|
|
|
|
}
|
|
|
|
/*! Reimplement this if you support a DeviceClass with createMethod \l{DeviceManager}{CreateMethodDiscovery}.
|
|
This will be called to discover Devices for the given \a deviceClassId with the given \a params. This will always
|
|
be an async operation. Return \l{DeviceManager}{DeviceErrorAsync} or \l{DeviceManager}{DeviceErrorNoError}
|
|
if the discovery has been started successfully. Return an appropriate error otherwise.
|
|
Once devices are discovered, emit devicesDiscovered().
|
|
*/
|
|
void DevicePlugin::discoverDevices(DeviceDiscoveryInfo *info)
|
|
{
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! This will be called when a new device is created. The plugin has the chance to do some setup.
|
|
Return \l{DeviceManager}{DeviceSetupStatusFailure} if something bad happened during the setup in which case the \a device
|
|
will be disabled. Return \l{DeviceManager}{DeviceSetupStatusSuccess} if everything went well. If you can't tell yet and
|
|
need more time to set up the \a device (note: you should never block in this method) you can
|
|
return \l{DeviceManager}{DeviceSetupStatusAsync}. In that case the \l{DeviceManager} will wait for you to emit
|
|
\l{DevicePlugin}{deviceSetupFinished} to report the status.
|
|
*/
|
|
void DevicePlugin::setupDevice(DeviceSetupInfo *info)
|
|
{
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! This will be called when a new \a device was added successfully and the device setup is finished.*/
|
|
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 should 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 actually execute actions on the hardware. The \{Device} and
|
|
the \{Action} are contained in the \a device and \a action parameters.
|
|
Return the appropriate \l{Device::DeviceError}{DeviceError}.
|
|
|
|
It is possible to execute actions asynchronously. You never should do anything blocking for
|
|
a long time (e.g. wait on a network reply from the internet) but instead return
|
|
Device::DeviceErrorAsync and continue processing in an async manner. Once
|
|
you have the reply ready, emit actionExecutionFinished() with the appropriate parameters.
|
|
|
|
\sa actionExecutionFinished()
|
|
*/
|
|
void DevicePlugin::executeAction(DeviceActionInfo *info)
|
|
{
|
|
info->finish(Device::DeviceErrorUnsupportedFeature);
|
|
}
|
|
|
|
/*! Implement this if your devices support browsing (set "browsable" to true in the metadata).
|
|
* When the system calls this method, fill the \a result object's items list with entries from the browser.
|
|
* If \a 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 \a 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 \a locale.
|
|
* When done, set the \l{BrowserResult::status}{result's status} field approprietly. Set the result's
|
|
* status to Device::DeviceErrorAsync if this operation requires async behavior and emit
|
|
* \l{browseRequestFinished} when done.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*! Implement this if your devices support browsing (set "browsable" to true in the metadata).
|
|
* When the system calls this method, fetch the item details required to create a BrowserItem
|
|
* for the item with the given \a id and append that one item to the \a result.
|
|
* When done, set the \l{BrowserResult::status}{result's status} field approprietly. Set the result's
|
|
* status to Device::DeviceErrorAsync if this operation requires async behavior and emit
|
|
* \l{browserItemRequestFinished} when done.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*! Implement this if your devices support browsing and execute the itemId defined in \a browserAction.
|
|
* Return Device::DeviceErrorAsync if this operation requires async behavior and emit
|
|
* \l{browserItemExecutionFinished} when done.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/*! Implement this if your devices support browsing and execute the item's action for the itemId defined
|
|
* in \a browserItemAction.
|
|
* Return Device::DeviceErrorAsync if this operation requires async behavior and emit
|
|
* \l{browserItemActionExecutionFinished} when done.
|
|
*/
|
|
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();
|
|
}
|
|
|
|
/*! This will be called when the DeviceManager initializes the plugin and set up the things behind the scenes.
|
|
When implementing a new plugin, use \l{DevicePlugin::init()} instead in order to do initialisation work.
|
|
The \l{DevicePlugin::init()} method will be called once the plugin configuration has been loaded. */
|
|
void DevicePlugin::initPlugin(const PluginMetadata &metadata, DeviceManager *deviceManager, HardwareManager *hardwareManager)
|
|
{
|
|
m_metaData = metadata;
|
|
m_deviceManager = deviceManager;
|
|
m_hardwareManager = hardwareManager;
|
|
|
|
}
|
|
|
|
/*! 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;
|
|
}
|
|
|
|
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;
|
|
}
|