Add example structure

This commit is contained in:
Simon Stürz 2018-05-08 14:03:56 +02:00 committed by Michael Zanetti
parent 42857d75af
commit e900f5e2a2
31 changed files with 812 additions and 2197 deletions

View File

@ -0,0 +1,4 @@
TEMPLATE = subdirs
CONFIG += no_docs_target
SUBDIRS = \
template \

View File

@ -0,0 +1,503 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@ -0,0 +1,4 @@
# template
--------------------------------
Description of the plugin...

View File

@ -0,0 +1,5 @@
nymea-plugin-template (0.0.1) xenial; urgency=medium
* Initial release.
-- Developer Name <developer.name@example.com> Mon, 21 Aug 2017 10:12:00 +0000

View File

@ -0,0 +1 @@
9

View File

@ -0,0 +1,22 @@
Source: nymea-plugin-template
Section: utils
Priority: options
Maintainer: Developer Name <developer.name@example.com>
Build-depends: debhelper (>= 9.0.0),
libnymea1-dev,
Standards-Version: 3.9.3
Package: nymea-plugin-template
Architecture: any
Multi-Arch: same
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
Description: nymea.io plugin for template
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package will install the nymea.io plugin for template

View File

@ -0,0 +1,14 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: ${ProjectName}
Upstream-Contact: Developer Name <developer.name@example.com>
Copyright: 2018, Developer Name
License: LGPL-2.1
On Debian systems, the complete text of the GNU Lesser General
Public License can be found in `/usr/share/common-licenses/LGPL-2.1'.
Files: *
License: LGPL-2.1
Copyright: Developer Name <developer.name@example.com>

View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_deviceplugintemplate.so

View File

@ -0,0 +1,25 @@
#!/usr/bin/make -f
# -*- makefile -*-
export DH_VERBOSE=1
DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
PREPROCESS_FILES := $(wildcard debian/*.in)
$(PREPROCESS_FILES:.in=): %: %.in
sed 's,/@DEB_HOST_MULTIARCH@,$(DEB_HOST_MULTIARCH:%=/%),g' $< > $@
%:
dh $@ --parallel
override_dh_install: $(PREPROCESS_FILES:.in=)
make -j9 install DESTDIR=debian/tmp AM_UPDATE_INFO_DIR=no INSTALL_ROOT=debian/tmp
dh_install --fail-missing
override_dh_auto_build:
dh_auto_build
make lrelease
override_dh_auto_clean:
dh_auto_clean
rm -rf $(PREPROCESS_FILES:.in=)

View File

@ -0,0 +1 @@
3.0 (native)

View File

@ -0,0 +1,67 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Developer Name <developer.name@example.com> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "plugininfo.h"
#include "deviceplugintemplate.h"
DevicePluginExample::DevicePluginExample()
{
}
void DevicePluginExample::init()
{
// Initialize/create objects
}
void DevicePluginExample::startMonitoringAutoDevices()
{
// Start seaching for devices which can be discovered and added automatically
}
void DevicePluginExample::postSetupDevice(Device *device)
{
qCDebug(dcTemplate()) << "Post setup device" << device->name() << device->params();
// This method will be called once the setup for device is finished
}
void DevicePluginExample::deviceRemoved(Device *device)
{
qCDebug(dcTemplate()) << "Remove device" << device->name() << device->params();
// Clean up all data related to this device
}
DeviceManager::DeviceSetupStatus DevicePluginExample::setupDevice(Device *device)
{
qCDebug(dcTemplate()) << "Setup device" << device->name() << device->params();
return DeviceManager::DeviceSetupStatusSuccess;
}
DeviceManager::DeviceError DevicePluginExample::executeAction(Device *device, const Action &action)
{
qCDebug(dcTemplate()) << "Executing action for device" << device->name() << action.actionTypeId().toString() << action.params();
return DeviceManager::DeviceErrorNoError;
}

View File

@ -0,0 +1,54 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Developer Name <developer.name@example.com> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINEXAMPLE_H
#define DEVICEPLUGINEXAMPLE_H
#include "devicemanager.h"
#include "plugin/deviceplugin.h"
class DevicePluginExample: public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "deviceplugintemplate.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginExample();
void init() override;
void startMonitoringAutoDevices() override;
void postSetupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
private:
private slots:
};
#endif // DEVICEPLUGINEXAMPLE_H

View File

@ -0,0 +1,39 @@
{
"name": "Template",
"displayName": "Template",
"id": "00000000-0000-0000-0000-000000000000",
"vendors": [
{
"name": "guh",
"displayName": "nymea",
"id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
"deviceClasses": [
{
"name": "template",
"displayName": "Example",
"id": "00000000-0000-0000-0000-000000000000",
"deviceIcon": "None",
"setupMethod": "JustAdd",
"createMethods": ["User"],
"interfaces": [ ],
"basicTags": [ ],
"paramTypes": [
],
"stateTypes":[
],
"actionTypes":[
],
"eventTypes":[
]
}
]
}
]
}

View File

@ -0,0 +1,14 @@
include(/usr/include/nymea/plugin.pri)
TARGET = $$qtLibraryTarget(nymea_deviceplugintemplate)
message(============================================)
message("Qt version: $$[QT_VERSION]")
message("Building $$deviceplugin$${TARGET}.so")
SOURCES += \
deviceplugintemplate.cpp
HEADERS += \
deviceplugintemplate.h

View File

@ -167,6 +167,7 @@ def writeDocumentationContent(apiVersion, apiJson):
printInfo('--> Write API documentation content')
printInfo('--> API version: \"%s\"' % (version))
writeToFile('/*!')
writeToFile('In the following section you can find a detaild description of the current API version %s.' % apiVersion)
writeToFile('\list')
@ -190,6 +191,7 @@ def writeDocumentationContent(apiVersion, apiJson):
writeToFile("\section1 Full introspect")
writeCodeSection(apiJson)
writeToFile('*/')
###########################################################################

View File

@ -3,18 +3,29 @@
\title Getting Started
\brief Understanding the basic concept of nymea plugins
\b{\unicode{0x2192} \underline{\l{https://www.youtube.com/watch?v=B8oqItKCcgU}{Video for this tutorial}}}
Plugins in nymea are used to exand the functionalitys and capabilitys of the nymea server. A plugin is basically a shared library, which will be loaded dynamically from the nymea server during the start up process. Each plugin has a \e name, a \e uuid and a list of supported \e vendors which will be visible in the system once the plugin is loaded. Each of thouse \l{Vendor}{Vendors} contains a list of supported \l{DeviceClass}{DeviceClasses}. A \l{DeviceClass} describes how the supported \l{Device} looks like, how it will be created (\l{DeviceClass::CreateMethod}{CreateMethod}), how the setup (\l{DeviceClass::SetupMethod}{SetupMethod}) looks like and what you can do with the \l{Device}.
Plugins in nymea are used to expand the functionalitys and capabilitys of the nymea server. A plugin is basically a shared library,
which will be loaded dynamically from the nymea server during the start up process. Each plugin has a \e name, a \e uuid and a
list of supported \e vendors which will be visible in the system once the plugin is loaded. Each of thouse \l{Vendor}{Vendors} contains
a list of supported \l{DeviceClass}{DeviceClasses}. A \l{DeviceClass} describes how the supported \l{Device} looks like, how it will be
created (\l{DeviceClass::CreateMethod}{CreateMethod}), how the setup (\l{DeviceClass::SetupMethod}{SetupMethod}) looks like and what you
can do with the \l{Device}.
\section1 Devices
A device in nymea can represent a real device, a gateway or even a service like weather. When you want to represent you own device / service in nymea, you should try to abstract that device and think in terms like:
\list
\li \l{ParamType}{ParamTypes} \unicode{0x2192} A \l{Device} can have \l{Param}{Params}, which will be needed to set up the device (like IP addresses or device configurations) and give information for the user like name or location. The \l{ParamType} represents the description of an actual \l{Param}.
\li \l{StateType}{StateTypes} \unicode{0x2192} A \l{Device} can have \l{State}{States}, which basically represent a value of a \l{Device} like \e {current temperature} or \e ON/OFF. The \l{StateType} represents the description of an actual \l{State}.
\li \l{EventType}{EventTypes} \unicode{0x2192} A \l{Device} can emit \l{Event}{Events}, which basically represent a signal. An example of an \l{Event} could be: \e {Button pressed}. An \l{Event} can have \l{Param}{Params} to give the possibility to pass information with the signal. The \l{EventType} represents the description of an actual \l{Event}.
\li \l{ActionType}{ActionTypes} \unicode{0x2192} A \l{Device} can execute \l{Action}{Actions}, which represent basically a method for the \l{Device} which the user can execute. An example of an \l{Action} could be: \e {Set temperature}. An \l{Action} can have \l{Param}{Params} to give the possibility to parameterize the action. The \l{ActionType} represents the description of an actual \l{Action}.
\li \l{ParamType}{ParamTypes} \unicode{0x2192} A \l{Device} can have \l{Param}{Params}, which will be needed to set up the device
(like IP addresses or device configurations) and give information for the user like name or location. The \l{ParamType} represents
the description of an actual \l{Param}.
\li \l{StateType}{StateTypes} \unicode{0x2192} A \l{Device} can have \l{State}{States}, which basically represent a value of a \l{Device}
like \e {current temperature} or \e ON/OFF. The \l{StateType} represents the description of an actual \l{State}.
\li \l{EventType}{EventTypes} \unicode{0x2192} A \l{Device} can emit \l{Event}{Events}, which basically represent a signal.
An example of an \l{Event} could be: \e {Button pressed}. An \l{Event} can have \l{Param}{Params} to give the possibility to pass
information with the signal. The \l{EventType} represents the description of an actual \l{Event}.
\li \l{ActionType}{ActionTypes} \unicode{0x2192} A \l{Device} can execute \l{Action}{Actions}, which represent basically
a method for the \l{Device} which the user can execute. An example of an \l{Action} could be: \e {Set temperature}.
An \l{Action} can have \l{Param}{Params} to give the possibility to parameterize the action. The \l{ActionType} represents
the description of an actual \l{Action}.
\endlist
The \l{DeviceClass} represents the description of an actual \l{Device}.
@ -23,197 +34,8 @@
The \e libnymea provides a list of \l{Hardware Resources}{HardwareResources}, which can be used in every plugin. When sou start writing a plugin, you need to know which resource you will need. Each resource provides it's own interface for a \l{DevicePlugin}. In the plugin you don't have to take care about the resource.
\section1 Getting started with a plugin
In order to show how a plugin ist structured here is an example of the most minimalistic device plugin possible for the nymea system.
For an easier start we provide a set of plugin templates which can be used for your own plugin and to have a basic for the tutorials described in this documentation. You can get the templates with following command:
\code
$ git clone https://github.com/guh/plugin-templates.git
\endcode
This will create the \tt plugin-templates folder containing all templates and examples you will need to write your own plugin. Let's start with the smallest, simplest plugin.
\section2 Basic structure
The name of the plugin should be clear and inform about the content in one word. In this first minimalistic example the \b <pluginName> is \e "minimal".
The basic structure of the \e minimal plugin looks like this:
\code
$ cd plugin-templates
$ ls -l minimal/
devicepluginminimal.cpp
devicepluginminimal.h
devicepluginminimal.json
minimal.pro
plugins.pri
\endcode
\section3 minimal.pro
The \tt minimal.pro file is the project file of the plugin, which can be openend with the \tt{Qt Creator}. The name of this file should be the same as the folder name of the project. In this example the name would be \e "minimal".
\code
include(plugins.pri)
TARGET = $$qtLibraryTarget(nymea_devicepluginminimal)
message("Building $$deviceplugin$${TARGET}.so")
SOURCES += \
devicepluginminimal.cpp \
HEADERS += \
devicepluginminimal.h \
\endcode
The \b TARGET parameter definens the name of the resulting plugin lib file and should should have following name structure:
\tt {nymea_deviceplugin\b<pluginName>}
In this example the pluginname is \e minimal, which means the lib file name will be \e nymea_devicepluginminimal.so. You can check the name of the plugin in the "Project Messages" (\tt{alt + 6} in Qt Creator).
The \b SOURCES and \b HEADERS variables define the \tt .cpp and \tt .h files of your plugin like in any other Qt project.
\section3 plugins.pri
The \tt plugins.pri file contains all relevant definitions and configuration to build a plugin. Each plugin must contain this file and \underline{should not} be changed. In this file the precompiler \b nymea-generateplugininfo will be called.
\code
TEMPLATE = lib
CONFIG += plugin
QT += network
QMAKE_CXXFLAGS += -Werror -std=c++11
QMAKE_LFLAGS += -std=c++11
INCLUDEPATH += /usr/include/nymea
LIBS += -lnymea
infofile.output = plugininfo.h
infofile.commands = /usr/bin/nymea-generateplugininfo ${QMAKE_FILE_NAME} ${QMAKE_FILE_OUT}
infofile.depends = /usr/bin/nymea-generateplugininfo
infofile.CONFIG = no_link
JSONFILES = deviceplugin"$$TARGET".json
infofile.input = JSONFILES
QMAKE_EXTRA_COMPILERS += infofile
target.path = /usr/lib/nymea/plugins/
INSTALLS += target
\endcode
If you need an extra Qt module i.e. \tt serialport please add it in the \tt <pluginName>.pro file an \underline{not} in the \tt plugin.pri:
\note Please make sure you have installed the corresponding development libs i.e. \b{\tt{sudo apt-get install libqt5serialport5-dev}}
\code
QT += serialport
\endcode
\section3 devicepluginminimal.json
The properties of a plugin will be definend with in the \tt JSON file containing all needed information for nymea to load it. The name convention fot the json file is:
\tt {deviceplugin\b{<pluginName>}.json}
This file must have a clear, definend structure and looking like this:
\note For more details about the structure, values and objects please take a look at the \l{The Plugin JSON File} documentation.
\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"],
"basicTags": [
"Device"
],
"paramTypes": [
{
"name": "name",
"type": "QString",
"defaultValue": "Minimal device"
}
]
}
]
}
]
}
\endcode
In this minimal example of a device plugin we have one \l{Vendor} ("Minimal vendor") with the VendorId \tt {3897e82e-7c48-4591-9a2f-0f56c55a96a4}, which contains one \l{DeviceClass} with the name "Minimal device". The \l{DeviceClass} has one QString \l{Param} called \e name.
\section3 devicepluginminimal.h
The main header file of the plugin. The naming convention is:
\tt {deviceplugin\b<pluginName>.h}
In this file you can find the main class of the plugin: \e DevicePluginMinimal. As you can see the \e DevicePluginMinimal inherits from the class \l{DevicePlugin}. You can check out the \l{DevicePlugin} class description to find how a \l{DevicePlugin} looks like.
\code
#ifndef DEVICEPLUGINMINIMAL_H
#define DEVICEPLUGINMINIMAL_H
#include "plugin/deviceplugin.h"
#include "devicemanager.h"
class DevicePluginMinimal : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginminimal.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginMinimal();
DeviceManager::HardwareResources requiredHardware() const override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
};
#endif // DEVICEPLUGINBOBLIGHT_H
\endcode
As you can see this class has two methods which override the corresponding method in the \l{DevicePlugin} class. These two methods are pure virtual, which meas you \underline{must} implement them.
\section3 devicepluginminimal.cpp
The implementation of the \l{DevicePlugin}. The naming convention is:
\tt {deviceplugin\b<pluginName>.cpp}
\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) << "setup device" << device->name() << device->id();
return DeviceManager::DeviceSetupStatusSuccess;
}
\endcode
Now you can start with \l{Tutorial 1 - The "Minimal" plugin}.
*/

View File

@ -1,4 +1,5 @@
In following section you can find a detaild description of the current API version 1.4.
/*!
In the following section you can find a detaild description of the current API version 1.4.
\list
\li \l{Types}
\li \l{Methods}
@ -3764,3 +3765,4 @@ Emitted whenever a Rule was removed.
}
}
\endcode
*/

View File

@ -201,12 +201,12 @@
In order to send a request to the server, the client has to send an API message according to the format descibed \l{Message format}{here}.
The JSON content should always be sent as a compack JSON Object (without spaces, tabs and new line characters within the object \tt{{...}}).
At the end of this compact JSON string the payload must be terminated with the \tt{\n} character. This makes sure, that the parsing of the
At the end of this compact JSON string the payload must be terminated with the \tt{\\n} character. This makes sure, that the parsing of the
message is easier and a single message can be split in multiple cunckes during the transport. The client parsing should work the same way.
In order to demonstrate this with an example, the \l{JSONRPC.Hello} request could look like this:
\note Send following content as compact JSON ending with \tt{\n}:
\note Send following content as compact JSON ending with \tt{\\n}:
\code
{"id":122,"method":"JSONRPC.Hello"}
@ -218,7 +218,7 @@
to our request.
The server will return a response for this request. The response will have the same \tt id as your request. The json content of the response will
also be a compact JSON string ending with the \tt{\n} character.
also be a compact JSON string ending with the \tt{\\n} character.
\code
{"id":122,"params":{"authenticationRequired":false,"id":0,"initialSetupRequired":false,"language":"de_DE","name":"My nymea","protocol version":"1.2","pushButtonAuthAvailable":false,"server":"nymea","uuid":"{42842b0f-a7bb-4a94-b624-a55f31c5603e}","version":"0.8.3"},"status":"success"}

View File

@ -15,5 +15,3 @@
\annotatedlist coap-group
*/

View File

@ -36,7 +36,7 @@
\li \l{Getting started}
\li \l{The plugin JSON File}
\li \l{CreateMethods and SetupMethods}
\li \l{Tutorials}
\li \l{Plugin Tutorials}
\endlist
\section1 Client developers

View File

@ -0,0 +1,7 @@
/*!
\page plugin-tutorials.html
\title Plugin tutorials
\annotatedlist tutorials
*/

14
doc/plugin-wizard.qdoc Normal file
View File

@ -0,0 +1,14 @@
/*!
\example template
\title Plugin wizard
\ingroup tutorials
\brief Explanation of the Qt Creator plugin wizard template
This tutorial shows you how to start a new plugin project using the nymea-qtcreator wizard. You can find the source code of the
qt-creator wizard on our \l{https://github.com/guh/nymea-qtcreator-wizards}{github page}.
*/

View File

@ -1,13 +1,14 @@
/*!
\page build-environment.html
\title Set up the build environment
\brief This tutorial shows you how to set up the build environment for developing nymea.
\brief This tutorial shows you how to set up the build environment for developing with nymea.
\b{\unicode{0x2192} \underline{\l{https://www.youtube.com/watch?v=7a_k0C1Ib1A}{Video for this tutorial}}}
Assuming you are working on an Ubuntu system here are the steps how to set up the build environment. Basically you can choose your preferred SDK but all tutorials are based on the Qt Creator and we reccommend to use that one. You can also use the Ubuntu SDK, which is basically a modified Qt Creator.
Assuming you are working on an Ubuntu system here are the steps how to set up the build environment. Basically you
can choose your preferred SDK but all tutorials are based on the Qt Creator and we recommend to use that one.
\note Please take care that you are using the Qt version from the system for building. The nymea server will always be built with the official Qt version for the appropriate system version. The plugin \underline{must} have the same version like the nymea server.
\note Please take care that you are using the Qt version from the system for building. The nymea server will always be
built with the official Qt version for the appropriate system version. The plugin \underline{must} have the same
version like the nymea server.
\section2 Install Qt
In the first step you need to install the Qt libraries:
@ -20,27 +21,8 @@
You can find a good instructions how to install the nymea repository on your system here:
\b {\unicode{0x2192}} \l{https://github.com/guh/nymea/wiki/Install}{nymea install wiki}
\b {\unicode{0x2192}} \l{https://nymea.io/en/wiki/nymea/master/install}{nymea install wiki}
For example, if you are working on Ubuntu 15.04 Vivid, you can create a source list file and add the nymea repository like this:
\code
$ sudo nano /etc/apt/sources.list.d/nymea.list
\endcode
Copy following 3 lines in the \tt /etc/apt/sources.list.d/nymea.list file, save and close it
\code
## nymea repo
deb http://repo.nymea.io xenial main
deb-src http://repo.nymea.io xenial main
\endcode
Now you need to add the public key of the \e nymea-repository to your key list with following command:
\code
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key 6B9376B0
\endcode
Update your package lists:
\code
@ -50,17 +32,9 @@
Now you are ready to install the nymea packages:
\code
$ sudo apt-get install nymea nymea-webinterface nymea-cli nymea-doc libnymea1-dev
$ sudo apt-get install nymea nymea-cli nymea-doc libnymea1-dev nymea-qtcreator-wizards
\endcode
\list
\li \underline{\e nymea} \unicode{0x2192} the \tt nymea package is a meta package and will install the \tt nymead, \tt libnymea1 and \tt nymea-plugins package so you can start the nymea daemon.
\li \underline{\e nymea-webinterface} \unicode{0x2192} the \tt nymea-webinterface package will install the webinterface for nymea, which is accessable on \l{http://localhost:3333}.
\li \underline{\e nymea-cli} \unicode{0x2192} the \tt nymea-cli package will install the command line interface for the nymea JSON-RPC API. You can find more information \l{https://github.com/guh/nymea/wiki/nymea-cli}{here}.
\li \underline{\e nymea-doc} \unicode{0x2192} the \tt nymea-doc package will install the offline documentation on your system (this homepage). You can access the documentation in your brwoser with \l{file:///usr/share/doc/nymea/html/index.html}{file:///usr/share/doc/nymea/html/index.html}.
\li \underline{\e libnymea1-dev} \unicode{0x2192} the \tt libnymea1-dev package brings all development files of nymea (header files and lib) which will be needed to write a plugin.
\endlist
Once you have successfully installed everything you are ready for \l{Getting started}.
*/

View File

@ -1,145 +0,0 @@
/*!
\page tutorial1.html
\title Tutorial 1 - The "Minimal" plugin
\brief This tutorial shows you how to open, edit, build and load the first plugin.
\ingroup tutorials
\contentspage {Tutorials}
\section1 Topics
This first tutorial shows you how to:
\list
\li \unicode{0x25B6} Open and edit the project
\li \unicode{0x25B6} Build and load the first plugin.
\endlist
\section1 Open the project
Assuming you already have downloaded the \l{https://github.com/guh/nymea-plugin-template}{nymea-plugin-template} you can open the \tt {plugin-template \unicode{0x2192} minimal \unicode{0x2192} minimal.pro} file with the Qt Creator.
\image minimal-project-open.png "Qt Creator"
Qt will create next to the \tt {plugin-templates \unicode{0x2192} minimal \unicode{0x2192}} folder the build directory (shadow build).
\section1 Build the plugin
In order to compile your first plugin you can press the \b "Build" button in the lower left corner of the \e {Qt Creator} window. You can follow the build process in the \e {Compile Output} window (\tt alt + \tt 4).
The resulting build directory should look like this:
\code
ls -l build-minimal-Desktop-Debug/
devicepluginminimal.o
extern-plugininfo.h
libnymea_devicepluginminimal.so
Makefile
moc_devicepluginminimal.cpp
moc_devicepluginminimal.o
plugininfo.h
\endcode
As you can see there are two new header files: the \tt plugininfo.h and \tt extern-plugininfo.h . Thouse files were generated by the \tt {\b nymea-generateplugininfo} and contain the uuid definitions of from the \tt devicepluginminimal.json file. You can find out more about thouse files in \l{The plugin JSON File} documentation.
The \b {\tt libnymea_devicepluginminimal.so} file is our fresh compiled deviceplugin.
\section1 Install the plugin
I you have installed nymea using the repository (see \l{Set up the build environment}) you will find the installed plugins here:
\code
/usr/lib/nymea/plugins/
\endcode
This is the directory where we have to install the \b {\tt libnymea_devicepluginminimal.so} file. You have two possibilities to install the new plugin. For bouth methods you need \tt root permissions:
\list 1
\li Install using \b {\tt make}:
\code
$ cd build-minimal-Desktop-Debug/
$ sudo make install
install -m 755 -p "libnymea_devicepluginminimal.so" "/usr/lib/nymea/plugins/libnymea_devicepluginminimal.so"
\endcode
\li Copy the file manually:
\code
$ cd build-minimal-Desktop-Debug/
$ sudo cp libnymea_devicepluginminimal.so /usr/lib/nymea/plugins/
\endcode
\endlist
Once you have installed the new plugin you can test it.
\section1 Test the plugin
If order to test the new plugin we need to restart the nymea daemon. Please make shore there is only one instance of nymea running on your system, otherwise one of the daemons will colide with the ports of the other one. Once you have installed the \b {\tt libnymea_devicepluginminimal.so} file you need a clean start of the nymea daemon.
\code
$ nymead -n
\endcode
If you want to see the debug output of the \b {\tt libnymea_devicepluginminimal.so} you can start \tt nymead with the parameter \tt -d :
\code
$ nymead -n -d Minimal
\endcode
With this command the debug category will be enabled for this plugin. By default the debug categorie of a plugin is disabled, to keep the STDOUT clean and readable. With the \tt -d parameter of \b {\tt nymead} you can specify which categorie you want to see. The categorie name will be defined in the \tt idName parameter of the plugin object in \l{The Plugin JSON file}.
Now you can use any nymea client application to examin the new plugin and device. Since the application \b {\tt nymea-cli} (nymea command line interface) was developed for developers, I will show you how this works with the \b {\tt nymea-cli}.
\note You can open two terminal tabs. In the first one you can start \tt nymead with the \tt -n parameter to see the debug output. In the second one you can start \b {\tt nymea-cli} to interact with the server and test your plugin. You can find the documentation for \b {\tt nymea-cli} \l{https://github.com/guh/nymea/wiki/guh-cli}{here}.
\section2 nymea-cli
The nymea command line interface \b {\tt nymea-cli} is an admin tool for testing \l{Plugins}, the \l{JSON-RPC API} and core functionalities of nymea. It communicates with the nymea daemon using the \l{JSON-RPC API} over the \l{nymeaserver::TcpServer}{TcpServer}.
\code
$ nymea-cli
\endcode
\section3 Add a new device
Here is an example how to \e {Add a new device} with \b {\tt nymea-cli}. This steps should give you a feeling how the setup process works from the client to the method in the \l{DevicePlugin}.
\list 1
\li Open nymea-cli and enter the \b "Devices" menue:
\image nymea-cli.png "nymea-cli"
\li Select the \b {"Add a new device"} menue:
\image nymea-cli_devices_add-device.png "Add a new device"
\li Select the \b {"Minimal vendor"} from the \l{Vendor}{Vendors} list. Take a look at \l{The Plugin JSON file} \unicode{0x2192} \l{The Vendor definition} to see where this will be definend.
\image nymea-cli_devices_add-device_minimal-1.png "Select vendor"
\li Select the \b {"Minimal device"} from the list of supported devices for this \l{Vendor}. Take a look at \l{The Plugin JSON file} \unicode{0x2192} \l{The DeviceClass definition} to see where this will be definend.
\image nymea-cli_devices_add-device_minimal-2.png "Select device"
\li Now enter the \e value for the \l{Param} \e "name" of the \l{DeviceClass} \e {"Minimal device"}. Take a look at \l{The Plugin JSON file} \unicode{0x2192} \l{The ParamType definition} to see where this will be definend. This parameter will be used to set up the new \l{Device}. Once you entered the \e name for the new \l {Device} and pressed \tt enter, the \l{DeviceManager} will call the \l{DevicePlugin::setupDevice()} in the \tt devicepluginminimal.cpp.
\image nymea-cli_devices_add-device_minimal-3.png "Set the parameter \"name\""
A new \l{Device} with a new \l{DeviceId} will be created and the passed to the \l{DevicePlugin::setupDevice()} in your plugin.
You can see here the implementation of the code:
\code
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
If you started \b {\tt nymead} 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}"
Minimal: ParamList (count:1)
0: Param(Name: "name", Value:QVariant(QString, "Name of minimal device") )
DeviceManager: Device setup complete.
\endcode
\endlist
Now you can take a look at \l{Tutorial 2 - The "Buttons" plugin}.
*/

View File

@ -1,482 +0,0 @@
/*!
\page tutorial2.html
\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 {"Buttons"}. We will use this name for the naming concentions of the filenames.
\section1 Topics
This tutorial will show you how to:
\list
\li \unicode{0x25B6} Start with a new \l{DevicePlugin}{Plugin}
\li \unicode{0x25B6} Implement an \l{Action}
\li \unicode{0x25B6} Implement an \l{Event}
\endlist
In order to getting started with the 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/ 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 \tt minimal.pro.user file if there is any.
Now we can rename the files using the plugin name convention:
\code
$ 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 buttons.pro
Open the \tt buttons.pro file with the \e {Qt Creator} and open that file in the editor:
\code
include(plugins.pri)
TARGET = $$qtLibraryTarget(nymea_devicepluginminimal)
message("Building $$deviceplugin$${TARGET}.so")
SOURCES += \
devicepluginminimal.cpp \
HEADERS += \
devicepluginminimal.h \
\endcode
\list 1
\li Change the \tt TARGET name form \tt nymea_devicepluginminimal \unicode{0x2192} \tt nymea_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(nymea_devicepluginbuttons)
message("Building $$deviceplugin$${TARGET}.so")
SOURCES += \
devicepluginbuttons.cpp \
HEADERS += \
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 devicepluginbuttons.h
Open the \tt devicepluginbuttons.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 "io.nymea.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 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 DEVICEPLUGINBUTTONS_H
#define DEVICEPLUGINBUTTONS_H
#include "plugin/deviceplugin.h"
#include "devicemanager.h"
class DevicePluginButtons : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginbuttons.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginButtons();
DeviceManager::HardwareResources requiredHardware() const override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
};
#endif // DEVICEPLUGINBUTTONS_H
\endcode
\section2 Change the \tt devicepluginbuttons.cpp
Open the \tt devicepluginbuttons.cpp 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 "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 "devicepluginbuttons.h"
#include "plugininfo.h"
DevicePluginButtons::DevicePluginButtons()
{
}
DeviceManager::HardwareResources DevicePluginButtons::requiredHardware() const
{
return DeviceManager::HardwareResourceNone;
}
DeviceManager::DeviceSetupStatus DevicePluginButtons::setupDevice(Device *device)
{
Q_UNUSED(device)
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.
\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/nymea-generateplugininfo ../buttons-diy/devicepluginbuttons.json plugininfo.h
../buttons-diy/devicepluginbuttons.json -> plugininfo.h
--> generate plugininfo.h
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 were definend for the \b {Buttons} plugin.
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
#include "typeutils.h"
#include <QLoggingCategory>
// Id definitions
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(dcButtons)
Q_LOGGING_CATEGORY(dcButtons, "Buttons")
#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 nymea-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.
\code
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
\endcode
Here is the implemented method:
\code
// Check the DeviceClassId for "Simple Button"
if (device->deviceClassId() == simpleButtonDeviceClassId ) {
// check if this is the "press" action
if (action.actionTypeId() == pressSimpleButtonActionTypeId) {
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
When a user or the \l {nymeaserver::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 nymea. 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{Install the plugin}{Tutorial 1 - Install the plugin}).
\list 1
\li Start nymea with following command:
\code
$ nymead -n -d Buttons
\endcode
\li Start nymea-cli and add the a new \b {"Simple Button"} devcice. Give an appropriate name like \b {Test button}.
\li Use nymea-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 {nymeaserver::RuleEngine}{RuleEngine}.
Now you can take a look at \l{Tutorial 3 - The "Power Button" device}.
*/

View File

@ -1,141 +0,0 @@
/*!
\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.
\section1 Topics
This tutorial will show you how to:
\list
\li \unicode{0x25B6} Implement a \l{State}
\li \unicode{0x25B6} Use \l{Param}{Params} in an \l{Event}
\li \unicode{0x25B6} Use \l{Param}{Params} in an \l{Action}
\endlist
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 nymea, 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{Install the plugin}{Tutorial 1 - Install the plugin}).
\list 1
\li Start nymea with following command:
\code
$ nymead -n -d Buttons
\endcode
\li Start nymea-cli and add the a new \b {"Power Button"} \l Device. Give an appropriate name like \b {Light}.
\li Use nymea-cli to check the current power \l State, it should be \tt false (default value).
\li Use nymea-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 nymead 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 {nymeaserver::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"}.
*/

View File

@ -1,122 +0,0 @@
/*!
\page tutorial4.html
\title Tutorial 4 - The alternative "Power Button"
\brief This device demonstrates how a writable state works.
\ingroup tutorials
\section1 Topics
This tutorial will show you how to:
\list
\li \unicode{0x25B6} Implement a writable \l{State} (which can be manipulated by an \l{Action})
\endlist
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": true
}
]
}
...
\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 always 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 device manager defines a new \l ActionType which has the same UUID as the \l 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{Install the plugin}{Tutorial 1 - Install the plugin}).
\list 1
\li Start nymea with following command:
\code
$ nymead -n -d Buttons
\endcode
\li Start nymea-cli and add the a new \b {"Alternative Power Button"} \l Device. Give an appropriate name like \b {Alternative Light}.
\li Use nymea-cli to check the current power \l State, it should be \tt false (default value).
\li Use nymea-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 nymead 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
Now you can take a look at \l{Tutorial 5 - The "Network Info" plugin}.
*/

View File

@ -1,590 +0,0 @@
/*!
\page tutorial5.html
\title Tutorial 5 - The "Network Info" plugin
\brief The plugin shows you how to use the NetworkManager and how asynchronous actions work
\ingroup tutorials
\section1 Topics
This tutorial will show you how to:
\list
\li \unicode{0x25B6} Use the hardware resource \l{NetworkManager}
\endlist
In the tutorial we make a plugin with the name \b {"Network Info"}. 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(nymea_devicepluginminimal)
message("Building $$deviceplugin$${TARGET}.so")
SOURCES += \
devicepluginminimal.cpp \
HEADERS += \
devicepluginminimal.h \
\endcode
\list 1
\li Change the \tt TARGET name form \tt nymea_devicepluginminimal \unicode{0x2192} \tt nymea_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(nymea_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 "io.nymea.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 "io.nymea.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/nymea-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 <QLoggingCategory>
// 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 <QLoggingCategory>
// 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 nymea. 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 already 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 nymea.
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": "nymea",
"idName": "nymea",
"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/nymea-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 nymea now with the parameter \b {\tt {nymead -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 nymead and add the a \b {Info about Network} device with \b {\tt nymea-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 already 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 don't 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 successful, 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 valid JSON document. If not, the action execution \b "update" was not successful 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 successful 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{Install the plugin}{Tutorial 1 - Install the plugin}).
\list 1
\li Start nymea with following command:
\code
$ nymead -n -d NetworkInfo
\endcode
\li Start nymea-cli and add the a new "Info" devcice.
\li Use nymea-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 nymea-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 nymea-cli to check if the device states were updated successfully:
\tt "Devices" \unicode{0x2192} \tt "List..." \unicode{0x2192} \tt {"List device states"} \unicode{0x2192} \tt {"Your device name"}.
\endlist
*/

View File

@ -1,434 +0,0 @@
/*!
\page tutorial6.html
\title Tutorial 6 - The "CoAP Client" plugin
\brief The plugin shows you how to use the CoAP lib
\ingroup tutorials
\section1 Topics
This tutorial will show you how to:
\list
\li \unicode{0x25B6} Allow only one \l{Device}
\li \unicode{0x25B6} Implement the \l{DevicePlugin::deviceRemoved()}{deviceRemoved()} method
\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 The plugin source code
\section2 networkinfo.pro
\code
include(plugins.pri)
TARGET = nymea_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": "nymea",
"idName": "nymea",
"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 <QHash>
#include <QNetworkReply>
class DevicePluginCoapClient : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.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<Device> m_device;
QPointer<Coap> m_coap;
// Replies from coap
QHash<CoapReply *, Device *> m_discoverReplies;
QHash<CoapReply *, Device *> m_notificationEnableReplies;
QHash<CoapReply *, Device *> m_notificationDisableReplies;
QList<CoapReply *> m_uploadReplies;
QHash< CoapReply *, ActionId> m_asyncActions;
private slots:
void onReplyFinished(CoapReply *reply);
void onNotificationReceived(const CoapObserveResource &resource, const int &notificationNumber, const QByteArray &payload);
};
#endif // DEVICEPLUGINNETWORKINFO_H
\endcode
\section2 devicepluginnetworkinfo.cpp
\code
#include "deviceplugincoapclient.h"
#include "plugininfo.h"
#include <QJsonDocument>
#include "coap/corelinkparser.h"
// Note: You can find the documentation for this code here -> http://doc.nymea.io/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 immediately if the there occurred 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 &notificationNumber, 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
*/

View File

@ -1,11 +0,0 @@
/*!
\page tutorials.html
\title Tutorials
\contentspage {Tutorials}
\nextpage {Tutorial 1 - The "Minimal" plugin}
\previouspage {Write your own plugin}
In order to getting started with your own \l DevicePlugin it is reccommended to walk trough following tutorials to understand how the mechanism works:
\annotatedlist tutorials
*/

View File

@ -8,40 +8,7 @@
\li \l{The plugin JSON File}
\li \l{CreateMethods and SetupMethods}
\li \l{Testing your plugin}
\li \l{Tutorials}
\list
\li \l{Tutorial 1 - The "Minimal" plugin}
\list
\li Open and edit the project
\li Build and load the first plugin.
\endlist
\li \l{Tutorial 2 - The "Buttons" plugin}
\list
\li Start with a new \l{DevicePlugin}{Plugin}
\li Implement an \l{Action}
\li Implement an \l{Event}
\endlist
\li \l{Tutorial 3 - The "Power Button" device}
\list
\li Implement a \l{State}
\li Use \l{Param}{Params} in an \l{Event}
\li Use \l{Param}{Params} in an \l{Action}
\endlist
\li \l{Tutorial 4 - The alternative "Power Button"}
\list
\li Implement a writable \l{State} (which can be manipulated by an \l{Action})
\endlist
\li \l{Tutorial 5 - The "Network Info" plugin}
\list
\li Use hardware resource \l{NetworkManager}
\endlist
\li \l{Tutorial 6 - The "CoAP Client" plugin}
\list
\li Allow only one \l{Device}
\li Implement the \l{DevicePlugin::deviceRemoved()}{deviceRemoved()} method
\li Use the \l{Coap}{CoAP} library
\endlist
\endlist
\li \l{Plugin tutorials}
\endlist
*/