added support for async device creation.

This commit is contained in:
Michael Zanetti 2014-05-03 15:10:16 +02:00
parent 9d1e62d923
commit f7559742a4
14 changed files with 205 additions and 38 deletions

View File

@ -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::DeviceError, QString> DeviceManager::addConfiguredDevice(const DeviceClassId &deviceClassId, const QList<Param> &params, const DeviceId id)
{
@ -250,13 +264,21 @@ QPair<DeviceManager::DeviceError, QString> 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<DeviceError, QString>(DeviceErrorSetupFailed, "Plugin failure");
QPair<DeviceSetupStatus, QString> status = setupDevice(device);
switch (status.first) {
case DeviceSetupStatusFailure:
qWarning() << "Device setup failed. Not adding device to system.";
delete device;
return qMakePair<DeviceError, QString>(DeviceErrorSetupFailed, QString("Device setup failed: %1").arg(status.second));
case DeviceSetupStatusAsync:
return qMakePair<DeviceError, QString>(DeviceErrorAsync, "");
case DeviceSetupStatusSuccess:
qDebug() << "Device setup complete.";
break;
}
m_configuredDevices.append(device);
storeConfiguredDevices();
return qMakePair<DeviceError, QString>(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<DeviceSetupStatus, QString> 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<Device*>(sender());
@ -525,7 +617,7 @@ void DeviceManager::timerEvent()
}
}
bool DeviceManager::setupDevice(Device *device)
QPair<DeviceManager::DeviceSetupStatus,QString> 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<DeviceSetupStatus, QString> 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<bool, QString> DeviceManager::verifyParams(const QList<ParamType> paramTypes, const QList<Param> params)

View File

@ -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<DeviceDescriptor> &devices);
void deviceSetupFinished(Device *device, DeviceError status, const QString &errorMessage);
public slots:
QPair<DeviceError, QString> executeAction(const Action &action);
@ -95,6 +103,7 @@ private slots:
void storeConfiguredDevices();
void createNewAutoDevices();
void slotDevicesDiscovered(const DeviceClassId &deviceClassId, const QList<DeviceDescriptor> 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<DeviceError, QString> addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QList<Param> &params, const DeviceId id = DeviceId::createDeviceId());
bool setupDevice(Device *device);
QPair<DeviceSetupStatus, QString> setupDevice(Device *device);
QPair<bool, QString> verifyParams(const QList<ParamType> paramTypes, const QList<Param> params);
private:

View File

@ -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;
}

View File

@ -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<Param> m_params;
QList<State> m_states;
bool m_setupComplete;
};
#endif

View File

@ -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<DeviceManager::DeviceSetupStatus, QString> 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<DeviceManager::DeviceError, QString> DevicePlugin::report(DeviceManager::D
return qMakePair<DeviceManager::DeviceError, QString>(error, message);
}
QPair<DeviceManager::DeviceSetupStatus, QString> DevicePlugin::reportDeviceSetup(DeviceManager::DeviceSetupStatus status, const QString &message)
{
return qMakePair<DeviceManager::DeviceSetupStatus, QString>(status, message);
}

View File

@ -51,7 +51,7 @@ public:
virtual bool configureAutoDevice(QList<Device *> loadedDevices, Device *device) const;
virtual DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const QVariantMap &params) const;
virtual bool deviceCreated(Device *device);
virtual QPair<DeviceManager::DeviceSetupStatus, QString> 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<DeviceDescriptor> &deviceDescriptors);
void deviceSetupFinished(Device *device, DeviceManager::DeviceSetupStatus status, const QString &errorMessage);
protected:
DeviceManager *deviceManager() const;
@ -79,6 +80,7 @@ protected:
void transmitData(QList<int> rawData);
QPair<DeviceManager::DeviceError, QString> report(DeviceManager::DeviceError error = DeviceManager::DeviceErrorNoError, const QString &message = QString());
QPair<DeviceManager::DeviceSetupStatus, QString> reportDeviceSetup(DeviceManager::DeviceSetupStatus status = DeviceManager::DeviceSetupStatusSuccess, const QString &message = QString());
private:
void initPlugin(DeviceManager *deviceManager);

View File

@ -243,7 +243,7 @@ QList<DeviceClass> DevicePluginMailNotification::supportedDevices() const
return ret;
}
bool DevicePluginMailNotification::deviceCreated(Device *device)
QPair<DeviceManager::DeviceSetupStatus, QString> 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

View File

@ -36,7 +36,7 @@ public:
QList<Vendor> supportedVendors() const override;
QList<DeviceClass> supportedDevices() const override;
bool deviceCreated(Device *device) override;
QPair<DeviceManager::DeviceSetupStatus, QString> setupDevice(Device *device) override;
DeviceManager::HardwareResources requiredHardware() const override;
QPair<DeviceManager::DeviceError, QString> executeAction(Device *device, const Action &action) override;

View File

@ -139,7 +139,7 @@ PluginId DevicePluginMock::pluginId() const
return PluginId("727a4a9a-c187-446f-aadf-f1b2220607d1");
}
bool DevicePluginMock::deviceCreated(Device *device)
QPair<DeviceManager::DeviceSetupStatus, QString> 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)

View File

@ -42,7 +42,7 @@ public:
QString pluginName() const override;
PluginId pluginId() const override;
bool deviceCreated(Device *device) override;
QPair<DeviceManager::DeviceSetupStatus, QString> setupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
bool configureAutoDevice(QList<Device *> loadedDevices, Device *device) const override;

View File

@ -431,10 +431,10 @@ DeviceManager::DeviceError DevicePluginOpenweathermap::discoverDevices(const Dev
return DeviceManager::DeviceErrorNoError;
}
bool DevicePluginOpenweathermap::deviceCreated(Device *device)
QPair<DeviceManager::DeviceSetupStatus, QString> DevicePluginOpenweathermap::setupDevice(Device *device)
{
m_openweaher->update(device->paramValue("id").toString());
return true;
return reportDeviceSetup();
}
DeviceManager::HardwareResources DevicePluginOpenweathermap::requiredHardware() const

View File

@ -39,7 +39,7 @@ public:
QList<DeviceClass> supportedDevices() const override;
DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const QVariantMap &params) const override;
bool deviceCreated(Device *device) override;
QPair<DeviceManager::DeviceSetupStatus, QString> setupDevice(Device *device) override;
DeviceManager::HardwareResources requiredHardware() const override;
QPair<DeviceManager::DeviceError, QString> executeAction(Device *device, const Action &action) override;

View File

@ -268,6 +268,11 @@ JsonReply* DeviceHandler::AddConfiguredDevice(const QVariantMap &params)
}
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();
}

View File

@ -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<DeviceDescriptor> deviceDescriptors);
void deviceSetupFinished(Device *device, DeviceManager::DeviceError status);
private:
// A cache for async replies
mutable QHash<DeviceClassId, JsonReply*> m_discoverRequests;
mutable QHash<DeviceId, JsonReply*> m_asynDeviceAdditions;
};
#endif // DEVICEHANDLER_H