diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index b1d05160..fa45fd66 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -165,7 +165,21 @@ DeviceManager::DeviceError DeviceManager::discoverDevices(const DeviceClassId &d /*! Add a new configured device for the given \l{DeviceClass} and the given parameters. \a deviceClassId must refer to an existing \{DeviceClass} and \a params must match the parameter description in the \l{DeviceClass}. Optionally you can supply an id yourself if you must keep track of the added device. If you don't supply it, a new one will - be generated. + be generated. Only devices with \l{DeviceClass::CreateMethodUser} can be created using this method. + Returns \l{DeviceManager::DeviceError} to inform about the result. + \list + \li DeviceManager::DeviceErrorNoError: The device has been created, set up and added to the list of configured devices correctly. + \li DeviceManager::DeviceErrorAsync: The device has been created, but the setup requires async operations. For instance a network query. + In this case, you should wait for the \l{deviceSetupFinished()} signal to get the final results. + \li DeviceManager::DeviceErrorCreateMethodNotSupported: The deviceId you've chosen refers to a DeviceClass which can't be created manually. + \li DeviceManager::DeviceErrorDeviceClassNotFound: The given deviceClassId can't be found in the list of supported devices. + \li DeviceManager::DeviceErrorMissingParameter: The given params do not suffice for the given deviceClassId. + \li DeviceManager::DeviceErrorDeviceParameterError: The given params can't be matched to the ParamTypes for the given deviceClassId. + \li DeviceManager::DeviceErrorDuplicateUuid: The given uuid already exists. + \li DeviceManager::DeviceErrorPluginNotFound: Couldn't find a plugin that handles this deviceClassId. + \li DeviceManager::DeviceErrorSetupFailed: The device couldn't be set up. Even though you supplied all the parameters correctly, something + went wrong during setup. Reasons may be a hardware/network failure, wrong username/password or similar, depending on what the device plugin + needs to do in order to set up the device. */ QPair DeviceManager::addConfiguredDevice(const DeviceClassId &deviceClassId, const QList ¶ms, const DeviceId id) { @@ -250,13 +264,21 @@ QPair DeviceManager::addConfiguredDeviceInt Device *device = new Device(plugin->pluginId(), id, deviceClassId, this); device->setName(deviceClass.name()); device->setParams(params); - if (setupDevice(device)) { - m_configuredDevices.append(device); - } else { - qWarning() << "Failed to set up device."; - return qMakePair(DeviceErrorSetupFailed, "Plugin failure"); + + QPair status = setupDevice(device); + switch (status.first) { + case DeviceSetupStatusFailure: + qWarning() << "Device setup failed. Not adding device to system."; + delete device; + return qMakePair(DeviceErrorSetupFailed, QString("Device setup failed: %1").arg(status.second)); + case DeviceSetupStatusAsync: + return qMakePair(DeviceErrorAsync, ""); + case DeviceSetupStatusSuccess: + qDebug() << "Device setup complete."; + break; } + m_configuredDevices.append(device); storeConfiguredDevices(); return qMakePair(DeviceErrorNoError, QString()); @@ -395,6 +417,7 @@ void DeviceManager::loadPlugins() m_devicePlugins.insert(pluginIface->pluginId(), pluginIface); connect(pluginIface, &DevicePlugin::emitEvent, this, &DeviceManager::emitEvent); connect(pluginIface, &DevicePlugin::devicesDiscovered, this, &DeviceManager::slotDevicesDiscovered); + connect(pluginIface, &DevicePlugin::deviceSetupFinished, this, &DeviceManager::slotDeviceSetupFinished); } } } @@ -422,10 +445,13 @@ void DeviceManager::loadConfiguredDevices() settings.endGroup(); - setupDevice(device); - - m_configuredDevices.append(device); qDebug() << "found stored device" << device->name() << idString; + + // We always add the device to the list in this case. If its in the storedDevices + // it means that it was working at some point so lets still add it as there might + // be rules associated with this device. Device::setupCompleted() will be false. + setupDevice(device); + m_configuredDevices.append(device); } } @@ -463,9 +489,21 @@ void DeviceManager::createNewAutoDevices() success = plugin->configureAutoDevice(loadedDevices, device); if (success) { qDebug() << "New device detected for" << deviceClass.name() << device->name(); - setupDevice(device); - m_configuredDevices.append(device); haveNewDevice = true; + + QPair setupStatus = setupDevice(device); + switch (setupStatus.first) { + case DeviceSetupStatusSuccess: + m_configuredDevices.append(device); + break; + case DeviceSetupStatusFailure: + qDebug() << "Error during device setup. Not adding device to system."; + delete device; + break; + case DeviceSetupStatusAsync: + // Nothing to do here... We'll add it to the list or destroy it in deviceSetupFinished. + break; + } } else { qDebug() << "No newly detected devices for" << deviceClass.name(); delete device; @@ -487,6 +525,60 @@ void DeviceManager::slotDevicesDiscovered(const DeviceClassId &deviceClassId, co emit devicesDiscovered(deviceClassId, deviceDescriptors); } +void DeviceManager::slotDeviceSetupFinished(Device *device, DeviceManager::DeviceSetupStatus status, const QString &errorMessage) +{ + Q_ASSERT_X(device, "DeviceManager", "Device must be a valid pointer."); + if (!device) { + qWarning() << "Received deviceSetupFinished for an invalid device... ignoring..."; + return; + } + + if (device->setupComplete()) { + qWarning() << "Received a deviceSetupFinished event, but this Device has been set up before... ignoring..."; + return; + } + + Q_ASSERT_X(status != DeviceSetupStatusAsync, "DeviceManager", "Bad plugin implementation. You should not emit deviceSetupFinished with status DeviceSetupStatusAsync."); + if (status == DeviceSetupStatusAsync) { + qWarning() << "Bad plugin implementation. Received a deviceSetupFinished event with status Async... ignoring..."; + return; + } + + if (status == DeviceSetupStatusFailure) { + if (m_configuredDevices.contains(device)) { + qWarning() << QString("Error in device setup. Device %1 (%2) will not be functional.").arg(device->name()).arg(device->id().toString()); + emit deviceSetupFinished(device, DeviceError::DeviceErrorSetupFailed, QString("Device setup failed: %1").arg(errorMessage)); + return; + } else { + qWarning() << QString("Error in device setup. Device %1 (%2) will not be added to the configured devices.").arg(device->name()).arg(device->id().toString()); + emit deviceSetupFinished(device, DeviceError::DeviceErrorSetupFailed, QString("Device setup failed: %1").arg(errorMessage)); + return; + } + } + + // A device might be in here already if loaded from storedDevices. If it's not in the configuredDevices, + // lets add it now. + if (!m_configuredDevices.contains(device)) { + m_configuredDevices.append(device); + storeConfiguredDevices(); + } + + DevicePlugin *plugin = m_devicePlugins.value(device->pluginId()); + if (plugin->requiredHardware().testFlag(HardwareResourceTimer)) { + if (!m_pluginTimer.isActive()) { + m_pluginTimer.start(); + // Additionally fire off one event to initialize stuff + QTimer::singleShot(0, this, SLOT(timerEvent())); + } + m_pluginTimerUsers.append(device); + } + + connect(device, SIGNAL(stateValueChanged(QUuid,QVariant)), this, SLOT(slotDeviceStateValueChanged(QUuid,QVariant))); + + device->setupCompleted(); + emit deviceSetupFinished(device, DeviceManager::DeviceErrorNoError, QString()); +} + void DeviceManager::slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value) { Device *device = qobject_cast(sender()); @@ -525,7 +617,7 @@ void DeviceManager::timerEvent() } } -bool DeviceManager::setupDevice(Device *device) +QPair DeviceManager::setupDevice(Device *device) { DeviceClass deviceClass = findDeviceClass(device->deviceClassId()); DevicePlugin *plugin = m_devicePlugins.value(deviceClass.pluginId()); @@ -545,6 +637,11 @@ bool DeviceManager::setupDevice(Device *device) } } + QPair status = plugin->setupDevice(device); + if (status.first != DeviceSetupStatusSuccess) { + return status; + } + if (plugin->requiredHardware().testFlag(HardwareResourceTimer)) { if (!m_pluginTimer.isActive()) { m_pluginTimer.start(); @@ -554,13 +651,10 @@ bool DeviceManager::setupDevice(Device *device) m_pluginTimerUsers.append(device); } - if (!plugin->deviceCreated(device)) { - qWarning() << "Device setup for device" << device->name() << "failed."; - return false; - } - connect(device, SIGNAL(stateValueChanged(QUuid,QVariant)), this, SLOT(slotDeviceStateValueChanged(QUuid,QVariant))); - return true; + + device->setupCompleted(); + return status; } QPair DeviceManager::verifyParams(const QList paramTypes, const QList params) diff --git a/libguh/devicemanager.h b/libguh/devicemanager.h index 2e99d7e4..0129e571 100644 --- a/libguh/devicemanager.h +++ b/libguh/devicemanager.h @@ -58,7 +58,14 @@ public: DeviceErrorCreationMethodNotSupported, DeviceErrorDeviceParameterError, DeviceErrorActionParameterError, - DeviceErrorDeviceDescriptorNotFound + DeviceErrorDeviceDescriptorNotFound, + DeviceErrorAsync + }; + + enum DeviceSetupStatus { + DeviceSetupStatusSuccess, + DeviceSetupStatusFailure, + DeviceSetupStatusAsync }; explicit DeviceManager(QObject *parent = 0); @@ -85,6 +92,7 @@ signals: void emitEvent(const Event &event); void deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList &devices); + void deviceSetupFinished(Device *device, DeviceError status, const QString &errorMessage); public slots: QPair executeAction(const Action &action); @@ -95,6 +103,7 @@ private slots: void storeConfiguredDevices(); void createNewAutoDevices(); void slotDevicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors); + void slotDeviceSetupFinished(Device *device, DeviceManager::DeviceSetupStatus status, const QString &errorMessage); // Only connect this to Devices. It will query the sender() void slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value); @@ -104,7 +113,7 @@ private slots: private: QPair addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QList ¶ms, const DeviceId id = DeviceId::createDeviceId()); - bool setupDevice(Device *device); + QPair setupDevice(Device *device); QPair verifyParams(const QList paramTypes, const QList params); private: diff --git a/libguh/plugin/device.cpp b/libguh/plugin/device.cpp index 8f38ba9b..c5828721 100644 --- a/libguh/plugin/device.cpp +++ b/libguh/plugin/device.cpp @@ -37,7 +37,8 @@ Device::Device(const PluginId &pluginId, const DeviceId &id, const DeviceClassId QObject(parent), m_id(id), m_deviceClassId(deviceClassId), - m_pluginId(pluginId) + m_pluginId(pluginId), + m_setupComplete(false) { } @@ -46,11 +47,17 @@ Device::Device(const PluginId &pluginId, const DeviceClassId &deviceClassId, QOb QObject(parent), m_id(DeviceId::createDeviceId()), m_deviceClassId(deviceClassId), - m_pluginId(pluginId) + m_pluginId(pluginId), + m_setupComplete(false) { } +void Device::setupCompleted() +{ + m_setupComplete = true; +} + /*! Returns the id of this Device. */ DeviceId Device::id() const { @@ -162,3 +169,8 @@ void Device::setStateValue(const StateTypeId &stateTypeId, const QVariant &value } qWarning() << "failed setting state for" << m_name; } + +bool Device::setupComplete() const +{ + return m_setupComplete; +} diff --git a/libguh/plugin/device.h b/libguh/plugin/device.h index 3e804b57..76181fc9 100644 --- a/libguh/plugin/device.h +++ b/libguh/plugin/device.h @@ -57,6 +57,8 @@ public: QVariant stateValue(const StateTypeId &stateTypeId) const; void setStateValue(const StateTypeId &stateTypeId, const QVariant &value); + bool setupComplete() const; + signals: void stateValueChanged(const QUuid &stateTypeId, const QVariant &value); @@ -64,6 +66,8 @@ private: Device(const PluginId &pluginId, const DeviceId &id, const DeviceClassId &deviceClassId, QObject *parent = 0); Device(const PluginId &pluginId, const DeviceClassId &deviceClassId, QObject *parent = 0); + void setupCompleted(); + private: DeviceId m_id; DeviceClassId m_deviceClassId; @@ -71,6 +75,7 @@ private: QString m_name; QList m_params; QList m_states; + bool m_setupComplete; }; #endif diff --git a/libguh/plugin/deviceplugin.cpp b/libguh/plugin/deviceplugin.cpp index ae92549e..1479ce42 100644 --- a/libguh/plugin/deviceplugin.cpp +++ b/libguh/plugin/deviceplugin.cpp @@ -120,9 +120,9 @@ DevicePlugin::~DevicePlugin() false. If instead you've found a new device which isn't known to the system yet, fill in the parameters of the passed device with some details that makes it possible for you to match this Device object with the detected hardware. After that, return true. - The DeviceManager will then insert the device into its database and call deviceCreated() + The DeviceManager will then insert the device into its database and call setupDevice() for this device. Therefore you should not do any hardware initialisation in this state yet - but rather wait for the subsequent deviceCreated() call to set it up like in any other + but rather wait for the subsequent setupDevice() call to set it up like in any other case where Device can be created. Returning false will cause the passed device object to be destroyed. If you have detected multiple new devices, just load them one by one. The DeviceManager @@ -143,12 +143,17 @@ DeviceManager::DeviceError DevicePlugin::discoverDevices(const DeviceClassId &de } /*! This will be called when a new device is created. The plugin has the chance to do some setup. - Return false if something bad happened during the setup. The device will be disabled. + Return DeviceSetupStatusFailure if something bad happened during the setup in which case the device + will be disabled. Return DeviceSetupStatusSuccess if everything went well. If you can't tell yet and + need more time to set up the device (note: you should never block in this method) you can + return DeviceSetupStatusAsync. In that case the devicemanager will wait for you to emit + \l{deviceSetupFinished(Device *device, DeviceManager::DeviceSetupStatus status)} to report + the status. */ -bool DevicePlugin::deviceCreated(Device *device) +QPair DevicePlugin::setupDevice(Device *device) { Q_UNUSED(device) - return true; + return reportDeviceSetup(); } /*! This will be called when a device removed. The plugin has the chance to do some teardown. @@ -271,3 +276,8 @@ QPair DevicePlugin::report(DeviceManager::D return qMakePair(error, message); } +QPair DevicePlugin::reportDeviceSetup(DeviceManager::DeviceSetupStatus status, const QString &message) +{ + return qMakePair(status, message); +} + diff --git a/libguh/plugin/deviceplugin.h b/libguh/plugin/deviceplugin.h index 89451c0a..f8237b3c 100644 --- a/libguh/plugin/deviceplugin.h +++ b/libguh/plugin/deviceplugin.h @@ -51,7 +51,7 @@ public: virtual bool configureAutoDevice(QList loadedDevices, Device *device) const; virtual DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const QVariantMap ¶ms) const; - virtual bool deviceCreated(Device *device); + virtual QPair setupDevice(Device *device); virtual void deviceRemoved(Device *device); // Hardware input @@ -70,6 +70,7 @@ public slots: signals: void emitEvent(const Event &event); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); + void deviceSetupFinished(Device *device, DeviceManager::DeviceSetupStatus status, const QString &errorMessage); protected: DeviceManager *deviceManager() const; @@ -79,6 +80,7 @@ protected: void transmitData(QList rawData); QPair report(DeviceManager::DeviceError error = DeviceManager::DeviceErrorNoError, const QString &message = QString()); + QPair reportDeviceSetup(DeviceManager::DeviceSetupStatus status = DeviceManager::DeviceSetupStatusSuccess, const QString &message = QString()); private: void initPlugin(DeviceManager *deviceManager); diff --git a/plugins/deviceplugins/mailnotification/devicepluginmailnotification.cpp b/plugins/deviceplugins/mailnotification/devicepluginmailnotification.cpp index fafa32be..67698266 100644 --- a/plugins/deviceplugins/mailnotification/devicepluginmailnotification.cpp +++ b/plugins/deviceplugins/mailnotification/devicepluginmailnotification.cpp @@ -243,7 +243,7 @@ QList DevicePluginMailNotification::supportedDevices() const return ret; } -bool DevicePluginMailNotification::deviceCreated(Device *device) +QPair DevicePluginMailNotification::setupDevice(Device *device) { // Google mail // if(device->deviceClassId() == googleMailDeviceClassId){ @@ -253,7 +253,7 @@ bool DevicePluginMailNotification::deviceCreated(Device *device) // m_smtpClient->setHost("smtp.gmail.com"); // m_smtpClient->login(device->paramValue("user").toString(), device->paramValue("password").toString()); // } - return true; + return reportDeviceSetup(); } DeviceManager::HardwareResources DevicePluginMailNotification::requiredHardware() const diff --git a/plugins/deviceplugins/mailnotification/devicepluginmailnotification.h b/plugins/deviceplugins/mailnotification/devicepluginmailnotification.h index dd810e79..f9a7fa70 100644 --- a/plugins/deviceplugins/mailnotification/devicepluginmailnotification.h +++ b/plugins/deviceplugins/mailnotification/devicepluginmailnotification.h @@ -36,7 +36,7 @@ public: QList supportedVendors() const override; QList supportedDevices() const override; - bool deviceCreated(Device *device) override; + QPair setupDevice(Device *device) override; DeviceManager::HardwareResources requiredHardware() const override; QPair executeAction(Device *device, const Action &action) override; diff --git a/plugins/deviceplugins/mock/devicepluginmock.cpp b/plugins/deviceplugins/mock/devicepluginmock.cpp index 81b586e6..e3660062 100644 --- a/plugins/deviceplugins/mock/devicepluginmock.cpp +++ b/plugins/deviceplugins/mock/devicepluginmock.cpp @@ -139,7 +139,7 @@ PluginId DevicePluginMock::pluginId() const return PluginId("727a4a9a-c187-446f-aadf-f1b2220607d1"); } -bool DevicePluginMock::deviceCreated(Device *device) +QPair DevicePluginMock::setupDevice(Device *device) { qDebug() << "Mockdevice created returning true" << device->paramValue("httpport").toInt(); @@ -148,13 +148,13 @@ bool DevicePluginMock::deviceCreated(Device *device) if (!daemon->isListening()) { qDebug() << "HTTP port opening failed."; - return false; + return reportDeviceSetup(DeviceManager::DeviceSetupStatusFailure, QString("Could not bind port.")); } connect(daemon, &HttpDaemon::triggerEvent, this, &DevicePluginMock::triggerEvent); connect(daemon, &HttpDaemon::setState, this, &DevicePluginMock::setState); - return true; + return reportDeviceSetup(); } void DevicePluginMock::deviceRemoved(Device *device) diff --git a/plugins/deviceplugins/mock/devicepluginmock.h b/plugins/deviceplugins/mock/devicepluginmock.h index c6d225dc..c1eb354d 100644 --- a/plugins/deviceplugins/mock/devicepluginmock.h +++ b/plugins/deviceplugins/mock/devicepluginmock.h @@ -42,7 +42,7 @@ public: QString pluginName() const override; PluginId pluginId() const override; - bool deviceCreated(Device *device) override; + QPair setupDevice(Device *device) override; void deviceRemoved(Device *device) override; bool configureAutoDevice(QList loadedDevices, Device *device) const override; diff --git a/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.cpp b/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.cpp index b0455732..986e9ab1 100644 --- a/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.cpp +++ b/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.cpp @@ -431,10 +431,10 @@ DeviceManager::DeviceError DevicePluginOpenweathermap::discoverDevices(const Dev return DeviceManager::DeviceErrorNoError; } -bool DevicePluginOpenweathermap::deviceCreated(Device *device) +QPair DevicePluginOpenweathermap::setupDevice(Device *device) { m_openweaher->update(device->paramValue("id").toString()); - return true; + return reportDeviceSetup(); } DeviceManager::HardwareResources DevicePluginOpenweathermap::requiredHardware() const diff --git a/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.h b/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.h index 3c997fbe..cbfd7361 100644 --- a/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.h +++ b/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.h @@ -39,7 +39,7 @@ public: QList supportedDevices() const override; DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const QVariantMap ¶ms) const override; - bool deviceCreated(Device *device) override; + QPair setupDevice(Device *device) override; DeviceManager::HardwareResources requiredHardware() const override; QPair executeAction(Device *device, const Action &action) override; diff --git a/server/jsonrpc/devicehandler.cpp b/server/jsonrpc/devicehandler.cpp index 6b835872..f6c237fa 100644 --- a/server/jsonrpc/devicehandler.cpp +++ b/server/jsonrpc/devicehandler.cpp @@ -268,6 +268,11 @@ JsonReply* DeviceHandler::AddConfiguredDevice(const QVariantMap ¶ms) } QVariantMap returns; switch(status.first) { + case DeviceManager::DeviceErrorAsync: { + JsonReply *asyncReply = createAsyncReply("AddConfiguredDevice"); + m_asynDeviceAdditions.insert(newDeviceId, asyncReply); + return asyncReply; + } case DeviceManager::DeviceErrorNoError: returns.insert("success", true); returns.insert("errorMessage", ""); @@ -424,3 +429,29 @@ void DeviceHandler::devicesDiscovered(const DeviceClassId &deviceClassId, const reply->setData(returns); reply->finished(); } + +void DeviceHandler::deviceSetupFinished(Device *device, DeviceManager::DeviceError status) +{ + if (!m_asynDeviceAdditions.contains(device->id())) { + return; // Not the device we're waiting for... + } + + JsonReply *reply = m_asynDeviceAdditions.take(device->id()); + + QVariantMap returns; + + if(status == DeviceManager::DeviceErrorNoError) { + returns.insert("success", true); + returns.insert("errorMessage", ""); + returns.insert("deviceId", device->deviceClassId()); + } else if (status == DeviceManager::DeviceErrorSetupFailed) { + returns.insert("errorMessage", QString("Error creating device. Device setup failed.")); + returns.insert("success", false); + } else { + Q_ASSERT_X(false, "DeviceHandler", "Unhandled status code for deviceSetupFinished"); + returns.insert("errorMessage", "Unknown error."); + returns.insert("success", false); + } + reply->setData(returns); + reply->finished(); +} diff --git a/server/jsonrpc/devicehandler.h b/server/jsonrpc/devicehandler.h index 3885e652..46139f6f 100644 --- a/server/jsonrpc/devicehandler.h +++ b/server/jsonrpc/devicehandler.h @@ -20,6 +20,7 @@ #define DEVICEHANDLER_H #include "jsonhandler.h" +#include "devicemanager.h" class DeviceHandler : public JsonHandler { @@ -61,9 +62,12 @@ private slots: void devicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors); + void deviceSetupFinished(Device *device, DeviceManager::DeviceError status); + private: // A cache for async replies mutable QHash m_discoverRequests; + mutable QHash m_asynDeviceAdditions; }; #endif // DEVICEHANDLER_H