diff --git a/doc/create-setupmethods.qdoc b/doc/create-setupmethods.qdoc index f8203498..c1bcfdaa 100644 --- a/doc/create-setupmethods.qdoc +++ b/doc/create-setupmethods.qdoc @@ -9,7 +9,7 @@ The \l{DeviceClass::SetupMethod}{SetupMethod} describes how the device will be set up. A \l{Device} can have multiple \l{DeviceClass::CreateMethods}{CreateMethods}, but only one \l{DeviceClass::SetupMethod}{SetupMethod}. \list - \li - \l{DeviceClass::CreateMethods}{CreateMethods} + \li \l{DeviceClass::CreateMethods}{CreateMethods} \list \li \e user \unicode{0x2192} \l{DeviceClass::CreateMethodUser} \li \e discovery \unicode{0x2192} \l{DeviceClass::CreateMethodDiscovery} @@ -168,15 +168,15 @@ \endlist \li \b Pairing \list - \li \b 4. | The user should see now the pin on the display and the pairing info. The method \l{DevicePlugin::confirmPairing()} will be called once he entered the pin. Here can be verified if the pin is authorized by the device and if the pairing succeeded. - \li \b 5. | Returns the \l{DeviceManager::DeviceSetupStatus} to inform about the result (sync or async). - \li \b 6. | Once the pairing has been verified (check if the button has been pushed) the plugin can emit the signal \l{DevicePlugin::pairingFinished()} to inform the \l{DeviceManager} about the result. + \li \b 6. | The user should see now the pin on the display and the pairing info. The method \l{DevicePlugin::confirmPairing()} will be called once he entered the pin. Here can be verified if the pin is authorized by the device and if the pairing succeeded. + \li \b 7. | Returns the \l{DeviceManager::DeviceSetupStatus} to inform about the result (sync or async). + \li \b 8. | Once the pairing has been verified (check if the button has been pushed) the plugin can emit the signal \l{DevicePlugin::pairingFinished()} to inform the \l{DeviceManager} about the result. \endlist \li \b Setup \list - \li \b 7. | The plugin will be set up with the params (i.e. containing a \tt pin param) of the paired \l{Device}. - \li \b 8. | If something goes wrong during the setup return \l{DeviceManager::DeviceSetupStatusFailure}, otherwise \l{DeviceManager::DeviceSetupStatusSuccess}. - \li \b 9. | If the device setup succeded and the device is in the system, the \l{DeviceManager} will call the \l{DevicePlugin::postSetupDevice()}{postSetupDevice()} method. + \li \b 9. | The plugin will be set up with the params (i.e. containing a \tt pin param) of the paired \l{Device}. + \li \b 10. | If something goes wrong during the setup return \l{DeviceManager::DeviceSetupStatusFailure}, otherwise \l{DeviceManager::DeviceSetupStatusSuccess}. + \li \b 11. | If the device setup succeded and the device is in the system, the \l{DeviceManager} will call the \l{DevicePlugin::postSetupDevice()}{postSetupDevice()} method. \endlist \endlist */ diff --git a/doc/plugin-json.qdoc b/doc/plugin-json.qdoc index e240d624..1bd834e9 100644 --- a/doc/plugin-json.qdoc +++ b/doc/plugin-json.qdoc @@ -191,6 +191,7 @@ \li \e enterPin \unicode{0x2192} \l{DeviceClass::SetupMethodEnterPin} \li \e pushButton \unicode{0x2192} \l{DeviceClass::SetupMethodPushButton} \endlist + \note For more information please take a look at \l{CreateMethods and SetupMethods} documentation. \li - \underline{\e pairingInfo:} The \l{DeviceClass::pairingInfo()}{pairingInfo} will inform the user how to pair the device \unicode{0x2192} \l{DeviceClass::setupMethod()}. This parameter will only be used for \l{DeviceClass::SetupMethodDisplayPin}{DisplayPin} and \l{DeviceClass::SetupMethodEnterPin}{EnterPin} and \l{DeviceClass::SetupMethodPushButton}{PushButton}. Example: "Please press the button on the device before continue." \li - \underline{\e discoveryParamTypes:} A list of \l{ParamType}{ParamTypes} which will be needed for discovering a device \unicode{0x2192} \l{DeviceClass::discoveryParamTypes()}. This parameter will only be used for devices with the \l{DeviceClass::CreateMethodDiscovery}{CreateMethodDiscovery} (see section "\l{The ParamType definition}"). \li - \underline{\e paramTypes:} A list of \l{ParamType}{ParamTypes} which define the paramters of a device \unicode{0x2192} \l{DeviceClass::paramTypes()} (see section "\l{The ParamType definition}"). diff --git a/doc/tutorial6.qdoc b/doc/tutorial6.qdoc index d9bb0e67..016e0e9e 100644 --- a/doc/tutorial6.qdoc +++ b/doc/tutorial6.qdoc @@ -9,15 +9,425 @@ \list \li \unicode{0x25B6} Allow only one \l{Device} \li \unicode{0x25B6} Implement the \l{DevicePlugin::deviceRemoved()}{deviceRemoved()} method - \li \unicode{0x25B6} Use plugin configurations \li \unicode{0x25B6} Use the \l{Coap}{CoAP} library \endlist This tutorial shows you how to write a \l{Coap}{CoAP} plugin and how the plugin configuration work. The plugin it self has no practical purpose but shows some concepts of CoAP and plugin development. - \section1 Plugin structure + \section1 The plugin source code + + \section2 networkinfo.pro + + \code + include(plugins.pri) + + TARGET = guh_deviceplugincoapclient + + message(============================================) + message("Qt version: $$[QT_VERSION]") + message("Building $$deviceplugin$${TARGET}.so") + + SOURCES += \ + deviceplugincoapclient.cpp \ + + HEADERS += \ + deviceplugincoapclient.h \ + + \endcode + + \section2 devicepluginnetworkinfo.json + + \code + { + "name": "Coap Client", + "idName": "CoapClient", + "id": "9ecadcbb-8699-41c2-a2e3-fd51a1faf1a1", + "paramTypes": [ + { + "name": "url", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "coap://vs0.inf.ethz.ch:5683" + } + ], + "vendors": [ + { + "id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6", + "name": "guh", + "idName": "guh", + "deviceClasses": [ + { + "deviceClassId": "69dcccbd-a66a-4c5b-8921-2fb86c4c4299", + "idName": "info", + "name": "Coap Client", + "createMethods": ["user"], + "basicTags": [ + "Service", + "Sensor", + "Actuator" + ], + "stateTypes": [ + { + "id": "b8433a82-cf83-424f-b4a2-3f6507405d6c", + "idName": "notifications", + "name": "notification", + "type": "bool", + "defaultValue": false, + "writable": true + } + ], + "actionTypes": [ + { + "id": "9aa31838-b62f-43b3-bdcd-8165840b5edf", + "name": "upload message", + "idName": "upload", + "paramTypes": [ + { + "name": "message", + "type": "QString", + "defaultValue": "Hallo world!" + } + ] + } + ], + "eventTypes": [ + { + "name": "time changed", + "idName": "time", + "id": "44513802-138e-42f8-86a6-9edd4df77535", + "paramTypes": [ + { + "name": "time", + "type": "QString" + } + ] + } + ] + } + ] + } + ] + } + \endcode + + \section2 devicepluginnetworkinfo.h + + \code + #ifndef DEVICEPLUGINCOAPCLIENT_H + #define DEVICEPLUGINCOAPCLIENT_H + + #include "devicemanager.h" + #include "plugin/deviceplugin.h" + #include "coap/coap.h" + + #include + #include + + class DevicePluginCoapClient : public DevicePlugin + { + Q_OBJECT + + Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "deviceplugincoapclient.json") + Q_INTERFACES(DevicePlugin) + + public: + explicit DevicePluginCoapClient(); + + DeviceManager::HardwareResources requiredHardware() const override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + + private: + QPointer m_device; + QPointer m_coap; + + // Replies from coap + QHash m_discoverReplies; + QHash m_notificationEnableReplies; + QHash m_notificationDisableReplies; + QList m_uploadReplies; + + QHash< CoapReply *, ActionId> m_asyncActions; + + private slots: + void onReplyFinished(CoapReply *reply); + void onNotificationReceived(const CoapObserveResource &resource, const int ¬ificationNumber, const QByteArray &payload); + + }; + + #endif // DEVICEPLUGINNETWORKINFO_H + \endcode + + \section2 devicepluginnetworkinfo.cpp + + \code + #include "deviceplugincoapclient.h" + #include "plugininfo.h" + + #include + + #include "coap/corelinkparser.h" + + // Note: You can find the documentation for this code here -> http://dev.guh.guru/write-plugins.html + + // The constructor of this device plugin. + DevicePluginCoapClient::DevicePluginCoapClient() + { + } + + DeviceManager::HardwareResources DevicePluginCoapClient::requiredHardware() const + { + return DeviceManager::HardwareResourceNone; + } + + DeviceManager::DeviceSetupStatus DevicePluginCoapClient::setupDevice(Device *device) + { + // Check if we already have a coap client device + if (!myDevices().isEmpty()) { + qCWarning(dcCoapClient) << "There is already a configured coap client device"; + return DeviceManager::DeviceSetupStatusFailure; + } + + qCDebug(dcCoapClient) << "Setting up a new device:" << device->name() << device->params(); + + // Verify the given URL + QUrl url(device->paramValue("url").toString()); + if (url.scheme() != "coap") { + qCWarning(dcCoapClient) << "Invalid URL scheme" << url.scheme() << " != " << "coap"; + return DeviceManager::DeviceSetupStatusFailure; + } + + m_device = device; + + // Create new CoAP client if there isn't one yet + if (m_coap.isNull()) { + m_coap = new Coap(this); + connect(m_coap, &Coap::replyFinished, this, &DevicePluginCoapClient::onReplyFinished); + connect(m_coap, &Coap::notificationReceived, this, &DevicePluginCoapClient::onNotificationReceived); + } + + // Discover the CoAP server + url.setPath("/.well-known/core"); + CoapReply *reply = m_coap->get(CoapRequest(url)); + + // Check imediatly if the there occured any error + if (reply->error() != CoapReply::NoError) { + qCWarning(dcCoapClient) << "Could not discover CoAP server:" << reply->errorString(); + reply->deleteLater(); + m_coap->deleteLater(); + return DeviceManager::DeviceSetupStatusFailure; + } + + // Store the reply and device until we get our asynchronous response + m_discoverReplies.insert(reply, device); + + // Tell the DeviceManager that the setup result will be communicated later + return DeviceManager::DeviceSetupStatusAsync; + } + + void DevicePluginCoapClient::deviceRemoved(Device *device) + { + // Prevent the unused variable warning + Q_UNUSED(device) + + // Delete the CoAP socket if not longer needed + m_coap->deleteLater(); + } + + // This method will be called whenever a client or the rule engine wants to execute an action for the given device. + DeviceManager::DeviceError DevicePluginCoapClient::executeAction(Device *device, const Action &action) + { + qCDebug(dcCoapClient) << "Execute action" << action.id() << action.params(); + + // check if the requested action is our "upload" action ... + if (action.actionTypeId() == notificationsActionTypeId) { + + // observe resource (enable notifications) + QUrl url(device->paramValue("url").toString()); + url.setPath(url.path().append("/obs")); + + if (action.param("notification").value().toBool()) { + qCDebug(dcCoapClient) << "Enable notification on resource" << url.toString(); + CoapReply *reply = m_coap->enableResourceNotifications(CoapRequest(url)); + m_asyncActions.insert(reply, action.id()); + m_notificationEnableReplies.insert(reply, device); + } else { + qCDebug(dcCoapClient) << "Disable notification on resource" << url.toString(); + CoapReply *reply = m_coap->disableNotifications(CoapRequest(url)); + m_asyncActions.insert(reply, action.id()); + m_notificationDisableReplies.insert(reply, device); + } + + // Tell the DeviceManager that this is an async action and the + // result of the execution will be emitted later. + return DeviceManager::DeviceErrorAsync; + + } else if (action.actionTypeId() == uploadActionTypeId) { + + // Define the URL for uploading the message (POST) + QUrl url(device->paramValue("url").toString()); + url.setPath(url.path().append("/test")); + + // Upload the message (POST) + CoapReply *reply = m_coap->post(CoapRequest(url), action.param("message").value().toString().toUtf8()); + m_uploadReplies.append(reply); + m_asyncActions.insert(reply, action.id()); + + // Tell the DeviceManager that this is an async action and the + // result of the execution will be emitted later. + return DeviceManager::DeviceErrorAsync; + } + + // ...otherwise the ActionType does not exist + return DeviceManager::DeviceErrorActionTypeNotFound; + } + + // This slot will be called whenever a reply from the CoAP socket has finished + void DevicePluginCoapClient::onReplyFinished(CoapReply *reply) + { + // Now check which reply this was by checking in which Hash it can be found + if (m_discoverReplies.keys().contains(reply)) { + Device *device = m_discoverReplies.take(reply); + + // Verify there where no reply errors (transport layer) + if (reply->error() != CoapReply::NoError) { + qCWarning(dcCoapClient) << "CoAP resource discovery reply error" << reply->errorString(); + reply->deleteLater(); + // Something went wrong during the discovery. Finish the setup with error. + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); + return; + } + + // Verify we have the right status code (server response) + if (reply->statusCode() != CoapPdu::Content) { + qCWarning(dcCoapClient) << "CoAP discovery status code:" << reply; + reply->deleteLater(); + // Something went wrong during the discovery. Finish the setup with error. + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); + return; + } + + qCDebug(dcCoapClient) << "Discovered successfully the resources"; + + // Print the CoRE links we got from the server resource discovery + CoreLinkParser parser(reply->payload()); + foreach (const CoreLink &link, parser.links()) { + qCDebug(dcCoapClient) << link << endl; + } + + // Tell the device manager that the device setup finished successfully + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); + + } else if (m_notificationEnableReplies.keys().contains(reply)) { + Device *device = m_notificationEnableReplies.take(reply); + ActionId actionId = m_asyncActions.take(reply); + + // Verify there where no reply errors (transport layer) + if (reply->error() != CoapReply::NoError) { + qCWarning(dcCoapClient) << "CoAP enable observe resource reply error" << reply->errorString(); + // Something went wrong. Tell the devicemanager that the action finished with error. + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); + reply->deleteLater(); + return; + } + + // Verify we have the right status code (server response) + if (reply->statusCode() != CoapPdu::Content) { + qCWarning(dcCoapClient) << "CoAP enable observe status code:" << reply; + // Something went wrong. Tell the devicemanager that the action finished with error. + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); + reply->deleteLater(); + return; + } + + qCDebug(dcCoapClient) << "Enabled successfully notifications" << reply; + + // Set the corresping state + device->setStateValue(notificationsStateTypeId, true); + + // Tell the device manager that the action execution finished successfully + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); + + } else if (m_notificationDisableReplies.keys().contains(reply)) { + Device *device = m_notificationDisableReplies.take(reply); + ActionId actionId = m_asyncActions.take(reply); + + // Verify there where no reply errors (transport layer) + if (reply->error() != CoapReply::NoError) { + qCWarning(dcCoapClient) << "CoAP disable observe resource reply error" << reply->errorString(); + // Something went wrong. Tell the devicemanager that the action finished with error. + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); + reply->deleteLater(); + return; + } + + // Verify we have the right status code (server response) + if (reply->statusCode() != CoapPdu::Content) { + qCWarning(dcCoapClient) << "CoAP disable observe status code:" << reply; + // Something went wrong. Tell the devicemanager that the action finished with error. + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); + reply->deleteLater(); + return; + } + + qCDebug(dcCoapClient) << "Disabled successfully notifications" << reply; + + // Set the corresping state + device->setStateValue(notificationsStateTypeId, false); + + // Tell the device manager that the action execution finished successfully + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); + + } else if (m_uploadReplies.contains(reply)) { + ActionId actionId = m_asyncActions.take(reply); + + // Verify there where no reply errors (transport layer) + if (reply->error() != CoapReply::NoError) { + qCWarning(dcCoapClient) << "CoAP upload reply error" << reply->errorString(); + // Something went wrong. Tell the devicemanager that the action finished with error. + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); + reply->deleteLater(); + return; + } + + // Verify we have the right status code (server response) + if (reply->statusCode() != CoapPdu::Created) { + qCWarning(dcCoapClient) << "CoAP upload status code:" << reply; + // Something went wrong. Tell the devicemanager that the action finished with error. + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); + reply->deleteLater(); + return; + } + + qCDebug(dcCoapClient) << "Uploaded message successfully" << reply; + + // Tell the device manager that the action execution finished successfully + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); + + } + + // Always make sure the reply will be deleted + reply->deleteLater(); + } + + // This method will be called if the CoAP socket received a notification from an observed resource + void DevicePluginCoapClient::onNotificationReceived(const CoapObserveResource &resource, const int ¬ificationNumber, const QByteArray &payload) + { + qCDebug(dcCoapClient) << "Got notification from observed resource" << notificationNumber << resource.url().path() << endl << payload; + + // Create the params for the event + ParamList paramList; + paramList.append(Param("time", payload)); + + // Tell the device manager we got an event + emitEvent(Event(timeEventTypeId, m_device->id(), paramList)); + } + \endcode + + \section1 Test the plugin - networkinfo.pro */ diff --git a/doc/write-plugins.qdoc b/doc/write-plugins.qdoc index 17faf39e..292f5dcc 100644 --- a/doc/write-plugins.qdoc +++ b/doc/write-plugins.qdoc @@ -38,7 +38,6 @@ \list \li Allow only one \l{Device} \li Implement the \l{DevicePlugin::deviceRemoved()}{deviceRemoved()} method - \li Use \l{DevicePlugin}{Plugin} configurations \li Use the \l{Coap}{CoAP} library \endlist \endlist