From 08412291368e9cc222ec1abb4d8c764004c402d8 Mon Sep 17 00:00:00 2001 From: Bruce Eckstein Date: Fri, 24 Sep 2021 15:42:54 -0400 Subject: [PATCH] Initial setup for Garadget garage door opener --- garadget/Garadget.pro | 14 + garadget/Garadget.pro.user | 323 ++++++++++++++++++ garadget/LICENSE.LGPL3 | 165 +++++++++ garadget/README.md | 4 + garadget/debian/changelog | 5 + garadget/debian/compat | 1 + garadget/debian/control | 25 ++ garadget/debian/copyright | 14 + .../debian/nymea-plugin-Garadget.install.in | 1 + garadget/debian/rules | 25 ++ garadget/debian/source/format | 1 + garadget/garadget.svg | 128 +++++++ garadget/integrationpluginGaradget.cpp | 278 +++++++++++++++ garadget/integrationpluginGaradget.cpp~ | 301 ++++++++++++++++ garadget/integrationpluginGaradget.h | 84 +++++ garadget/integrationpluginGaradget.json | 78 +++++ garadget/meta.json | 15 + ...067fe-719b-493b-9cce-0f170978c8f7-en_US.ts | 57 ++++ ...67fe-719b-493b-9cce-0f170978c8f7-en_US.ts~ | 91 +++++ 19 files changed, 1610 insertions(+) create mode 100644 garadget/Garadget.pro create mode 100644 garadget/Garadget.pro.user create mode 100644 garadget/LICENSE.LGPL3 create mode 100644 garadget/README.md create mode 100644 garadget/debian/changelog create mode 100644 garadget/debian/compat create mode 100644 garadget/debian/control create mode 100644 garadget/debian/copyright create mode 100644 garadget/debian/nymea-plugin-Garadget.install.in create mode 100644 garadget/debian/rules create mode 100644 garadget/debian/source/format create mode 100644 garadget/garadget.svg create mode 100644 garadget/integrationpluginGaradget.cpp create mode 100644 garadget/integrationpluginGaradget.cpp~ create mode 100644 garadget/integrationpluginGaradget.h create mode 100644 garadget/integrationpluginGaradget.json create mode 100644 garadget/meta.json create mode 100644 garadget/translations/068067fe-719b-493b-9cce-0f170978c8f7-en_US.ts create mode 100644 garadget/translations/068067fe-719b-493b-9cce-0f170978c8f7-en_US.ts~ diff --git a/garadget/Garadget.pro b/garadget/Garadget.pro new file mode 100644 index 00000000..b38efde7 --- /dev/null +++ b/garadget/Garadget.pro @@ -0,0 +1,14 @@ +include($$[QT_INSTALL_PREFIX]/include/nymea/plugin.pri) + +QT += network + +PKGCONFIG += nymea-mqtt + +TARGET = $$qtLibraryTarget(nymea_integrationpluginGaradget) + +SOURCES += \ + integrationpluginGaradget.cpp + +HEADERS += \ + integrationpluginGaradget.h + diff --git a/garadget/Garadget.pro.user b/garadget/Garadget.pro.user new file mode 100644 index 00000000..fb6e084f --- /dev/null +++ b/garadget/Garadget.pro.user @@ -0,0 +1,323 @@ + + + + + + EnvironmentId + {ba25b4ed-600c-4ef0-9f6e-aee74c4d5771} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + true + true + true + true + + + 0 + true + + true + Builtin.Questionable + + true + true + Builtin.DefaultTidyAndClazy + 4 + + + + true + + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop + Desktop + {42208f61-3dc1-4538-a721-4593dcfd5243} + 0 + 0 + 0 + + 0 + /home/beckstein/nymea-testing/build-Garadget-Desktop-Debug + /home/beckstein/nymea-testing/build-Garadget-Desktop-Debug + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + 0 + + + /home/beckstein/nymea-testing/build-Garadget-Desktop-Release + /home/beckstein/nymea-testing/build-Garadget-Desktop-Release + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + /home/beckstein/nymea-testing/build-Garadget-Desktop-Profile + /home/beckstein/nymea-testing/build-Garadget-Desktop-Profile + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + 2 + + LD_LIBRARY_PATH=/home/beckstein/nymea-testing/build-nymea-Desktop-Debug/libnymea/:home/beckstein/nymea-testing/build-nymea-Desktop-Debug/libnymea-core/ + NYMEA_PLUGINS_PATH=/home/beckstein/nymea-testing/build-Garadget-Desktop-Debug + + + /usr/bin/nymead + ProjectExplorer.CustomExecutableRunConfiguration + + -n -d Garadget + false + true + false + true + /home/beckstein/nymea-testing/working + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/garadget/LICENSE.LGPL3 b/garadget/LICENSE.LGPL3 new file mode 100644 index 00000000..0a041280 --- /dev/null +++ b/garadget/LICENSE.LGPL3 @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + 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 that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU 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 as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/garadget/README.md b/garadget/README.md new file mode 100644 index 00000000..79fb1ac1 --- /dev/null +++ b/garadget/README.md @@ -0,0 +1,4 @@ +# Garadget +-------------------------------- + +Description of the plugin... diff --git a/garadget/debian/changelog b/garadget/debian/changelog new file mode 100644 index 00000000..2aba7598 --- /dev/null +++ b/garadget/debian/changelog @@ -0,0 +1,5 @@ +nymea-plugin-Garadget (0.0.1) xenial; urgency=medium + + * Initial release. + + -- Bruce Eckstein Mon, 21 Aug 2017 10:12:00 +0000 diff --git a/garadget/debian/compat b/garadget/debian/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/garadget/debian/compat @@ -0,0 +1 @@ +9 diff --git a/garadget/debian/control b/garadget/debian/control new file mode 100644 index 00000000..b1e138bb --- /dev/null +++ b/garadget/debian/control @@ -0,0 +1,25 @@ +Source: nymea-plugin-Garadget +Section: utils +Priority: options +Maintainer: Bruce Eckstein +Build-depends: debhelper (>= 9.0.0), + libnymea1-dev, + nymea-dev-tools:native, + qt5-qmake:native, + pkg-config, +Standards-Version: 3.9.3 + +Package: nymea-plugin-Garadget +Architecture: any +Multi-Arch: same +Section: libs +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: nymea.io plugin for Garadget + 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 Garadget diff --git a/garadget/debian/copyright b/garadget/debian/copyright new file mode 100644 index 00000000..ab5fccbe --- /dev/null +++ b/garadget/debian/copyright @@ -0,0 +1,14 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: ${ProjectName} +Upstream-Contact: Bruce Eckstein +Copyright: 2020, Bruce Eckstein + + +License: LGPL-3 + On Debian systems, the complete text of the GNU Lesser General + Public License can be found in `/usr/share/common-licenses/LGPL-3'. + +Files: * +License: LGPL-3 +Copyright: Bruce Eckstein + diff --git a/garadget/debian/nymea-plugin-Garadget.install.in b/garadget/debian/nymea-plugin-Garadget.install.in new file mode 100644 index 00000000..889763ba --- /dev/null +++ b/garadget/debian/nymea-plugin-Garadget.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginGaradget.so diff --git a/garadget/debian/rules b/garadget/debian/rules new file mode 100644 index 00000000..2c4801ea --- /dev/null +++ b/garadget/debian/rules @@ -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=) diff --git a/garadget/debian/source/format b/garadget/debian/source/format new file mode 100644 index 00000000..89ae9db8 --- /dev/null +++ b/garadget/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/garadget/garadget.svg b/garadget/garadget.svg new file mode 100644 index 00000000..a5a7e695 --- /dev/null +++ b/garadget/garadget.svg @@ -0,0 +1,128 @@ + + + + + + image/svg+xml + + Zeichenfläche 1 + + + + + + + + + + + Zeichenfläche 1 + + + + + + + + + + diff --git a/garadget/integrationpluginGaradget.cpp b/garadget/integrationpluginGaradget.cpp new file mode 100644 index 00000000..db7ee9d0 --- /dev/null +++ b/garadget/integrationpluginGaradget.cpp @@ -0,0 +1,278 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginGaradget.h" +#include "plugininfo.h" + +#include +#include +#include +#include + +#include "hardwaremanager.h" +#include "network/networkaccessmanager.h" +#include "network/mqtt/mqttprovider.h" +#include "network/mqtt/mqttchannel.h" + +static QHash sonoff_basicPowerStateTypeIds = { + {"POWER1", sonoff_basicPowerStateTypeId}, +}; +static QHash> stateMaps = { + {sonoff_basicThingClassId, sonoff_basicPowerStateTypeIds}, +}; + +IntegrationPluginGaradget::IntegrationPluginGaradget() +{ + // Helper maps for parent devices (aka sonoff_*) + m_ipAddressParamTypeMap[sonoff_basicThingClassId] = sonoff_basicThingIpAddressParamTypeId; + + + // Helper maps for all devices + m_connectedStateTypeMap[sonoff_basicThingClassId] = sonoff_basicConnectedStateTypeId; + + m_signalStrengthStateTypeMap[sonoff_basicThingClassId] = sonoff_basicSignalStrengthStateTypeId; +} + +IntegrationPluginGaradget::~IntegrationPluginGaradget() +{ +} + +void IntegrationPluginGaradget::init() +{ +} + +void IntegrationPluginGaradget::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + if (m_ipAddressParamTypeMap.contains(thing->thingClassId())) { + ParamTypeId ipAddressParamTypeId = m_ipAddressParamTypeMap.value(thing->thingClassId()); + + QHostAddress deviceAddress = QHostAddress(thing->paramValue(ipAddressParamTypeId).toString()); + if (deviceAddress.isNull()) { + qCWarning(dcGaradget) << "Not a valid IP address given for IP address parameter"; + //: Error setting up thing + return info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The given IP address is not valid.")); + } + MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(thing->id().toString().remove(QRegExp("[{}-]")), deviceAddress ); + if (!channel) { + qCWarning(dcGaradget) << "Failed to create MQTT channel."; + //: Error setting up thing + return info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings.")); + } +// ipaddress is defined here but not sure how to get rid of and still have the mqtt channel. Garadget only works with the broker. + + qCDebug(dcGaradget) << "BEE initial setup instructions:" << deviceAddress.toString() << channel; + + m_mqttChannels.insert(info->thing(), channel); + connect(channel, &MqttChannel::clientConnected, this, &IntegrationPluginGaradget::onClientConnected); + connect(channel, &MqttChannel::clientDisconnected, this, &IntegrationPluginGaradget::onClientDisconnected); + connect(channel, &MqttChannel::publishReceived, this, &IntegrationPluginGaradget::onPublishReceived); + + qCDebug(dcGaradget) << "Garadget setup complete"; + info->finish(Thing::ThingErrorNoError); + foreach (Thing *child, myThings()) { + if (child->parentId() == info->thing()->id()) { + // Already have child devices... We're done here + return; + } + } + +// insert to reprogram client Garadget to new device/topic name + QJsonObject beeobj; + beeobj.insert("nme", QString(channel->topicPrefixList().first())); + QJsonDocument beedoc(beeobj); + QByteArray beedata = beedoc.toJson(QJsonDocument::Compact); + QString jsonString(beedata); + qCDebug(dcGaradget) << "Publishing:" << "garadget/garage/set-conf " << beedata; +// next line will not publish to the correct device as topic is 32 characters long and garadget only allows 30 characters. + channel->publish("garadget/garaged/set-config", beedata); + channel->publish("garadget/garage/command", "get-config"); +// need to develop test for correct modification of nme + return; + } + + if (m_connectedStateTypeMap.contains(thing->thingClassId())) { + Thing* parentDevice = myThings().findById(thing->parentId()); + StateTypeId connectedStateTypeId = m_connectedStateTypeMap.value(thing->thingClassId()); + thing->setStateValue(m_connectedStateTypeMap.value(thing->thingClassId()), parentDevice->stateValue(connectedStateTypeId)); + return info->finish(Thing::ThingErrorNoError); + } + + qCWarning(dcGaradget) << "Unhandled ThingClass in setupDevice" << thing->thingClassId(); +} + +void IntegrationPluginGaradget::thingRemoved(Thing *thing) +{ + qCDebug(dcGaradget) << "Device removed" << thing->name(); + if (m_mqttChannels.contains(thing)) { + qCDebug(dcGaradget) << "Releasing MQTT channel"; + MqttChannel* channel = m_mqttChannels.take(thing); + hardwareManager()->mqttProvider()->releaseChannel(channel); + } +} + +void IntegrationPluginGaradget::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + Action action = info->action(); + + if (thing->thingClassId() == sonoff_basicThingClassId) { + MqttChannel *channel = m_mqttChannels.value(thing); + if (!channel) { + qCWarning(dcGaradget()) << "No MQTT channel for this thing."; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + QString channelName = stateMaps.value(thing->thingClassId()).key(action.actionTypeId()); + QByteArray payload = action.paramValue(action.actionTypeId()).toBool() ? "ON" : "OFF"; + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + channelName << payload; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + channelName, payload); + thing->setStateValue(action.actionTypeId(), action.paramValue(action.actionTypeId())); + info->finish(Thing::ThingErrorNoError); + return; + } + + // Legacy (deprecated) connected devices + if (m_powerStateTypeMap.contains(thing->thingClassId())) { + Thing *parentDev = myThings().findById(thing->parentId()); + MqttChannel *channel = m_mqttChannels.value(parentDev); + if (!channel) { + qCWarning(dcGaradget()) << "No mqtt channel for this thing."; + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + ParamTypeId channelParamTypeId = m_channelParamTypeMap.value(thing->thingClassId()); + ParamTypeId powerActionParamTypeId = ParamTypeId(m_powerStateTypeMap.value(thing->thingClassId()).toString()); + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(channelParamTypeId).toString() << (action.param(powerActionParamTypeId).value().toBool() ? "ON" : "OFF"); + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(channelParamTypeId).toString().toLower(), action.param(powerActionParamTypeId).value().toBool() ? "ON" : "OFF"); + thing->setStateValue(m_powerStateTypeMap.value(thing->thingClassId()), action.param(powerActionParamTypeId).value().toBool()); + return info->finish(Thing::ThingErrorNoError); + } + if (m_closableStopActionTypeMap.contains(thing->thingClassId())) { + Thing *parentDev = myThings().findById(thing->parentId()); + MqttChannel *channel = m_mqttChannels.value(parentDev); + if (!channel) { + qCWarning(dcGaradget()) << "No mqtt channel for this thing."; + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + ParamTypeId openingChannelParamTypeId = m_openingChannelParamTypeMap.value(thing->thingClassId()); + ParamTypeId closingChannelParamTypeId = m_closingChannelParamTypeMap.value(thing->thingClassId()); + if (action.actionTypeId() == m_closableOpenActionTypeMap.value(thing->thingClassId())) { + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString() << "ON"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString().toLower(), "ON"); + } else if (action.actionTypeId() == m_closableCloseActionTypeMap.value(thing->thingClassId())) { + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString() << "ON"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString().toLower(), "ON"); + } else { // Stop + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString().toLower(), "OFF"); + } + return info->finish(Thing::ThingErrorNoError); + } + qCWarning(dcGaradget) << "Unhandled execute action call for devie" << thing; +} + +void IntegrationPluginGaradget::onClientConnected(MqttChannel *channel) +{ + qCDebug(dcGaradget) << "Sonoff thing connected!"; + Thing *dev = m_mqttChannels.key(channel); + dev->setStateValue(m_connectedStateTypeMap.value(dev->thingClassId()), true); + + foreach (Thing *child, myThings()) { + if (child->parentId() == dev->id()) { + child->setStateValue(m_connectedStateTypeMap.value(child->thingClassId()), true); + } + } +} + +void IntegrationPluginGaradget::onClientDisconnected(MqttChannel *channel) +{ + qCDebug(dcGaradget) << "Sonoff thing disconnected!"; + Thing *dev = m_mqttChannels.key(channel); + dev->setStateValue(m_connectedStateTypeMap.value(dev->thingClassId()), false); + + foreach (Thing *child, myThings()) { + if (child->parentId() == dev->id()) { + child->setStateValue(m_connectedStateTypeMap.value(child->thingClassId()), false); + } + } +} + +void IntegrationPluginGaradget::onPublishReceived(MqttChannel *channel, const QString &topic, const QByteArray &payload) +{ + qCDebug(dcGaradget) << "Publish received from Sonoff thing:" << topic << qUtf8Printable(payload); + Thing *thing = m_mqttChannels.key(channel); + if (topic.startsWith(channel->topicPrefixList().first() + "/sonoff/POWER")) { + QString channelName = topic.split("/").last(); + + thing->setStateValue(stateMaps.value(thing->thingClassId()).value(channelName), payload == "ON"); + + // Legacy (deprecated) connected things via params + foreach (Thing *child, myThings().filterByParentId(thing->id())) { + if (child->paramValue(m_channelParamTypeMap.value(child->thingClassId())).toString() != channelName) { + continue; + } + if (m_powerStateTypeMap.contains(child->thingClassId())) { + child->setStateValue(m_powerStateTypeMap.value(child->thingClassId()), payload == "ON"); + } + } + } + if (topic.startsWith(channel->topicPrefixList().first() + "/sonoff/STATE")) { + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcGaradget) << "Cannot parse JSON from Garadget device" << error.errorString(); + return; + } + QVariantMap dataMap = jsonDoc.toVariant().toMap(); + thing->setStateValue(m_signalStrengthStateTypeMap.value(thing->thingClassId()), dataMap.value("Wifi").toMap().value("RSSI").toInt()); + + if (m_brightnessStateTypeMap.contains(thing->thingClassId())) { + thing->setStateValue(m_brightnessStateTypeMap.value(thing->thingClassId()), dataMap.value("Dimmer").toInt()); + } + + // Legacy (deprecated) connected things by params + foreach (Thing *child, myThings().filterByParentId(thing->id())) { + if (m_powerStateTypeMap.contains(child->thingClassId())) { + QString childChannel = child->paramValue(m_channelParamTypeMap.value(child->thingClassId())).toString(); + QString valueString = jsonDoc.toVariant().toMap().value(childChannel).toString(); + child->setStateValue(m_powerStateTypeMap.value(child->thingClassId()), valueString == "ON"); + } + child->setStateValue(m_signalStrengthStateTypeMap.value(child->thingClassId()), dataMap.value("Wifi").toMap().value("RSSI").toInt()); + } + } +} + diff --git a/garadget/integrationpluginGaradget.cpp~ b/garadget/integrationpluginGaradget.cpp~ new file mode 100644 index 00000000..3f584483 --- /dev/null +++ b/garadget/integrationpluginGaradget.cpp~ @@ -0,0 +1,301 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginGaradget.h" +#include "plugininfo.h" + +#include +#include +#include +#include + +#include "hardwaremanager.h" +#include "network/networkaccessmanager.h" +#include "network/mqtt/mqttprovider.h" +#include "network/mqtt/mqttchannel.h" + +static QHash sonoff_basicPowerStateTypeIds = { + {"POWER1", sonoff_basicPowerStateTypeId}, +}; +static QHash> stateMaps = { + {sonoff_basicThingClassId, sonoff_basicPowerStateTypeIds}, +}; + +IntegrationPluginGaradget::IntegrationPluginGaradget() +{ + // Helper maps for parent devices (aka sonoff_*) + m_ipAddressParamTypeMap[sonoff_basicThingClassId] = sonoff_basicThingIpAddressParamTypeId; + + + // Helper maps for all devices + m_connectedStateTypeMap[sonoff_basicThingClassId] = sonoff_basicConnectedStateTypeId; + + m_signalStrengthStateTypeMap[sonoff_basicThingClassId] = sonoff_basicSignalStrengthStateTypeId; +} + +IntegrationPluginGaradget::~IntegrationPluginGaradget() +{ +} + +void IntegrationPluginGaradget::init() +{ +} + +void IntegrationPluginGaradget::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + if (m_ipAddressParamTypeMap.contains(thing->thingClassId())) { + ParamTypeId ipAddressParamTypeId = m_ipAddressParamTypeMap.value(thing->thingClassId()); + + QHostAddress deviceAddress = QHostAddress(thing->paramValue(ipAddressParamTypeId).toString()); + if (deviceAddress.isNull()) { + qCWarning(dcGaradget) << "Not a valid IP address given for IP address parameter"; + //: Error setting up thing + return info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The given IP address is not valid.")); + } + + MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(thing->id().toString().remove(QRegExp("[{}-]")), deviceAddress ); + if (!channel) { + qCWarning(dcGaradget) << "Failed to create MQTT channel."; + //: Error setting up thing + return info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings.")); + } +// ipaddress is defined here but not sure how to get rid of and still have the mqtt channel. Garadget only works with the broker. + + qCDebug(dcGaradget) << "BEE initial setup instructions:" << deviceAddress.toString() << channel; + + m_mqttChannels.insert(info->thing(), channel); + connect(channel, &MqttChannel::clientConnected, this, &IntegrationPluginGaradget::onClientConnected); + connect(channel, &MqttChannel::clientDisconnected, this, &IntegrationPluginGaradget::onClientDisconnected); + connect(channel, &MqttChannel::publishReceived, this, &IntegrationPluginGaradget::onPublishReceived); + + qCDebug(dcGaradget) << "Garadget setup complete"; + info->finish(Thing::ThingErrorNoError); + foreach (Thing *child, myThings()) { + if (child->parentId() == info->thing()->id()) { + // Already have child devices... We're done here + return; + } + } + +// insert to reprogram client Garadget to new device/topic name + QJsonObject beeobj; + beeobj.insert("nme", QString(channel->topicPrefixList().first())); + QJsonDocument beedoc(beeobj); + QByteArray beedata = beedoc.toJson(QJsonDocument::Compact); + QString jsonString(beedata); + qCDebug(dcGaradget) << "Publishing:" << "garadget/garage/set-conf " << beedata; +// next line will not publish to the correct device as topic is 32 characters long and garadget only allows 30 characters. + channel->publish("garadget/garaged/set-config", beedata); + channel->publish("garadget/garage/command", "get-config"); +// need to develop test for correct modification of nme + return; + } + + if (m_connectedStateTypeMap.contains(thing->thingClassId())) { + Thing* parentDevice = myThings().findById(thing->parentId()); + StateTypeId connectedStateTypeId = m_connectedStateTypeMap.value(thing->thingClassId()); + thing->setStateValue(m_connectedStateTypeMap.value(thing->thingClassId()), parentDevice->stateValue(connectedStateTypeId)); + return info->finish(Thing::ThingErrorNoError); + } + + qCWarning(dcGaradget) << "Unhandled ThingClass in setupDevice" << thing->thingClassId(); +} + +void IntegrationPluginGaradget::onPublishReceivedSetup(MqttChannel *channel, const QString &topic, const QByteArray &payload) +{ + qCDebug(dcGaradget) << "Publish received from garadget/garage thing:" << topic << qUtf8Printable(payload); + Thing *thing = m_mqttChannels.key(channel); + if (topic.startsWith( "garadget/garage/config")) { + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error); + qCWarning(dcGaradget) << "BEE on receive QJsonDocument: " << jsonDoc; + if (error.error != QJsonParseError::NoError) { + qCWarning(dcGaradget) << "Cannot parse JSON from Garadget device" << error.errorString(); + return; + } + QVariantMap dataMap = jsonDoc.toVariant().toMap(); + thing->setStateValue(m_signalStrengthStateTypeMap.value(thing->thingClassId()), dataMap.value("Wifi").toMap().value("RSSI").toInt()); + + } + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error); + qCWarning(dcGaradget) << "BEE on receive ending QJsonDocument: " << jsonDoc; +} + + +void IntegrationPluginGaradget::thingRemoved(Thing *thing) +{ + qCDebug(dcGaradget) << "Device removed" << thing->name(); + if (m_mqttChannels.contains(thing)) { + qCDebug(dcGaradget) << "Releasing MQTT channel"; + MqttChannel* channel = m_mqttChannels.take(thing); + hardwareManager()->mqttProvider()->releaseChannel(channel); + } +} + +void IntegrationPluginGaradget::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + Action action = info->action(); + + if (thing->thingClassId() == sonoff_basicThingClassId) { + MqttChannel *channel = m_mqttChannels.value(thing); + if (!channel) { + qCWarning(dcGaradget()) << "No MQTT channel for this thing."; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + QString channelName = stateMaps.value(thing->thingClassId()).key(action.actionTypeId()); + QByteArray payload = action.paramValue(action.actionTypeId()).toBool() ? "ON" : "OFF"; + qCDebug(dcGaradget) << "Publishing:" << "garadget/" + channel->topicPrefixList().first() + "/command " + channelName << payload; + channel->publish("garadget/" + channel->topicPrefixList().first() + "/command " + channelName, payload); + thing->setStateValue(action.actionTypeId(), action.paramValue(action.actionTypeId())); + info->finish(Thing::ThingErrorNoError); + return; + } + + // Legacy (deprecated) connected devices + if (m_powerStateTypeMap.contains(thing->thingClassId())) { + Thing *parentDev = myThings().findById(thing->parentId()); + MqttChannel *channel = m_mqttChannels.value(parentDev); + if (!channel) { + qCWarning(dcGaradget()) << "No mqtt channel for this thing."; + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + ParamTypeId channelParamTypeId = m_channelParamTypeMap.value(thing->thingClassId()); + ParamTypeId powerActionParamTypeId = ParamTypeId(m_powerStateTypeMap.value(thing->thingClassId()).toString()); + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(channelParamTypeId).toString() << (action.param(powerActionParamTypeId).value().toBool() ? "ON" : "OFF"); + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(channelParamTypeId).toString().toLower(), action.param(powerActionParamTypeId).value().toBool() ? "ON" : "OFF"); + thing->setStateValue(m_powerStateTypeMap.value(thing->thingClassId()), action.param(powerActionParamTypeId).value().toBool()); + return info->finish(Thing::ThingErrorNoError); + } + if (m_closableStopActionTypeMap.contains(thing->thingClassId())) { + Thing *parentDev = myThings().findById(thing->parentId()); + MqttChannel *channel = m_mqttChannels.value(parentDev); + if (!channel) { + qCWarning(dcGaradget()) << "No mqtt channel for this thing."; + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + ParamTypeId openingChannelParamTypeId = m_openingChannelParamTypeMap.value(thing->thingClassId()); + ParamTypeId closingChannelParamTypeId = m_closingChannelParamTypeMap.value(thing->thingClassId()); + if (action.actionTypeId() == m_closableOpenActionTypeMap.value(thing->thingClassId())) { + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString() << "ON"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString().toLower(), "ON"); + } else if (action.actionTypeId() == m_closableCloseActionTypeMap.value(thing->thingClassId())) { + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString() << "ON"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString().toLower(), "ON"); + } else { // Stop + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(openingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcGaradget) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + thing->paramValue(closingChannelParamTypeId).toString().toLower(), "OFF"); + } + return info->finish(Thing::ThingErrorNoError); + } + qCWarning(dcGaradget) << "Unhandled execute action call for devie" << thing; +} + +void IntegrationPluginGaradget::onClientConnected(MqttChannel *channel) +{ + qCDebug(dcGaradget) << "Sonoff thing connected!"; + Thing *dev = m_mqttChannels.key(channel); + dev->setStateValue(m_connectedStateTypeMap.value(dev->thingClassId()), true); + + foreach (Thing *child, myThings()) { + if (child->parentId() == dev->id()) { + child->setStateValue(m_connectedStateTypeMap.value(child->thingClassId()), true); + } + } +} + +void IntegrationPluginGaradget::onClientDisconnected(MqttChannel *channel) +{ + qCDebug(dcGaradget) << "Sonoff thing disconnected!"; + Thing *dev = m_mqttChannels.key(channel); + dev->setStateValue(m_connectedStateTypeMap.value(dev->thingClassId()), false); + + foreach (Thing *child, myThings()) { + if (child->parentId() == dev->id()) { + child->setStateValue(m_connectedStateTypeMap.value(child->thingClassId()), false); + } + } +} + +void IntegrationPluginGaradget::onPublishReceived(MqttChannel *channel, const QString &topic, const QByteArray &payload) +{ + qCDebug(dcGaradget) << "Publish received from Sonoff thing:" << topic << qUtf8Printable(payload); + Thing *thing = m_mqttChannels.key(channel); + if (topic.startsWith(channel->topicPrefixList().first() + "/sonoff/POWER")) { + QString channelName = topic.split("/").last(); + + thing->setStateValue(stateMaps.value(thing->thingClassId()).value(channelName), payload == "ON"); + + // Legacy (deprecated) connected things via params + foreach (Thing *child, myThings().filterByParentId(thing->id())) { + if (child->paramValue(m_channelParamTypeMap.value(child->thingClassId())).toString() != channelName) { + continue; + } + if (m_powerStateTypeMap.contains(child->thingClassId())) { + child->setStateValue(m_powerStateTypeMap.value(child->thingClassId()), payload == "ON"); + } + } + } + if (topic.startsWith(channel->topicPrefixList().first() + "/sonoff/STATE")) { + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcGaradget) << "Cannot parse JSON from Garadget device" << error.errorString(); + return; + } + QVariantMap dataMap = jsonDoc.toVariant().toMap(); + thing->setStateValue(m_signalStrengthStateTypeMap.value(thing->thingClassId()), dataMap.value("Wifi").toMap().value("RSSI").toInt()); + + if (m_brightnessStateTypeMap.contains(thing->thingClassId())) { + thing->setStateValue(m_brightnessStateTypeMap.value(thing->thingClassId()), dataMap.value("Dimmer").toInt()); + } + + // Legacy (deprecated) connected things by params + foreach (Thing *child, myThings().filterByParentId(thing->id())) { + if (m_powerStateTypeMap.contains(child->thingClassId())) { + QString childChannel = child->paramValue(m_channelParamTypeMap.value(child->thingClassId())).toString(); + QString valueString = jsonDoc.toVariant().toMap().value(childChannel).toString(); + child->setStateValue(m_powerStateTypeMap.value(child->thingClassId()), valueString == "ON"); + } + child->setStateValue(m_signalStrengthStateTypeMap.value(child->thingClassId()), dataMap.value("Wifi").toMap().value("RSSI").toInt()); + } + } +} + diff --git a/garadget/integrationpluginGaradget.h b/garadget/integrationpluginGaradget.h new file mode 100644 index 00000000..5d1c4213 --- /dev/null +++ b/garadget/integrationpluginGaradget.h @@ -0,0 +1,84 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINGARADGET_H +#define INTEGRATIONPLUGINGARADGET_H + +#include "integrations/integrationplugin.h" + +class MqttChannel; + +class IntegrationPluginGaradget: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginGaradget.json") + Q_INTERFACES(IntegrationPlugin) + + +public: + explicit IntegrationPluginGaradget(); + ~IntegrationPluginGaradget(); + + void init() override; + void setupThing(ThingSetupInfo *info) override; + void thingRemoved(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; + +private slots: + void onClientConnected(MqttChannel *channel); + void onClientDisconnected(MqttChannel *channel); + void onPublishReceived(MqttChannel *channel, const QString &topic, const QByteArray &payload); + +private: + QHash m_mqttChannels; + + // Helpers for parent devices (the ones starting with sonoff) + QHash m_ipAddressParamTypeMap; + QHash > m_attachedDeviceParamTypeIdMap; + + // Helpers for child devices (virtual ones, starting with Garadget) + QHash m_channelParamTypeMap; + QHash m_openingChannelParamTypeMap; + QHash m_closingChannelParamTypeMap; + QHash m_powerStateTypeMap; + + QHash m_closableOpenActionTypeMap; + QHash m_closableCloseActionTypeMap; + QHash m_closableStopActionTypeMap; + + // Helpers for both devices + QHash m_connectedStateTypeMap; + QHash m_signalStrengthStateTypeMap; + + QHash m_brightnessStateTypeMap; +}; + +#endif // INTEGRATIONPLUGINGARADGET_H diff --git a/garadget/integrationpluginGaradget.json b/garadget/integrationpluginGaradget.json new file mode 100644 index 00000000..cbf896d4 --- /dev/null +++ b/garadget/integrationpluginGaradget.json @@ -0,0 +1,78 @@ +{ + "name": "Garadget", + "displayName": "Sonoff-Garadget", + "id": "476781af-ad41-423b-b9bb-f308f2aff339", + "vendors": [ + { + "name": "Garadget", + "displayName": "Garadget", + "id": "22d59bee-79cc-4dea-b742-30ac6bdcf2c9", + "thingClasses": [ + { + "name": "sonoff_basic", + "displayName": "Garadget", + "id": "e808b8ae-7608-41ce-8444-892f0648a4d3", + "setupMethod": "JustAdd", + "createMethods": ["User"], + "interfaces": [ ], + "paramTypes": [ + { + "id": "54a11a17-fc37-4316-891f-001c55e38220", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + } + + ], + "stateTypes": [ + { + "id": "a3d7e6eb-82d0-47ee-b95f-4dde931eb7e2", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "e276e6ca-4cb5-46d2-9a78-31f92d78ac70", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "type": "uint", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": -1 + }, + { + "id": "481a7cf8-418b-4774-8fd4-32693ab52299", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "writable": true, + "defaultValue": false, + "ioType": "digitalOutput" + + } + + + ], + "actionTypes": [ + + ], + "eventTypes": [ + + ] + } + + + ] + } + ] +} + + + diff --git a/garadget/meta.json b/garadget/meta.json new file mode 100644 index 00000000..6b3a7ae6 --- /dev/null +++ b/garadget/meta.json @@ -0,0 +1,15 @@ +{ + "title": "Garadget", + "tagline": "Garadget", + "icon": "garadget.svg", + "stability": "community", + "offline": true, + "technologies": [ + "network", + "mqtt" + ], + "categories": [ + "relay", + "socket" + ] +} diff --git a/garadget/translations/068067fe-719b-493b-9cce-0f170978c8f7-en_US.ts b/garadget/translations/068067fe-719b-493b-9cce-0f170978c8f7-en_US.ts new file mode 100644 index 00000000..d9b0f53a --- /dev/null +++ b/garadget/translations/068067fe-719b-493b-9cce-0f170978c8f7-en_US.ts @@ -0,0 +1,57 @@ + + + + + IntegrationPluginGaradget + + Error creating MQTT channel. Please check MQTT server settings. + Error setting up thing + + + + Could not connect to Garadget device. + Error setting up thing + + + + + Garadget + + Sonoff-Garadget + The name of the plugin Garadget ({476781af-ad41-423b-b9bb-f308f2aff339}) + + + + Single switch (Sonoff Basic, RF, Touch...) + The name of the ThingClass ({e808b8ae-7608-41ce-8444-892f0648a4d3}) + + + + Power changed + The name of the EventType ({481a7cf8-418b-4774-8fd4-32693ab52299}) of ThingClass sonoff_basic + + + + Power + The name of the ParamType (ThingClass: sonoff_basic, ActionType: power, ID: {481a7cf8-418b-4774-8fd4-32693ab52299}) +---------- +The name of the ParamType (ThingClass: sonoff_basic, EventType: power, ID: {481a7cf8-418b-4774-8fd4-32693ab52299}) +---------- +The name of the StateType ({481a7cf8-418b-4774-8fd4-32693ab52299}) of ThingClass sonoff_basic +---------- +The name of the ActionType ({481a7cf8-418b-4774-8fd4-32693ab52299}) of ThingClass sonoff_basic + + + + Set power + The name of the ActionType ({481a7cf8-418b-4774-8fd4-32693ab52299}) of ThingClass sonoff_basic + + + + Garadget + The name of the vendor ({22d59bee-79cc-4dea-b742-30ac6bdcf2c9}) + + + + + diff --git a/garadget/translations/068067fe-719b-493b-9cce-0f170978c8f7-en_US.ts~ b/garadget/translations/068067fe-719b-493b-9cce-0f170978c8f7-en_US.ts~ new file mode 100644 index 00000000..4bb61265 --- /dev/null +++ b/garadget/translations/068067fe-719b-493b-9cce-0f170978c8f7-en_US.ts~ @@ -0,0 +1,91 @@ + + + + + IntegrationPluginGaradget + + The given IP address is not valid. + Error setting up thing + + + + Error creating MQTT channel. Please check MQTT server settings. + Error setting up thing + + + + Could not connect to Garadget device. + Error setting up thing + + + + + Garadget + + Sonoff-Garadget + The name of the plugin Garadget ({476781af-ad41-423b-b9bb-f308f2aff339}) + + + + Single switch (Sonoff Basic, RF, Touch...) + The name of the ThingClass ({e808b8ae-7608-41ce-8444-892f0648a4d3}) + + + + IP address + The name of the ParamType (ThingClass: sonoff_basic, Type: thing, ID: {54a11a17-fc37-4316-891f-001c55e38220}) + + + + Connected device + The name of the ParamType (ThingClass: sonoff_basic, Type: thing, ID: {f2a79266-1b69-4c50-8397-9533dd2f30cb}) + + + + Connected changed + The name of the EventType ({a3d7e6eb-82d0-47ee-b95f-4dde931eb7e2}) of ThingClass sonoff_basic + + + + Connected + The name of the ParamType (ThingClass: sonoff_basic, EventType: connected, ID: {a3d7e6eb-82d0-47ee-b95f-4dde931eb7e2}) +---------- +The name of the StateType ({a3d7e6eb-82d0-47ee-b95f-4dde931eb7e2}) of ThingClass sonoff_basic + + + + + Power changed + The name of the EventType ({481a7cf8-418b-4774-8fd4-32693ab52299}) of ThingClass sonoff_basic + + + + Power + The name of the ParamType (ThingClass: sonoff_basic, ActionType: power, ID: {481a7cf8-418b-4774-8fd4-32693ab52299}) +---------- +The name of the ParamType (ThingClass: sonoff_basic, EventType: power, ID: {481a7cf8-418b-4774-8fd4-32693ab52299}) +---------- +The name of the StateType ({481a7cf8-418b-4774-8fd4-32693ab52299}) of ThingClass sonoff_basic + + + + + Garadget + The name of the vendor ({22d59bee-79cc-4dea-b742-30ac6bdcf2c9}) + + + + Signal strength + The name of the ParamType (ThingClass: sonoff_basic, EventType: signalStrength, ID: {e276e6ca-4cb5-46d2-9a78-31f92d78ac70}) +---------- +The name of the StateType ({e276e6ca-4cb5-46d2-9a78-31f92d78ac70}) of ThingClass sonoff_basic + + + + Signal strength changed + The name of the EventType ({e276e6ca-4cb5-46d2-9a78-31f92d78ac70}) of ThingClass sonoff_basic + + + + +