/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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},
};
// bee add static id
static QHash sonoff_basicstateStateTypeIds = {
{"closed", sonoff_basicStateStateTypeId},
};
static QHash> stateStateMAPS = {
{sonoff_basicThingClassId, sonoff_basicstateStateTypeIds},
};
static QHash sonoff_basicClosingOutputStateTypeIds = {
{"CLOSE1", sonoff_basicClosingOutputStateTypeId},
};
static QHash> stateClosingOutputMaps = {
{sonoff_basicThingClassId, sonoff_basicClosingOutputStateTypeIds},
};
static QHash sonoff_basicOpeningOutputStateTypeIds = {
{"OPEN1", sonoff_basicOpeningOutputStateTypeId},
};
static QHash> stateOpeningMaps = {
{sonoff_basicThingClassId, sonoff_basicOpeningOutputStateTypeIds},
};
static QHash sonoff_basicMovingStateTypeIds = {
{"STOP1", sonoff_basicMovingStateTypeId},
};
static QHash> stateMovingMaps = {
{sonoff_basicThingClassId, sonoff_basicMovingStateTypeIds},
};
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;
// bee adders for extendedgaragestate
m_stateStateTypeMap[sonoff_basicThingClassId] = sonoff_basicStateStateTypeId;
m_movingStateTypeMap[sonoff_basicThingClassId] = sonoff_basicMovingStateTypeId;
m_percentageStateTypeMap[sonoff_basicThingClassId] = sonoff_basicPercentageStateTypeId;
m_closingOutputStateTypeMap[sonoff_basicThingClassId] = sonoff_basicClosingOutputStateTypeId;
m_openingOutputStateTypeMap[sonoff_basicThingClassId] = sonoff_basicOpeningOutputStateTypeId;
}
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());
// ipaddress is defined here but not sure how to get rid of and still have the mqtt channel. Garadget only works with the broker.
QHostAddress deviceAddress = QHostAddress("192.168.1.174");
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 );
MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel("garage", 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."));
}
qCDebug(dcGaradget) << "initial setup instructions:" << deviceAddress.toString() << channel;
/*
QUrl url(QString("http://%1/cm").arg(deviceAddress.toString()));
QUrlQuery query;
QMap configItems;
configItems.insert("MqttHost", channel->serverAddress().toString());
configItems.insert("MqttPort", QString::number(channel->serverPort()));
configItems.insert("MqttClient", channel->clientId());
configItems.insert("MqttUser", channel->username());
configItems.insert("MqttPassword", channel->password());
configItems.insert("Topic", "sonoff");
configItems.insert("FullTopic", channel->topicPrefixList().first() + "/%topic%/");
QStringList configList;
foreach (const QString &key, configItems.keys()) {
configList << key + ' ' + configItems.value(key);
}
QString fullCommand = "Backlog " + configList.join(';');
query.addQueryItem("cmnd", fullCommand.toUtf8().toPercentEncoding());
url.setQuery(query);
QNetworkRequest request(url);
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [this, info, channel, reply](){
if (reply->error() != QNetworkReply::NoError) {
qCDebug(dcTasmota) << "Sonoff thing setup call failed:" << reply->error() << reply->errorString() << reply->readAll();
hardwareManager()->mqttProvider()->releaseChannel(channel);
//: Error setting up thing
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Could not connect to Tasmota device."));
return;
}
*/
//BEE mod
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 garadgetobj;
garadgetobj.insert("nme", QString(channel->topicPrefixList().first()));
QJsonDocument garadgetdoc(garadgetobj);
QByteArray garadgetdata = garadgetdoc.toJson(QJsonDocument::Compact);
QString jsonString(garadgetdata);
qCDebug(dcGaradget) << "Publishing:" << "garadget/" + channel->topicPrefixList().first() << garadgetdata;
channel->publish("garadget/" + channel->topicPrefixList().first() + "/set-config", garadgetdata);
return;
}
if (m_connectedStateTypeMap.contains(thing->thingClassId())) {
qCDebug(dcGaradget) << "entered connectedStateTypeMap function";
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);
// insert to reprogram client Garadget to new device/topic name
QJsonObject garadgetobj;
garadgetobj.insert("nme","garage" );
QJsonDocument garadgetdoc(garadgetobj);
QByteArray garadgetdata = garadgetdoc.toJson(QJsonDocument::Compact);
QString jsonString(garadgetdata);
qCDebug(dcGaradget) << "garadget/" + QString(channel->topicPrefixList().first()) + "/set-config" << " topic = " << garadgetdata;
channel->publish("garadget/" + QString(channel->topicPrefixList().first()) + "/set-config", garadgetdata);
hardwareManager()->mqttProvider()->releaseChannel(channel);
}
}
void IntegrationPluginGaradget::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
Action action = info->action();
qCDebug(dcGaradget) << "executeAction function" << (thing->thingClassId());
if (m_closableStopActionTypeMap.contains(thing->thingClassId())) {
qCDebug(dcGaradget) << "Publishing: m_closableStopActionTypeMap" << "how did I get here?";
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);
}
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;
}
qCDebug(dcGaradget) << "entered sonoff_basicThingClassId function" << (thing->thingClassId());
// 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);
// qCDebug(dcGaradget) << "Publishing:" << "garadget/" + channel->topicPrefixList().first() + "/command" << payload;
// channel->publish("garadget/" + channel->topicPrefixList().first() + "/command", payload);
// thing->setStateValue(action.actionTypeId(), action.paramValue(action.actionTypeId()));
// info->finish(Thing::ThingErrorNoError);
// return;
if (action.actionTypeId() == sonoff_basicOpenActionTypeId) {
qCDebug(dcGaradget) << "Publishing:" << "garadget/" + channel->topicPrefixList().first() + "/command" << "open";
channel->publish("garadget/" + channel->topicPrefixList().first() + "/command", "open");
// qCDebug(dcGaradget) << "OpenAction stuff:" << action.actionTypeId() << action.paramValue(action.actionTypeId());
// qCDebug(dcGaradget) << "OpenMapAction stuff:" << action.paramValue(action.actionTypeId()) << stateOpeningMaps.value(thing->thingClassId()).key(action.actionTypeId());
// thing->setStateValue(action.actionTypeId(), action.paramValue(action.actionTypeId()));
info->finish(Thing::ThingErrorNoError);
return;
}
if (action.actionTypeId() == sonoff_basicCloseActionTypeId) {
qCDebug(dcGaradget) << "Publishing:" << "garadget/" + channel->topicPrefixList().first() + "/command" << "close";
channel->publish("garadget/" + channel->topicPrefixList().first() + "/command", "close");
// qCDebug(dcGaradget) << "CloseAction stuff:" << thing << thing->thingClassId() << thing->thingClassId();
// qCDebug(dcGaradget) << "CloseAction newparameter" << thing->paramValue(stateStateMAPS) ;
// thing->setStateValue(stateStateMAPS.value(thing->thingClassId()).key(action.actionTypeId()),"closed");
// qCDebug(dcGaradget) << "CloseMapAction stuff:" << stateStateMAPS.value(thing->thingClassId()).key(action.actionTypeId());
// thing->setStateValue(action.actionTypeId(), action.paramValue(action.actionTypeId()));
info->finish(Thing::ThingErrorNoError);
return;
}
if (action.actionTypeId() == sonoff_basicStopActionTypeId) {
qCDebug(dcGaradget) << "Publishing:" << "garadget/" + channel->topicPrefixList().first() + "/command" << "stop";
channel->publish("garadget/" + channel->topicPrefixList().first() + "/command", "stop");
// qCDebug(dcGaradget) << "StopAction stuff:" << action.actionTypeId() << action.paramValue(action.actionTypeId());
// qCDebug(dcGaradget) << "StopMapAction stuff:" << action.paramValue(action.actionTypeId()) << stateMovingMaps.value(thing->thingClassId()).key(action.actionTypeId());
// thing->setStateValue(action.actionTypeId(), action.paramValue(action.actionTypeId()));
info->finish(Thing::ThingErrorNoError);
return;
}
}
qCWarning(dcGaradget) << "Unhandled execute action call for device" << 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());
}
}
if (topic.startsWith("garadget/" + channel->topicPrefixList().first() + "/status")) {
qCDebug(dcGaradget) << "Garadget status received " << payload;
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("Gstatus").toMap().value("status"));
thing->setStateValue(m_stateStateTypeMap.value(thing->thingClassId()), dataMap.value("status").toMap().value("state"));
qCDebug(dcGaradget) << "Datamap " << m_signalStrengthStateTypeMap.value(thing->thingClassId());
qCDebug(dcGaradget) << "Garadget status decode " << jsonDoc;
}
}