diff --git a/doc/guh.qdoc b/doc/guh.qdoc index 8b9ec4e4..a7f49f8e 100644 --- a/doc/guh.qdoc +++ b/doc/guh.qdoc @@ -40,7 +40,7 @@ \li \l{Getting started} \li \l{The plugin JSON File} \li \l{Tutorial 1 - The "Minimal" plugin} - \li \l{Tutorial 2 - The "NetworkInfo" plugin} + \li \l{Tutorial 2 - The "Buttons" plugin} \endlist \section1 Quicklinks diff --git a/doc/tutorial1.qdoc b/doc/tutorial1.qdoc index fca7a7f1..b236fa06 100644 --- a/doc/tutorial1.qdoc +++ b/doc/tutorial1.qdoc @@ -1,7 +1,7 @@ /*! \page tutorial1.html \title Tutorial 1 - The "Minimal" plugin - \brief The smallest and simplest plugin possible + \brief This tutorial shows you how to open, edit, build and load the first plugin. \ingroup tutorials This first tutorial shows you how to open, edit, build and load the first plugin. @@ -123,6 +123,8 @@ If you started \b {\tt guhd} with the parameters \b {\tt{-n -d Minimal}} you will see following debug output: \code + ... + Connection: Tcp server: new client connected: "127.0.0.1" Minimal: Hello word! Setting up a new device: "Minimal device" Minimal: The new device has the DeviceId "{b8d1f5a3-e892-4995-94b1-fa9aef662db2}" @@ -133,6 +135,6 @@ \endcode \endlist - Now you can take a look at \l{Tutorial 2 - The "NetworkInfo" plugin}. + Now you can take a look at \l{Tutorial 2 - The "Buttons" plugin}. */ diff --git a/doc/tutorial2.qdoc b/doc/tutorial2.qdoc index 0e9a23fa..e49a8234 100644 --- a/doc/tutorial2.qdoc +++ b/doc/tutorial2.qdoc @@ -1,40 +1,43 @@ /*! \page tutorial2.html - \title Tutorial 2 - The "NetworkInfo" plugin - \brief The first plugin which actualy does something. + \title Tutorial 2 - The "Buttons" plugin + \brief This plugin demonstrates the usage of events and actions. \ingroup tutorials - In the second tutorial we make our own first plugin with the name \b {"Network Info"}. We will use this name for the naming concentions of the filenames. + In the second tutorial we make our own first plugin with the name \b {"Buttons"}. We will use this name for the naming concentions of the filenames. - This plugin will use the \l{NetworkManager} hardware resource to fetch the location and WAN ip of your internet connection from \l{http://ip-api.com/json}. It will have an \l Action called \e "update" which will refresh the \l{State}{States} of the \l{Device}. + This plugin will show you how to implement \l{Action}{Actions}. -In order to get started with our new \b {"Network Info"} plugin we use the minimal plugin as template and start from there. Make a copy of the minimal folder and name the new folder \b networkinfo-diy. In this case \b{networkinfo-diy} because the folder \b networkinfo already exits from the \tt plugin-template repository. + In order to get started with our new \b {"Button"} plugin we use the \b {"Minimal"} plugin as template and start from there. Make a copy of the minimal folder and name the new folder \b buttons-diy. In this case \b{buttons-diy} because the folder \b buttons already exits from the \tt plugin-template repository. \section1 Create the basic structure \code - $ cp -rv minimal/ networkinfo-diy - ‘minimal/’ -> ‘networkinfo-diy’ - ‘minimal/plugins.pri’ -> ‘networkinfo-diy/plugins.pri’ - ‘minimal/minimal.pro’ -> ‘networkinfo-diy/minimal.pro’ - ‘minimal/devicepluginminimal.json’ -> ‘networkinfo-diy/devicepluginminimal.json’ - ‘minimal/devicepluginminimal.h’ -> ‘networkinfo-diy/devicepluginminimal.h’ - ‘minimal/devicepluginminimal.cpp’ -> ‘networkinfo-diy/devicepluginminimal.cpp’ + $cp -rv minimal/ buttons-diy/ + + ‘minimal/’ -> ‘buttons-diy/’ + ‘minimal/plugins.pri’ -> ‘buttons-diy/plugins.pri’ + ‘minimal/minimal.pro’ -> ‘buttons-diy/minimal.pro’ + ‘minimal/devicepluginminimal.json’ -> ‘buttons-diy/devicepluginminimal.json’ + ‘minimal/minimal.pro.user’ -> ‘buttons-diy/minimal.pro.user’ + ‘minimal/devicepluginminimal.h’ -> ‘buttons-diy/devicepluginminimal.h’ + ‘minimal/devicepluginminimal.cpp’ -> ‘buttons-diy/devicepluginminimal.cpp’ \endcode - \note Delete the minimal.pro.user file if there is any. + \note Delete the \tt minimal.pro.user file if there is any. Now we can rename the files using the plugin name convention: \code - $ cd networkinfo-diy/ - $ mv minimal.pro networkinfo.pro - $ mv devicepluginminimal.h devicepluginnetworkinfo.h - $ mv devicepluginminimal.cpp devicepluginnetworkinfo.cpp - $ mv devicepluginminimal.json devicepluginnetworkinfo.json + $ cd buttons-diy/ + + $ mv minimal.pro buttons.pro + $ mv devicepluginminimal.h devicepluginbuttons.h + $ mv devicepluginminimal.cpp devicepluginbuttons.cpp + $ mv devicepluginminimal.json devicepluginbuttons.json \endcode - \section2 Change the \tt networkinfo.pro - Open the \tt networkinfo.pro file with the \e {Qt Creator} and open that file in the editor: + \section2 Change the \tt buttons.pro + Open the \tt buttons.pro file with the \e {Qt Creator} and open that file in the editor: \code include(plugins.pri) @@ -52,30 +55,30 @@ In order to get started with our new \b {"Network Info"} plugin we use the minim \endcode \list 1 - \li Change the \tt TARGET name form \tt guh_devicepluginminimal \unicode{0x2192} \tt guh_devicepluginnetworkinfo - \li Change the SOURCES file from \tt devicepluginminimal.cpp \unicode{0x2192} \tt devicepluginnetworkinfo.cpp - \li Change the HEADERS file from \tt devicepluginminimal.h \unicode{0x2192} \tt devicepluginnetworkinfo.h + \li Change the \tt TARGET name form \tt guh_devicepluginminimal \unicode{0x2192} \tt guh_devicepluginbuttons + \li Change the SOURCES file from \tt devicepluginminimal.cpp \unicode{0x2192} \tt devicepluginbuttons.cpp + \li Change the HEADERS file from \tt devicepluginminimal.h \unicode{0x2192} \tt devicepluginbuttons.h \endlist Your file sould look now like this: \code include(plugins.pri) - TARGET = $$qtLibraryTarget(guh_devicepluginnetworkinfo) + TARGET = $$qtLibraryTarget(guh_devicepluginbuttons) message("Building $$deviceplugin$${TARGET}.so") SOURCES += \ - devicepluginnetworkinfo.cpp \ + devicepluginbuttons.cpp \ HEADERS += \ - devicepluginnetworkinfo.h \ + devicepluginbuttons.h \ \endcode If you save the file, the header and source file should appear in the project structure of the \e {Qt Creator}. - \section2 Change the \tt devicepluginnetworkinfo.h - Open the \tt devicepluginnetworkinfo.h file. + \section2 Change the \tt devicepluginbuttons.h + Open the \tt devicepluginbuttons.h file. \code #ifndef DEVICEPLUGINMINIMAL_H @@ -102,41 +105,40 @@ In order to get started with our new \b {"Network Info"} plugin we use the minim \endcode \list 1 - \li Change the \tt {#ifndef}, \tt {#define} and \tt #define name from \tt DEVICEPLUGINMINIMAL_H \unicode{0x2192} \tt DEVICEPLUGINNETWORKINFO_H - \li Change the class name form \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginNetworkInfo - \li Change in the \tt Q_PLUGIN_METADATA line the \tt FILE parameter from \tt "devicepluginminimal.json" \unicode{0x2192} \tt "devicepluginnetworkinfo.json" to set \l{The Plugin JSON file}. - \li Change the constructor name from \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginNetworkInfo + \li Change the \tt {#ifndef}, \tt {#define} and \tt #define name from \tt DEVICEPLUGINMINIMAL_H \unicode{0x2192} \tt DEVICEPLUGINBUTTONS_H + \li Change the class name form \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginButtons + \li Change in the \tt Q_PLUGIN_METADATA line the \tt FILE parameter from \tt "devicepluginminimal.json" \unicode{0x2192} \tt "devicepluginbuttons.json" to set \l{The Plugin JSON file}. + \li Change the constructor name from \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginButtons \endlist Your file sould look now like this: - \code - #ifndef DEVICEPLUGINNETWORKINFO_H - #define DEVICEPLUGINNETWORKINFO_H + #ifndef DEVICEPLUGINBUTTONS_H + #define DEVICEPLUGINBUTTONS_H #include "plugin/deviceplugin.h" #include "devicemanager.h" - class DevicePluginNetworkInfo : public DevicePlugin + class DevicePluginButtons : public DevicePlugin { Q_OBJECT - Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginnetworkinfo.json") + Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginbuttons.json") Q_INTERFACES(DevicePlugin) public: - explicit DevicePluginNetworkInfo(); + explicit DevicePluginButtons(); DeviceManager::HardwareResources requiredHardware() const override; DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; }; - #endif // DEVICEPLUGINNETWORKINFO_H + #endif // DEVICEPLUGINBUTTONS_H \endcode - \section2 Change the \tt devicepluginnetworkinfo.cpp + \section2 Change the \tt devicepluginbuttons.cpp - Open the \tt devicepluginnetworkinfo.h file. + Open the \tt devicepluginbuttons.cpp file. \code #include "devicepluginminimal.h" @@ -163,55 +165,224 @@ In order to get started with our new \b {"Network Info"} plugin we use the minim \endcode \list 1 - \li Change the \tt {#include "devicepluginminimal.h"} \unicode{0x2192} \tt {#include "devicepluginnetworkinfo.h"} - \li Change in each method implementation the \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginNetworkInfo namespace. + \li Change the \tt {#include "devicepluginminimal.h"} \unicode{0x2192} \tt {#include "devicepluginbuttons.h"} + \li Change in each method implementation the \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginButtons namespace. + \li Change each \tt {qCDebug(dcMinimal)} \unicode{0x2192} \tt {qCDebug(dcButtons)}, you will see later why. \endlist Your file sould look now like this: \code - #include "devicepluginnetworkinfo.h" + #include "devicepluginbuttons.h" #include "plugininfo.h" - DevicePluginNetworkInfo::DevicePluginNetworkInfo() + DevicePluginButtons::DevicePluginButtons() { } - DeviceManager::HardwareResources DevicePluginNetworkInfo::requiredHardware() const + DeviceManager::HardwareResources DevicePluginButtons::requiredHardware() const { return DeviceManager::HardwareResourceNone; } - DeviceManager::DeviceSetupStatus DevicePluginNetworkInfo::setupDevice(Device *device) + DeviceManager::DeviceSetupStatus DevicePluginButtons::setupDevice(Device *device) { Q_UNUSED(device) - qCDebug(dcMinimal) << "Hello word! Setting up a new device:" << device->name(); - qCDebug(dcMinimal) << "The new device has the DeviceId" << device->id().toString(); - qCDebug(dcMinimal) << device->params(); + qCDebug(dcButtons) << "Hello word! Setting up a new device:" << device->name(); + qCDebug(dcButtons) << "The new device has the DeviceId" << device->id().toString(); + qCDebug(dcButtons) << device->params(); return DeviceManager::DeviceSetupStatusSuccess; } \endcode - The basic structure of our new \l{DevicePlugin} is finished. You may recognize that the \tt {plugininfo.h} file does not exist yet. You have to build the plugin to generate that file. Each time you change \l{The Plugin JSON file} this file will be new generated. Once the build step is finished, you can take a look at that file (curser in line \tt {#include "plugininfo.h"} and press \tt F2) + The basic structure of our new \l{DevicePlugin} is finished. You may recognize that the \tt {plugininfo.h} file does not exist yet. You have to build the plugin to generate that file. Each time you change \l{The Plugin JSON file} this file will be new generated. + + \section2 Change the \tt devicepluginbuttons.json + + Our new plugin will have the name \b {Buttons}, the corresponding logging categorie will be \tt dcButtons (defined from the \e {idName}). There will be one new \l{DeviceClass} with the name \b {Simple Button}. This \l{DeviceClass} will have one \l{EventType} and one \l{ActionType}. + + The current \tt devicepluginbuttons.json should still look like this: + \code + { + "name": "Minimal plugin", + "idName": "Minimal", + "id": "6878754a-f27d-4007-a4e5-b030b55853f5", + "vendors": [ + { + "name": "Minimal vendor", + "idName": "minimal", + "id": "3897e82e-7c48-4591-9a2f-0f56c55a96a4", + "deviceClasses": [ + { + "deviceClassId": "7014e5f1-5b04-407a-a819-bbebd11fa372", + "idName": "minimal", + "name": "Minimal device", + "createMethods": ["user"], + "paramTypes": [ + { + "name": "name", + "type": "QString", + "defaultValue": "Simple button device default name" + } + ] + } + ] + } + ] + } + \endcode + + Now we change \l{The Plugin JSON file} for our new plugin: + \list 1 + \li Set the plugin \e name to \b Buttons + \li Set the plugin \e idName to \b Buttons (used for logging category name -> \e {dcButtons}) + \li Use \b {\tt uuidgen} to create a new UUID. Replace the old plugin \e id with the new one. + \li Set the vendor \e name to \b {Button vendor} + \li Set the vendor \e idName to \b buttons (used for VendorId variable definition in the \tt plugininfo.h -> \e buttonsVendorId}) + \li Use \b {\tt uuidgen} to create a new UUID. Replace the old vendor \e id with the new one. + \li Use \b {\tt uuidgen} to create a new UUID. Replace the old DeviceClassId \e id with the new one. + \li Set the device class \e idName to \b simpleButton (used for DeviceClassId variable definition in the \tt plugininfo.h -> \e simpleButtonDeviceClassId}) + \li Set the device class \e name to \b {Simple Button} + \li The \e createMethod is still the same: \b user + \li This single \l ParamType called \b name should be in every single DeviceClass to allow the user to give a custom name to a \l{Device}. Just change the defaultValue for the name to \b {Simple button device default name}. + \endlist + + Your device should now look like this (with your own UUIDs): + \code + { + "name": "Buttons", + "idName": "Buttons", + "id": "7bfd3af5-7983-4540-9398-d14085d069f4", + "vendors": [ + { + "name": "Button vendor", + "idName": "buttons", + "id": "fd2ae067-2c3d-4332-9c4b-ee0af653bcaf", + "deviceClasses": [ + { + "deviceClassId": "73bb670b-e7a3-40da-bd6f-3260f017ec80", + "idName": "simpleButton", + "name": "Simple Button", + "createMethods": ["user"], + "paramTypes": [ + { + "name": "name", + "type": "QString", + "defaultValue": "Simple button device default name" + } + ] + } + ] + } + ] + } + \endcode + + Now the basic structure is finished and we have a new \l{DevicePlugin}, a new \l{Vendor} and a new \l{DeviceClass}. + + Now we have to add an \l{ActionType} which will called \b press and gives the user the possibility to press this button device (see \l{The ActionType definition}) + + \list 1 + \li Use \b {\tt uuidgen} to create a new UUID for this \l{ActionType}. + \li Set the \e idName of this \l{ActionType} to \b pressSimpleButton (used for ActionTypeId variable definition in the \tt plugininfo.h -> \e pressSimpleButtonActionTypeId}) + \li Set the \e name of this \l{ActionType} to \b {press the button} + \endlist + \code + "actionTypes": [ + { + "id": "64c4ced5-9a1a-4858-81dd-1b5c94dba495", + "idName": "pressSimpleButton", + "name": "press the button" + } + ] + \endcode + + Now we have to add an \l{EventType} which will be emitted when the button was pressed. + + \list 1 + \li Use \b {\tt uuidgen} to create a new UUID for this \l{EventType}. + \li Set the \e idName of this \l{EventType} to \b simpleButtonPressed (used for EventTypeId variable definition in the \tt plugininfo.h -> \e simpleButtonPressedEventTypeId}) + \li Set the \e name of this \l{EventType} to \b {button pressed} + \endlist + + \code + "eventTypes": [ + { + "id": "f9652210-9aed-4f38-8c19-2fd54f703fbe", + "idName": "simpleButtonPressed", + "name": "button pressed" + } + ] + \endcode + + \code + { + "name": "Buttons", + "idName": "Buttons", + "id": "7bfd3af5-7983-4540-9398-d14085d069f4", + "vendors": [ + { + "name": "Button vendor", + "idName": "buttons", + "id": "fd2ae067-2c3d-4332-9c4b-ee0af653bcaf", + "deviceClasses": [ + { + "deviceClassId": "73bb670b-e7a3-40da-bd6f-3260f017ec80", + "idName": "simpleButton", + "name": "Simple Button", + "createMethods": ["user"], + "paramTypes": [ + { + "name": "name", + "type": "QString", + "defaultValue": "Simple button device default name" + } + ], + "actionTypes": [ + { + "id": "64c4ced5-9a1a-4858-81dd-1b5c94dba495", + "idName": "pressSimpleButton", + "name": "press the button" + } + ], + "eventTypes": [ + { + "id": "f9652210-9aed-4f38-8c19-2fd54f703fbe", + "idName": "simpleButtonPressed", + "name": "button pressed" + } + ] + } + ] + } + ] + } + \endcode + + Rebuild the entire project to generate the new \tt {plugininfo.h}. You need to call the \underline{Rebuild all} command in the \e {Qt Creator} to take over the changes in the \tt plugininfo.h . + + If you make a syntax error in the JSON file, you will get a build error with the position of the syntax error in the JSON file. Now your definitions should be in the plugininfo.h file and ready to use in the plugin source code. You will see in the build output following section: \code - /usr/bin/guh-generateplugininfo ../networkinfo-diy/devicepluginnetworkinfo.json plugininfo.h - ../networkinfo-diy/devicepluginnetworkinfo.json -> plugininfo.h + /usr/bin/guh-generateplugininfo ../buttons-diy/devicepluginbuttons.json plugininfo.h + ../buttons-diy/devicepluginbuttons.json -> plugininfo.h --> generate plugininfo.h - PluginId for plugin "Minimal plugin" = 6878754a-f27d-4007-a4e5-b030b55853f5 - define VendorId MinimalVendorId = 3897e82e-7c48-4591-9a2f-0f56c55a96a4 - define DeviceClassId minimalDeviceClassId = 7014e5f1-5b04-407a-a819-bbebd11fa372 - define logging category: "dcMinimal" + PluginId for plugin "Buttons" = 7bfd3af5-7983-4540-9398-d14085d069f4 + define VendorId ButtonsVendorId = fd2ae067-2c3d-4332-9c4b-ee0af653bcaf + define DeviceClassId simpleButtonDeviceClassId = 73bb670b-e7a3-40da-bd6f-3260f017ec80 + define logging category: "dcButtons" --> generated successfully "plugininfo.h" --> generate extern-plugininfo.h --> generated successfully "extern-plugininfo.h" \endcode - This shows you how the \tt{plugininfo.h} and \tt{extern-plugininfo.h} will be generated. As you can see the UUID definitions and the logging category will be definend for the \b {Minimal} plugin because we have not changed yet \l{The Plugin JSON file}. + This shows you how the \tt{plugininfo.h} and \tt{extern-plugininfo.h} will be generated. As you can see the UUID definitions and the logging category were definend for the \b {Buttons} plugin. - The generated \tt {plugininfo.h} file will look like this: + Once the build step is finished, you can take a look at that file (curser in line \tt {#include "plugininfo.h"} and press \tt F2) + + Your \tt plugininfo.h should now look like this (with your own UUIDs): \code #ifndef PLUGININFO_H #define PLUGININFO_H @@ -219,41 +390,86 @@ In order to get started with our new \b {"Network Info"} plugin we use the minim #include // Id definitions - PluginId pluginId = PluginId("6878754a-f27d-4007-a4e5-b030b55853f5"); - VendorId minimalVendorId = VendorId("3897e82e-7c48-4591-9a2f-0f56c55a96a4"); - DeviceClassId minimalDeviceClassId = DeviceClassId("7014e5f1-5b04-407a-a819-bbebd11fa372"); + PluginId pluginId = PluginId("7bfd3af5-7983-4540-9398-d14085d069f4"); + VendorId buttonsVendorId = VendorId("fd2ae067-2c3d-4332-9c4b-ee0af653bcaf"); + DeviceClassId simpleButtonDeviceClassId = DeviceClassId("73bb670b-e7a3-40da-bd6f-3260f017ec80"); + ActionTypeId pressSimpleButtonActionTypeId = ActionTypeId("64c4ced5-9a1a-4858-81dd-1b5c94dba495"); + EventTypeId simpleButtonPressedEventTypeId = EventTypeId("f9652210-9aed-4f38-8c19-2fd54f703fbe"); // Loging category - Q_DECLARE_LOGGING_CATEGORY(dcMinimal) - Q_LOGGING_CATEGORY(dcMinimal, "Minimal") + Q_DECLARE_LOGGING_CATEGORY(dcButtons) + Q_LOGGING_CATEGORY(dcButtons, "Buttons") - #endif // PLUGININFO_H - \endcode + #endif // PLUGININFO_H + \endcode + + \section1 Writing the plugin + + Now we have our basic for starting to implement the new plugin. If you install the current plugin, you would already see the plugin implementation in \tt guh-cli, but it would do nothing because we have not implemented yet the code. + + \section2 The \tt executeAction method + + Every plugin with \l{Action}{Actions} needs the \l{DevicePlugin::executeAction()} method which should be overridden in your own plugin. - The generated \tt {extern-plugininfo.h} file will look like this: \code - #ifndef EXTERNPLUGININFO_H - #define EXTERNPLUGININFO_H - #include "typeutils.h" - #include + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + \endcode - // Id definitions - extern VendorId minimalVendorId; - extern DeviceClassId minimalDeviceClassId; + Here is the implemented method: + \code + // Check the DeviceClassId for "Simple Button" + if (device->deviceClassId() == simpleButtonDeviceClassId ) { - // Logging category definition - Q_DECLARE_LOGGING_CATEGORY(dcMinimal) + // check if this is the "press" action + if (action.actionTypeId() == pressSimpleButtonActionTypeId) { - #endif // EXTERNPLUGININFO_H + qCDebug(dcButtons) << "Simple button" << device->paramValue("name").toString() << "was pressed"; + + // Emit the "button pressed" event + Event event(simpleButtonPressedEventTypeId, device->id()); + emit emitEvent(event); + + return DeviceManager::DeviceErrorNoError; + } + return DeviceManager::DeviceErrorActionTypeNotFound; + } \endcode - \section2 Change the \tt devicepluginnetworkinfo.json + When a user or the \l {guhserver::RuleEngine}{RuleEngine} calls the executeAction method, our plugin will first check if the \l DeviceClassId matches, then the \l ActionTypeId. If both are correct, we can emit our \l Event to show that the simple button \l Device was pressed. + + You can see in the implementation that a new \l{Event} will be generated in guh. This is the way how you emit an Event for a device. + + \section1 Test the plugin + Rebuild the whole project to make shore all changes are registered and install the plugin (see \l{Tutorial 1 - The "Minimal" plugin}{Tutorial 1}). + + \list 1 + \li Start guh with following command: + + \code + $ guhd -n -d Buttons + \endcode + + \li Start guh-cli and add the a new \b {"Simple Button"} devcice. Give an appropriate name like \b {Test button}. + \li Use guh-cli to execute the \b {press the button} action: + + \tt "Devices" \unicode{0x2192} \tt "Execute an action" \unicode{0x2192} \tt {"Your device name (Simple Button)"} \unicode{0x2192} \tt {press the button} + + \endlist + \code + ... + Connection: Tcp server: new client connected: "127.0.0.1" + Buttons: Simple button "Test button" was pressed + RuleEngine: got event: Event(EventTypeId: "{f9652210-9aed-4f38-8c19-2fd54f703fbe}", + DeviceId"{967d4c50-7cc5-4114-865a-822c64a1e7ce}") "Simple Button" + QUuid ("{f9652210-9aed-4f38-8c19-2fd54f703fbe}") + \endcode + Now you have successfully implemented you first DeviceClass, which has an \l Action and an \l Event and can be used together with the \l {guhserver::RuleEngine}{RuleEngine}. - + Now you can take a look at \l{Tutorial 3 - The "Power Button" device}. */ diff --git a/doc/tutorial3.qdoc b/doc/tutorial3.qdoc new file mode 100644 index 00000000..3f26e5f9 --- /dev/null +++ b/doc/tutorial3.qdoc @@ -0,0 +1,135 @@ +/*! + \page tutorial3.html + \title Tutorial 3 - The "Power Button" device + \brief This device demonstrates the usage of states and params. + \ingroup tutorials + + + In the third tutorial we use the project from \l{Tutorial 2 - The "Buttons" plugin}{Tutorial 2} and add a new button type to the existing plugin. + + This device will show you how to implement \l{State}{States} and how \l{Param}{Params} will be used in \l{Action}{Actions} and \l{Event}{Events}. + + Our new "Power Button" will be able to execute an \l Action and set the \l State of the \l Device \tt true or \tt false . + + \section1 Add a new DeviceClass + + Open the \tt devicepluginbuttons.json: + + Add the new DeviceClass right behind the existing one: + + \code + ... + }, + { + "deviceClassId": "fb587886-a649-42d0-9609-8423de587685", + "idName": "powerButton", + "name": "Power Button", + "createMethods": ["user"], + "paramTypes": [ + { + "name": "name", + "type": "QString", + "defaultValue": "Power button device default name" + } + ], + "actionTypes": [ + { + "id": "1e97a057-c525-463d-a593-9b8dce16645f", + "idName": "setPowerButton", + "name": "set power", + "paramTypes": [ + { + "name": "power", + "type": "bool", + "defaultValue": false + } + ] + } + ], + "stateTypes": [ + { + "id": "9328693e-9054-47bc-b95f-ae3e42d50b8b", + "idName": "power", + "name": "power", + "type": "bool", + "defaultValue": false + } + ] + } + + ... + \endcode + + As you can see we added a \l ParamType to the \l ActionType, removed the \l EventType and added a \l StateType definition. + + When the \l Action "set power" will be executed, a \l Param named "power" will be passed with it from type \tt bool. This param holds the power status, which can be true or false and set the new \l State with the name "power". + + \section1 Writing the plugin + + Since we have a new ActionType and a new DeviceClass, we have to change the \tt {executeAction()} method in the \b {"Button"} plugin. + + \code + ... + + // Check the DeviceClassId for "Power Button" + if (device->deviceClassId() == powerButtonDeviceClassId ) { + + // check if this is the "set power" action + if (action.actionTypeId() == setPowerButtonActionTypeId) { + + // get the param value + Param powerParam = action.param("power"); + bool power = powerParam.value().toBool(); + + qCDebug(dcButtons) << "Power button" << device->paramValue("name").toString() << "set power to" << power; + + // Set the "power" state + device->setStateValue(powerStateTypeId, power); + + return DeviceManager::DeviceErrorNoError; + } + return DeviceManager::DeviceErrorActionTypeNotFound; + } + + ... + \endcode + + When a \l State value of a \l Device will be set, that will generate an \l Event in guh, which contains a \l Param holding the new \l State value. + + \note You \underline don't have to take care about that \l Event, it will be generated automatically and will have the same uuid as \l EventTypeId like the \l StateTypeId. This makes it possible for client applications to link the \l Event to the corresponding \l State which generated the \l Event. + + \note You \underline don't have to check if the \l State value has changed or not when you set the value. i.e. if the current \l State value of the power \l State is \tt true, and the \l Action \l Param is also \tt true, this code will not generate the \l Event because the value has not changed. + + \section1 Test the plugin + Rebuild the whole project to make shore all changes are registered and install the plugin (see \l{Tutorial 1 - The "Minimal" plugin}{Tutorial 1}). + + \list 1 + \li Start guh with following command: + + \code + $ guhd -n -d Buttons + \endcode + + \li Start guh-cli and add the a new \b {"Power Button"} \l Device. Give an appropriate name like \b {Light}. + \li Use guh-cli to check the current power \l State, it should be \tt false (default value). + \li Use guh-cli to execute the \b {set power} \l Action: + + \tt "Devices" \unicode{0x2192} \tt "Execute an action" \unicode{0x2192} \tt {"Your device name (Power Button)"} \unicode{0x2192} \tt {set power} \unicode{0x2192} \tt {true} + \endlist + + In the guhd stdout you should see the debug output from you plugin. + \code + ... + + Connection: Tcp server: new client connected: "127.0.0.1" + Buttons: Power button "Light" set power to true + RuleEngine: got event: Event(EventTypeId: "{9328693e-9054-47bc-b95f-ae3e42d50b8b}", + DeviceId"{2304632a-a77a-452f-b438-87e7d69e9a00}") "Power Button" + QUuid("{9328693e-9054-47bc-b95f-ae3e42d50b8b}") + \endcode + + Now you have successfully implemented you first DeviceClass, which has an parametrized \l Action and a \l State which generates an \l Event containig the new \l State value. This new DeviceClass can be used in the \l {guhserver::RuleEngine}{RuleEngine}. Feel free to play with the Device and the Rule engine to get a feeling how the system works. + + Now you can take a look at \l{Tutorial 4 - The alternative "Power Button"}. + +*/ diff --git a/doc/tutorial4.qdoc b/doc/tutorial4.qdoc new file mode 100644 index 00000000..f131c659 --- /dev/null +++ b/doc/tutorial4.qdoc @@ -0,0 +1,117 @@ +/*! + \page tutorial4.html + \title Tutorial 4 - The alternative "Power Button" + \brief This device demonstrates how a writable state works. + \ingroup tutorials + + In the fourth tutorial you will see how a \e writable \l State works. We use the \l DeviceClass \b {"Power Button"} from the previouse \l{Tutorial 3 - The "Power Button" device}{Tutorial 3} for that and create a new one called \b {"Alternative Power Button"}. It does exactly the same like the \b {"Power Button"} except it will be created in a different way for a good reason. + + \section1 Add a new DeviceClass + Let's start again with the \tt devicepluginbuttons.json and append a new \l DeviceClass definition: + + \code + ... + + }, + { + "deviceClassId": "910b2f58-70dc-4da3-89ae-9e7393290ccb", + "idName": "alternativePowerButton", + "name": "Alternative Power Button", + "createMethods": ["user"], + "paramTypes": [ + { + "name": "name", + "type": "QString", + "defaultValue": "Alternative power button device default name" + } + ], + "stateTypes": [ + { + "id": "fa63c0b9-10e5-4280-9cc2-243bf27c05ad", + "idName": "alternativePower", + "name": "power", + "type": "bool", + "defaultValue": false, + "writable": {} + } + ] + } + + ... + \endcode + + As you can see, there is only one \tt bool \l State which has the property \e {"writable"}. This property indicates, that this \l State is writable and we need an \l Action for doing that. You can find more details about this property in \l {The StateType definition} documentation. + + We learnend in the previouse tutorial that a \l State will allways generate an \l Event when he changes his \e {value}. This \l Event has the same UUID as the \l State which generated the \l Event. The same thing happens with the \l Action if you make a \l State writable. The devicemanager defines a new \l ActionType which has the same UUID as the \State which will be changed with the \l Action. + + \tt {\l{StateTypeId} == \l{EventTypeId} == \l{ActionTypeId}} + + This makes it possible for clients to link the \l Action to the \l State which will be changed to the \l Event which will be emitted if the \l State was changed. All the types have the same UUID and the same Param. + + \section1 Writing the plugin + The implementation in the \tt executeAction() is almost the same like in the previous tutorial execpt the UUID variables changed and a new debug output was inseted to show the UUID. + + \code + ... + + // Check the DeviceClassId for "Alternative Power Button" + if (device->deviceClassId() == alternativePowerButtonDeviceClassId) { + + // check if this is the "set power" action + if (action.actionTypeId() == alternativePowerActionTypeId) { + + // get the param value + Param powerParam = action.param("power"); + bool power = powerParam.value().toBool(); + + qCDebug(dcButtons) << "Alternative power button" << device->paramValue("name").toString() << "set power to" << power; + qCDebug(dcButtons) << "ActionTypeId :" << action.actionTypeId().toString(); + qCDebug(dcButtons) << "StateTypeId :" << alternativePowerStateTypeId.toString(); + + // Set the "power" state + device->setStateValue(alternativePowerStateTypeId, power); + + return DeviceManager::DeviceErrorNoError; + } + return DeviceManager::DeviceErrorActionTypeNotFound; + } + + ... + \endcode + + + \section1 Test the plugin + Rebuild the whole project to make shore all changes are registered and install the plugin (see \l{Tutorial 1 - The "Minimal" plugin}{Tutorial 1}). + + \list 1 + \li Start guh with following command: + + \code + $ guhd -n -d Buttons + \endcode + + \li Start guh-cli and add the a new \b {"Alternative Power Button"} \l Device. Give an appropriate name like \b {Alternative Light}. + \li Use guh-cli to check the current power \l State, it should be \tt false (default value). + \li Use guh-cli to execute the \b {set power} \l Action: + + \tt "Devices" \unicode{0x2192} \tt "Execute an action" \unicode{0x2192} \tt {"Your device name (Alternative Power Button)"} \unicode{0x2192} \tt {set power} \unicode{0x2192} \tt {true} + \endlist + + In the guhd stdout you should see the debug output from you plugin. You will notice that the ActionTypeId, StateTypeId and EventTypeId are equal. + + \code + ... + + Connection: Tcp server: new client connected: "127.0.0.1" + Buttons: Alternative power button "Alternative Light" set power to true + Buttons: ActionTypeId : "{fa63c0b9-10e5-4280-9cc2-243bf27c05ad}" + Buttons: StateTypeId : "{fa63c0b9-10e5-4280-9cc2-243bf27c05ad}" + RuleEngine: got event: Event(EventTypeId: "{fa63c0b9-10e5-4280-9cc2-243bf27c05ad}", + DeviceId"{bb1c6795-2701-49e3-9529-45d87136b731}") "Alternative Power Button" + QUuid("{fa63c0b9-10e5-4280-9cc2-243bf27c05ad}") + + \endcode + + + +*/ diff --git a/doc/tutorial5.qdoc b/doc/tutorial5.qdoc new file mode 100644 index 00000000..181cf93e --- /dev/null +++ b/doc/tutorial5.qdoc @@ -0,0 +1,586 @@ +/*! + \page tutorial5.html + \title Tutorial 5 - The "Network Info" plugin + \brief The plugin shows you how the Network Manager hardware resource works + \ingroup tutorials + + + In the second tutorial we make our own first plugin with the name \b {"Network Info"}. We will use this name for the naming concentions of the filenames. + + This plugin will use the \l{NetworkManager} hardware resource to fetch the location and WAN ip of your internet connection from \l{http://ip-api.com/json}. It will have an \l Action called \e "update" which will refresh the \l{State}{States} of the \l{Device}. + +In order to get started with our new \b {"Network Info"} plugin we use the minimal plugin as template and start from there. Make a copy of the minimal folder and name the new folder \b networkinfo-diy. In this case \b{networkinfo-diy} because the folder \b networkinfo already exits from the \tt plugin-template repository. + + \section1 Create the basic structure + \code + $ cp -rv minimal/ networkinfo-diy + ‘minimal/’ -> ‘networkinfo-diy’ + ‘minimal/plugins.pri’ -> ‘networkinfo-diy/plugins.pri’ + ‘minimal/minimal.pro’ -> ‘networkinfo-diy/minimal.pro’ + ‘minimal/devicepluginminimal.json’ -> ‘networkinfo-diy/devicepluginminimal.json’ + ‘minimal/devicepluginminimal.h’ -> ‘networkinfo-diy/devicepluginminimal.h’ + ‘minimal/devicepluginminimal.cpp’ -> ‘networkinfo-diy/devicepluginminimal.cpp’ + \endcode + + \note Delete the minimal.pro.user file if there is any. + + Now we can rename the files using the plugin name convention: + \code + $ cd networkinfo-diy/ + $ mv minimal.pro networkinfo.pro + $ mv devicepluginminimal.h devicepluginnetworkinfo.h + $ mv devicepluginminimal.cpp devicepluginnetworkinfo.cpp + $ mv devicepluginminimal.json devicepluginnetworkinfo.json + \endcode + + \section2 Change the \tt networkinfo.pro + Open the \tt networkinfo.pro file with the \e {Qt Creator} and open that file in the editor: + + \code + include(plugins.pri) + + TARGET = $$qtLibraryTarget(guh_devicepluginminimal) + + message("Building $$deviceplugin$${TARGET}.so") + + SOURCES += \ + devicepluginminimal.cpp \ + + HEADERS += \ + devicepluginminimal.h \ + + \endcode + + \list 1 + \li Change the \tt TARGET name form \tt guh_devicepluginminimal \unicode{0x2192} \tt guh_devicepluginnetworkinfo + \li Change the SOURCES file from \tt devicepluginminimal.cpp \unicode{0x2192} \tt devicepluginnetworkinfo.cpp + \li Change the HEADERS file from \tt devicepluginminimal.h \unicode{0x2192} \tt devicepluginnetworkinfo.h + \endlist + + Your file sould look now like this: + \code + include(plugins.pri) + + TARGET = $$qtLibraryTarget(guh_devicepluginnetworkinfo) + + message("Building $$deviceplugin$${TARGET}.so") + + SOURCES += \ + devicepluginnetworkinfo.cpp \ + + HEADERS += \ + devicepluginnetworkinfo.h \ + \endcode + + If you save the file, the header and source file should appear in the project structure of the \e {Qt Creator}. + + \section2 Change the \tt devicepluginnetworkinfo.h + Open the \tt devicepluginnetworkinfo.h file. + + \code + #ifndef DEVICEPLUGINMINIMAL_H + #define DEVICEPLUGINMINIMAL_H + + #include "plugin/deviceplugin.h" + #include "devicemanager.h" + + class DevicePluginMinimal : public DevicePlugin + { + Q_OBJECT + + Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginminimal.json") + Q_INTERFACES(DevicePlugin) + + public: + explicit DevicePluginMinimal(); + + DeviceManager::HardwareResources requiredHardware() const override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + }; + + #endif // DEVICEPLUGINMINIMAL_H + \endcode + + \list 1 + \li Change the \tt {#ifndef}, \tt {#define} and \tt #define name from \tt DEVICEPLUGINMINIMAL_H \unicode{0x2192} \tt DEVICEPLUGINNETWORKINFO_H + \li Change the class name form \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginNetworkInfo + \li Change in the \tt Q_PLUGIN_METADATA line the \tt FILE parameter from \tt "devicepluginminimal.json" \unicode{0x2192} \tt "devicepluginnetworkinfo.json" to set \l{The Plugin JSON file}. + \li Change the constructor name from \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginNetworkInfo + \endlist + + Your file sould look now like this: + + \code + #ifndef DEVICEPLUGINNETWORKINFO_H + #define DEVICEPLUGINNETWORKINFO_H + + #include "plugin/deviceplugin.h" + #include "devicemanager.h" + + class DevicePluginNetworkInfo : public DevicePlugin + { + Q_OBJECT + + Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginnetworkinfo.json") + Q_INTERFACES(DevicePlugin) + + public: + explicit DevicePluginNetworkInfo(); + + DeviceManager::HardwareResources requiredHardware() const override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + }; + + #endif // DEVICEPLUGINNETWORKINFO_H + \endcode + + \section2 Change the \tt devicepluginnetworkinfo.cpp + + Open the \tt devicepluginnetworkinfo.h file. + + \code + #include "devicepluginminimal.h" + #include "plugininfo.h" + + DevicePluginMinimal::DevicePluginMinimal() + { + } + + DeviceManager::HardwareResources DevicePluginMinimal::requiredHardware() const + { + return DeviceManager::HardwareResourceNone; + } + + DeviceManager::DeviceSetupStatus DevicePluginMinimal::setupDevice(Device *device) + { + Q_UNUSED(device) + qCDebug(dcMinimal) << "Hello word! Setting up a new device:" << device->name(); + qCDebug(dcMinimal) << "The new device has the DeviceId" << device->id().toString(); + qCDebug(dcMinimal) << device->params(); + + return DeviceManager::DeviceSetupStatusSuccess; + } + \endcode + + \list 1 + \li Change the \tt {#include "devicepluginminimal.h"} \unicode{0x2192} \tt {#include "devicepluginnetworkinfo.h"} + \li Change in each method implementation the \tt DevicePluginMinimal \unicode{0x2192} \tt DevicePluginNetworkInfo namespace. + \endlist + + Your file sould look now like this: + + \code + #include "devicepluginnetworkinfo.h" + #include "plugininfo.h" + + DevicePluginNetworkInfo::DevicePluginNetworkInfo() + { + } + + DeviceManager::HardwareResources DevicePluginNetworkInfo::requiredHardware() const + { + return DeviceManager::HardwareResourceNone; + } + + DeviceManager::DeviceSetupStatus DevicePluginNetworkInfo::setupDevice(Device *device) + { + Q_UNUSED(device) + qCDebug(dcMinimal) << "Hello word! Setting up a new device:" << device->name(); + qCDebug(dcMinimal) << "The new device has the DeviceId" << device->id().toString(); + qCDebug(dcMinimal) << device->params(); + + return DeviceManager::DeviceSetupStatusSuccess; + } + \endcode + + The basic structure of our new \l{DevicePlugin} is finished. You may recognize that the \tt {plugininfo.h} file does not exist yet. You have to build the plugin to generate that file. Each time you change \l{The Plugin JSON file} this file will be new generated. Once the build step is finished, you can take a look at that file (curser in line \tt {#include "plugininfo.h"} and press \tt F2) + + You will see in the build output following section: + \code + /usr/bin/guh-generateplugininfo ../networkinfo-diy/devicepluginnetworkinfo.json plugininfo.h + ../networkinfo-diy/devicepluginnetworkinfo.json -> plugininfo.h + --> generate plugininfo.h + PluginId for plugin "Minimal plugin" = 6878754a-f27d-4007-a4e5-b030b55853f5 + define VendorId MinimalVendorId = 3897e82e-7c48-4591-9a2f-0f56c55a96a4 + define DeviceClassId minimalDeviceClassId = 7014e5f1-5b04-407a-a819-bbebd11fa372 + define logging category: "dcMinimal" + --> generated successfully "plugininfo.h" + --> generate extern-plugininfo.h + --> generated successfully "extern-plugininfo.h" + \endcode + + This shows you how the \tt{plugininfo.h} and \tt{extern-plugininfo.h} will be generated. As you can see the UUID definitions and the logging category will be definend for the \b {Minimal} plugin because we have not changed yet \l{The Plugin JSON file}. + + The generated \tt {plugininfo.h} file will look like this: + \code + #ifndef PLUGININFO_H + #define PLUGININFO_H + #include "typeutils.h" + #include + + // Id definitions + PluginId pluginId = PluginId("6878754a-f27d-4007-a4e5-b030b55853f5"); + VendorId minimalVendorId = VendorId("3897e82e-7c48-4591-9a2f-0f56c55a96a4"); + DeviceClassId minimalDeviceClassId = DeviceClassId("7014e5f1-5b04-407a-a819-bbebd11fa372"); + + // Loging category + Q_DECLARE_LOGGING_CATEGORY(dcMinimal) + Q_LOGGING_CATEGORY(dcMinimal, "Minimal") + + #endif // PLUGININFO_H + \endcode + + The generated \tt {extern-plugininfo.h} file will look like this: + \code + #ifndef EXTERNPLUGININFO_H + #define EXTERNPLUGININFO_H + #include "typeutils.h" + #include + + // Id definitions + extern VendorId minimalVendorId; + extern DeviceClassId minimalDeviceClassId; + + // Logging category definition + Q_DECLARE_LOGGING_CATEGORY(dcMinimal) + + #endif // EXTERNPLUGININFO_H + \endcode + + \section2 Change the \tt devicepluginnetworkinfo.json + + Before we can write our plugin JSON file we need to know which \l{State}{States}, \l{Action}{Actions} will be available. You can take a look at the \l{http://ip-api.com/} page. For the plugin we will need thouse information in a format which we can parse i.e. JSON \unicode{0x2192} \l{http://ip-api.com/json}. + + For more details about how to write the JSON file please take a look at \l{The Plugin JSON file} documentation. + + \note As you can see in this example the \l Vendor for this \l DevicePlugin is the \e guh. Of course you can define here a new Vendor (using \tt uuidgen to generate a new UUID). Please take a look at the existing \l{Vendor}{Vendors} and check if your \l Vendor allready exists. If the \l{Vendor} exists, please copy the \e name, \e idName and \e id to make shore all \l{Device}{Devices} from one \l{Vendor} will be together in the system like in this example for \e guh. + + Our new plugin will have the name \b {"Network Info"}, the corresponding logging categorie will be \tt dcNetworkInfo (defined from the \e {idName}). There will be one new \l{DeviceClass} with the \e name \b {Info about Network}. This \l{DeviceClass} has 6 \l{StateType}{StateTypes} and one \l{ActionType}. + + \code + { + "name": "Network Info", + "idName": "NetworkInfo", + "id": "c16852d7-f123-4dd5-983d-fc2eedb885aa", + "vendors": [ + { + "name": "guh", + "idName": "guh", + "id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6", + "deviceClasses": [ + { + "deviceClassId": "6c9d4852-cdfa-4eba-9ff2-c084d6f9d756", + "idName": "info", + "name": "Info about Network", + "createMethods": ["user"], + "paramTypes": [ + { + "name": "name", + "type": "QString", + "defaultValue": "Network Information" + } + ], + "stateTypes": [ + { + "name": "ip address", + "id": "0b4751ca-f126-4369-bfc0-f745985ae59b", + "idName": "address", + "type": "QString", + "defaultValue": "-" + }, + { + "name": "city", + "id": "8c777cf7-1a54-4b80-a8fe-141ae2334a63", + "idName": "city", + "type": "QString", + "defaultValue": "-" + }, + { + "name": "country", + "id": "69a01d64-c68f-4175-85f3-69329fd66b52", + "idName": "country", + "type": "QString", + "defaultValue": "-" + }, + { + "name": "time zone", + "id": "ab5278ce-87e0-4a79-9d08-c989c50d62cb", + "idName": "timeZone", + "type": "QString", + "defaultValue": "-" + }, + { + "name": "lon", + "id": "5a3a54d3-afd4-464a-adba-23def0110ed7", + "idName": "lon", + "type": "double", + "defaultValue": 0 + }, + { + "name": "lat", + "id": "f7b52b93-688d-47bb-83cc-85a694f33537", + "idName": "lat", + "type": "double", + "defaultValue": 0 + } + ], + "actionTypes": [ + { + "name": "update", + "id": "0b4751ca-f126-4369-bfc0-f745985ae59b", + "idName": "update" + } + ] + } + ] + } + ] + } + \endcode + + Once you have changed \l{The Plugin JSON file} you should rebuild the whole project to make shore all changed will be considerated. In the \e {Qt Creator} got to the menu \unicode{0x2192} \b Build \unicode{0x2192} \b{Rebuild all} to create the new \tt plugininfo.h file. You should see in the build output something like this: + + \code + /usr/bin/guh-generateplugininfo ../networkinfo-diy/devicepluginnetworkinfo.json plugininfo.h + ../networkinfo-diy/devicepluginnetworkinfo.json -> plugininfo.h + --> generate plugininfo.h + PluginId for plugin "Network Info" = c16852d7-f123-4dd5-983d-fc2eedb885aa + define VendorId NetworkInfoVendorId = 2062d64d-3232-433c-88bc-0d33c0ba2ba6 + define DeviceClassId infoDeviceClassId = 6c9d4852-cdfa-4eba-9ff2-c084d6f9d756 + define StateTypeId addressStateTypeId = 0b4751ca-f126-4369-bfc0-f745985ae59b + define StateTypeId cityStateTypeId = 8c777cf7-1a54-4b80-a8fe-141ae2334a63 + define StateTypeId countryStateTypeId = 69a01d64-c68f-4175-85f3-69329fd66b52 + define StateTypeId timeZoneStateTypeId = ab5278ce-87e0-4a79-9d08-c989c50d62cb + define StateTypeId lonStateTypeId = 5a3a54d3-afd4-464a-adba-23def0110ed7 + define StateTypeId latStateTypeId = f7b52b93-688d-47bb-83cc-85a694f33537 + define logging category: "dcNetworkInfo" + --> generated successfully "plugininfo.h" + --> generate extern-plugininfo.h + --> generated successfully "extern-plugininfo.h" + \endcode + + \note You have to change the \tt {qCDebug(dcMinimal)} \unicode{0x2192} \tt {qCDebug(dcNetworkInfo)} because you have changed the plugin \e idName and therefore also the logging categorie. You need to start guh now with the parameter \b {\tt {guhd -n -d NetworkInfo}} to see the debug output of the new plugin. + + If you make a syntax error in the JSON file, you will get a build error with the position of the syntax error in the JSON file. Now your definitions should be in the plugininfo.h file and ready to use in the plugin source code. + + \section1 Writing the plugin + + Now we have our basic for starting to implement the new defined plugin. If you install the current plugin, start \tt guhd and add the a \b {Info about Network} device whith \b {\tt guh-cli} you can check the device states and should see something like this: + + \code + ======================================================== + -> States of device "Info about Network" {83a1c0bb-c169-4292-a100-85af5fa9a1a4}: + + ip address: - + city: - + country: - + time zone: - + lon: 0 + lat: 0 + -------------------------------------------------------- + + \endcode + + All defined states are allready availabe in the system and initialized with the \e defaultValue +parameter from \l{The Plugin JSON file}. + + \section2 Define the required hardware resource + Now we have to fetch the data from \l{http://ip-api.com/json} once the action \tt update will be executed. The first thing we have to define is the hardware resource. Since we are communicating with a REST API we need the \l{NetworkManager} hardware resource, which is basically a \l{http://doc.qt.io/qt-5/qnetworkaccessmanager.html}{QNetworkAccessManager} for all plugins. + + \code + DeviceManager::HardwareResources DevicePluginNetworkInfo::requiredHardware() const + { + return DeviceManager::HardwareResourceNetworkManager; + } + \endcode + + + \section2 Implement executeAction method + The next verry important method we have to implement and override is the \l{DevicePlugin::executeAction()} method, which will be calle when the user wants to execute a certain \l{Action}. + + \code + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + \endcode + + The implementation looks like this: + \code + DeviceManager::DeviceError DevicePluginNetworkInfo::executeAction(Device *device, const Action &action) + { + // check if this device is a Network info device using the DeviceClassId + if (device->deviceClassId() != infoDeviceClassId) { + return DeviceManager::DeviceErrorDeviceClassNotFound; + } + + // check if the requested action is our "update" action ... + if (action.actionTypeId() == updateActionTypeId) { + + // Print information that we are executing now the update action + qCDebug(dcNetworkInfo) << "Execute update action" << action.id(); + + // Create a network request + QNetworkRequest locationRequest(QUrl("http://ip-api.com/json")); + + // Call the GET method from the NetworkManager + QNetworkReply *reply = networkManagerGet(locationRequest); + + // Hash the reply, because we dont get the result immediately + m_asyncActionReplies.insert(reply, action.id()); + + // Hash the device for this action + m_asyncActions.insert(action.id(), device); + + // 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; + } + \endcode + + \section2 Implement networkManagerReplyReady method + Once the result of your pending network request is finished, the method \l{DevicePlugin::networkManagerReplyReady()} will be called, so we have to implement this method in our plugin header file and override the method: + \code + void networkManagerReplyReady(QNetworkReply *reply) override; + \endcode + + The implementation looks like this: + \code + // This method will be called whenever the reply from a NetworkManager call is ready. + void DevicePluginNetworkInfo::networkManagerReplyReady(QNetworkReply *reply) + { + // Make shore this is our reply + if (!m_asyncActionReplies.keys().contains(reply)) + return; + + // This is one of our action replies!! + + // Take the corresponding action from our hash + ActionId actionId = m_asyncActionReplies.take(reply); + + // Check the status code of the reply + if (reply->error()) { + + // Print the warning message + qCWarning(dcNetworkInfo) << "Reply error" << reply->errorString(); + + // The action execution is finished, and was not successfully + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareNotAvailable); + + // Important -> delete the reply to prevent a memory leak! + reply->deleteLater(); + return; + } + + // The request was successfull, lets read the payload + QByteArray data = reply->readAll(); + + // Important -> delete the reply to prevent a memory leak! + reply->deleteLater(); + + // Process the data from the reply + actionDataReady(actionId, data); + } + \endcode + + \section2 Update the state values + + Once the reply was read successfully we have to read the json document and set our state values to the fetched values. For this we implement a private method called: + + \code + void actionDataReady(const ActionId &actionId, const QByteArray &data); + \endcode + + First we have to check if the received data is a vaild JSON document. If not, the action execution \b "update" was not successfull and we have to report the error. Otherwise we read the data and set the state values of our device. + + \code + void DevicePluginNetworkInfo::actionDataReady(const ActionId &actionId, const QByteArray &data) + { + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + + // Check if we got a valid JSON document + if(error.error != QJsonParseError::NoError) { + qCWarning(dcNetworkInfo) << "Failed to parse JSON data" << data << ":" << error.errorString(); + + // the action execution is finished, and was not successfully + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); + return; + } + + // print the fetched data in json format to stdout + qCDebug(dcNetworkInfo) << jsonDoc.toJson(); + + // Get the device for this action + Device *device = m_asyncActions.take(actionId); + + // Parse the data and update the states of our device + QVariantMap dataMap = jsonDoc.toVariant().toMap(); + + // Set the city state + if (dataMap.contains("city")) { + device->setStateValue(cityStateTypeId, dataMap.value("city").toString()); + } + + // Set the country state + if (dataMap.contains("countryCode")) { + device->setStateValue(countryStateTypeId, dataMap.value("countryCode").toString()); + } + + // Set the wan ip + if (dataMap.contains("query")) { + device->setStateValue(addressStateTypeId, dataMap.value("query").toString()); + } + + // Set the time zone state + if (dataMap.contains("timezone")) { + device->setStateValue(timeZoneStateTypeId, dataMap.value("timezone").toString()); + } + + // Set the longitude state + if (dataMap.contains("lon")) { + device->setStateValue(lonStateTypeId, dataMap.value("lon").toDouble()); + } + + // Set the latitude state + if (dataMap.contains("lat")) { + device->setStateValue(latStateTypeId, dataMap.value("lat").toDouble()); + } + + qCDebug(dcNetworkInfo) << "Action" << actionId << "execution finished successfully."; + + // Emit the successfull action execution result to the device manager + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); + } + \endcode + + You can find the full example in the \tt plugin-templates \unicode{0x2192} \tt networkinfo folder. + + \section1 Test the plugin + + Rebuild the whole project to make shore all changes are registered and install the plugin (see \l{Tutorial 1 - The "Minimal" plugin}{Tutorial 1}). + + \list 1 + \li Start guh with following command: + + \code + $ guhd -n -d NetworkInfo + \endcode + + \li Start guh-cli and add the a new "Info" devcice. + \li Use guh-cli to check if the device states are initialized with the default values from \l{Change the devicepluginnetworkinfo.json}: + + \tt "Devices" \unicode{0x2192} \tt "List..." \unicode{0x2192} \tt {"List device states"} \unicode{0x2192} \tt {"Your device name"}. + \li Use guh-cli to execute the \b update action: + + \tt "Devices" \unicode{0x2192} \tt "Execute action" \unicode{0x2192} \tt {"Your device name"} \unicode{0x2192} \tt {update} + \li Use guh-cli to check if the device states were updated sucessfully: + + \tt "Devices" \unicode{0x2192} \tt "List..." \unicode{0x2192} \tt {"List device states"} \unicode{0x2192} \tt {"Your device name"}. + + \endlist +*/ + + diff --git a/doc/write-plugins.qdoc b/doc/write-plugins.qdoc index acd2304c..a6918fcb 100644 --- a/doc/write-plugins.qdoc +++ b/doc/write-plugins.qdoc @@ -9,7 +9,10 @@ \li \l{Tutorials} \list \li \l{Tutorial 1 - The "Minimal" plugin} - \li \l{Tutorial 2 - The "NetworkInfo" plugin} + \li \l{Tutorial 2 - The "Buttons" plugin} + \li \l{Tutorial 3 - The "Power Button" device} + \li \l{Tutorial 4 - The alternative "Power Button"} + \li \l{Tutorial 5 - The "Network Info" plugin} \endlist \endlist */