From 0d4d6cd4f68c1e51a8e5460317e0012091904dd1 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 25 Sep 2019 14:44:38 +0200 Subject: [PATCH] New plugin: Shelly --- mqttclient/devicepluginmqttclient.cpp | 2 +- nymea-plugins.pro | 1 + shelly/README.md | 20 +++ shelly/devicepluginshelly.cpp | 231 ++++++++++++++++++++++++++ shelly/devicepluginshelly.h | 59 +++++++ shelly/devicepluginshelly.json | 57 +++++++ shelly/shelly.pro | 9 + tasmota/deviceplugintasmota.cpp | 36 ++-- 8 files changed, 396 insertions(+), 19 deletions(-) create mode 100644 shelly/README.md create mode 100644 shelly/devicepluginshelly.cpp create mode 100644 shelly/devicepluginshelly.h create mode 100644 shelly/devicepluginshelly.json create mode 100644 shelly/shelly.pro diff --git a/mqttclient/devicepluginmqttclient.cpp b/mqttclient/devicepluginmqttclient.cpp index c71f7784..61e4d03e 100644 --- a/mqttclient/devicepluginmqttclient.cpp +++ b/mqttclient/devicepluginmqttclient.cpp @@ -36,7 +36,7 @@ Device::DeviceSetupStatus DevicePluginMqttClient::setupDevice(Device *device) { MqttClient *client = nullptr; if (device->deviceClassId() == internalMqttClientDeviceClassId) { - client = hardwareManager()->mqttProvider()->createInternalClient(device->id()); + client = hardwareManager()->mqttProvider()->createInternalClient(device->id().toString()); } else if (device->deviceClassId() == mqttClientDeviceClassId){ client = new MqttClient("nymea-" + device->id().toString().remove(QRegExp("[{}]")).left(8), this); client->setUsername(device->paramValue(mqttClientDeviceUsernameParamTypeId).toString()); diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 9addd3f2..ced622b7 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -34,6 +34,7 @@ PLUGIN_DIRS = \ osdomotics \ philipshue \ pushbullet \ + shelly \ systemmonitor \ remotessh \ senic \ diff --git a/shelly/README.md b/shelly/README.md new file mode 100644 index 00000000..cd89004a --- /dev/null +++ b/shelly/README.md @@ -0,0 +1,20 @@ +# Tasmota + +This plugin allows to make use of Sonoff-Tasmota devices via the nymea internal MQTT broker. There is no external MQTT broker needed. + +Note that Sonoff devices must be flashed with the Tasmota sofware and connected to the WiFi network in order to work with this plugin. + +See the [Sonoff-Tasmota wiki](https://github.com/arendst/Sonoff-Tasmota/wiki) for a list of all supported devices and instructions on how to +install Tasmota on those. + +After flashing Tasmota to a Sonoff device and connecting it to WiFi, it can be added to nymea. The only required +thing is the IP address to the device. This plugin will create a new isoloated MQTT channel on the nymea internal +MQTT broker and provision login details to the Tasmota device via HTTP. Once that is successful, the Tasmota device +will connect to the MQTT broker and appear as connected in nymea. + +## Plugin properties +When adding a Tasmota device it will add a new Gateway type device representing the Tasmota device itself. In addition +to that a power switch device will appear which can be used to control the switches in the Tasmota device. Upon +device setup, the user can optionally select the type of the connected hardware, (e.g. a light) which causes this +plugin to create a light device in the system which also controls the switches inside the Tasmota device and nicely +integrates with the nymea:ux for the given device type. diff --git a/shelly/devicepluginshelly.cpp b/shelly/devicepluginshelly.cpp new file mode 100644 index 00000000..354ad850 --- /dev/null +++ b/shelly/devicepluginshelly.cpp @@ -0,0 +1,231 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "devicepluginshelly.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" + +#include "network/zeroconf/zeroconfservicebrowser.h" +#include "platform/platformzeroconfcontroller.h" + +DevicePluginShelly::DevicePluginShelly() +{ +} + +DevicePluginShelly::~DevicePluginShelly() +{ +} + +void DevicePluginShelly::init() +{ + m_zeroconfBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_http._tcp"); +} + +void DevicePluginShelly::discoverDevices(DeviceDiscoveryInfo *info) +{ + foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { +// qCDebug(dcShelly()) << "Have entry" << entry; + QRegExp namePattern("^shelly[1-2]-[0-9A-Z]+$"); + if (!entry.name().contains(namePattern)) { + continue; + } + + DeviceDescriptor descriptor(shellyOneDeviceClassId, entry.name(), entry.hostAddress().toString()); + ParamList params; + params << Param(shellyOneDeviceIdParamTypeId, entry.name()); + descriptor.setParams(params); + + Device *existingDevice = myDevices().findByParams(params); + if (existingDevice) { + descriptor.setDeviceId(existingDevice->id()); + } + + info->addDeviceDescriptor(descriptor); + qCDebug(dcShelly()) << "Found shelly device!" << entry; + + } + + info->finish(Device::DeviceErrorNoError); +} + +void DevicePluginShelly::setupDevice(DeviceSetupInfo *info) +{ + Device *device = info->device(); + + if (device->deviceClassId() == shellyOneDeviceClassId) { + QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString(); + ZeroConfServiceEntry zeroConfEntry; + foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { + if (entry.name() == shellyId) { + zeroConfEntry = entry; + } + } + QHostAddress address; + pluginStorage()->beginGroup(device->id().toString()); + if (zeroConfEntry.isValid()) { + address = zeroConfEntry.hostAddress().toString(); + pluginStorage()->setValue("cachedAddress", address.toString()); + } else { + qCWarning(dcShelly()) << "Could not find Shelly device on zeroconf. Trying cached address."; + address = pluginStorage()->value("cachedAddress").toString(); + } + pluginStorage()->endGroup(); + + if (address.isNull()) { + qCWarning(dcShelly()) << "Unable to determine Shelly's network address. Failed to set up device."; + info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Unable to find the device in the network.")); + return; + } + + MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(shellyId, QHostAddress(address), {"shellies"}); + if (!channel) { + qCWarning(dcShelly()) << "Failed to create MQTT channel."; + return info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings.")); + } + + QUrl url; + url.setScheme("http"); + url.setHost(address.toString()); + url.setPort(80); + url.setPath("/settings"); + + QUrlQuery query; + query.addQueryItem("mqtt_server", channel->serverAddress().toString() + ":" + QString::number(channel->serverPort())); + query.addQueryItem("mqtt_user", channel->username()); + query.addQueryItem("mqtt_pass", channel->password()); + query.addQueryItem("mqtt_enable", "true"); + + url.setQuery(query); + + QNetworkRequest request(url); + qCDebug(dcShelly()) << "Connecting to" << url.toString(); + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(info, &DeviceSetupInfo::aborted, channel, [this, channel](){ + hardwareManager()->mqttProvider()->releaseChannel(channel); + }); + connect(reply, &QNetworkReply::finished, info, [this, info, reply, channel](){ + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcShelly()) << "Error fetching device settings" << reply->error() << reply->errorString(); + info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error connecting to Shelly device.")); + hardwareManager()->mqttProvider()->releaseChannel(channel); + return; + } + QByteArray data = reply->readAll(); + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcShelly()) << "Error parsing settings reply" << error.errorString() << "\n" << data; + info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Unexpected data received from Shelly device.")); + hardwareManager()->mqttProvider()->releaseChannel(channel); + return; + } + qCDebug(dcShelly()) << "Settings data" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); + + m_mqttChannels.insert(info->device(), channel); + connect(channel, &MqttChannel::clientConnected, this, &DevicePluginShelly::onClientConnected); + connect(channel, &MqttChannel::clientDisconnected, this, &DevicePluginShelly::onClientDisconnected); + connect(channel, &MqttChannel::publishReceived, this, &DevicePluginShelly::onPublishReceived); + + info->finish(Device::DeviceErrorNoError); + }); + return; + } + + qCWarning(dcShelly) << "Unhandled DeviceClass in setupDevice" << device->deviceClassId(); +} + +void DevicePluginShelly::deviceRemoved(Device *device) +{ + if (m_mqttChannels.contains(device)) { + hardwareManager()->mqttProvider()->releaseChannel(m_mqttChannels.take(device)); + } + qCDebug(dcShelly()) << "Device removed" << device->name(); +} + +void DevicePluginShelly::executeAction(DeviceActionInfo *info) +{ + Device *device = info->device(); + Action action = info->action(); + + if (action.actionTypeId() == shellyOnePowerActionTypeId) { + MqttChannel *channel = m_mqttChannels.value(device); + QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString(); + bool on = action.param(shellyOnePowerActionPowerParamTypeId).value().toBool(); + channel->publish("shellies/" + shellyId + "/relay/0/command", on ? "on" : "off"); + info->finish(Device::DeviceErrorNoError); + return; + } + + qCWarning(dcShelly()) << "Unhandled execute action call for device" << device; +} + +void DevicePluginShelly::onClientConnected(MqttChannel *channel) +{ + Device *device = m_mqttChannels.key(channel); + if (!device) { + qCWarning(dcShelly()) << "Received a client connect for a device we don't know!"; + return; + } + device->setStateValue(shellyOneConnectedStateTypeId, true); +} + +void DevicePluginShelly::onClientDisconnected(MqttChannel *channel) +{ + Device *device = m_mqttChannels.key(channel); + if (!device) { + qCWarning(dcShelly()) << "Received a client disconnect for a device we don't know!"; + return; + } + device->setStateValue(shellyOneConnectedStateTypeId, false); +} + +void DevicePluginShelly::onPublishReceived(MqttChannel *channel, const QString &topic, const QByteArray &payload) +{ + Device *device = m_mqttChannels.key(channel); + if (!device) { + qCWarning(dcShelly()) << "Received a publish message for a device we don't know!"; + return; + } + + QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString(); + if (topic == "shellies/" + shellyId + "/input/0") { + // "1" or "0" + // Emit event button pressed + } + + if (topic == "shellies/" + shellyId + "/relay/0") { + bool on = payload == "on"; + device->setStateValue(shellyOnePowerStateTypeId, on); + } + qCDebug(dcShelly()) << "Publish received from" << device->name() << topic << payload; + +} diff --git a/shelly/devicepluginshelly.h b/shelly/devicepluginshelly.h new file mode 100644 index 00000000..34f35a68 --- /dev/null +++ b/shelly/devicepluginshelly.h @@ -0,0 +1,59 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * nymea is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * nymea 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with nymea. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINSHELLY_H +#define DEVICEPLUGINSHELLY_H + +#include "devices/deviceplugin.h" + +class ZeroConfServiceBrowser; + +class MqttChannel; + +class DevicePluginShelly: public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginshelly.json") + Q_INTERFACES(DevicePlugin) + + +public: + explicit DevicePluginShelly(); + ~DevicePluginShelly() override; + + void init() override; + void discoverDevices(DeviceDiscoveryInfo *info) override; + void setupDevice(DeviceSetupInfo *info) override; + void deviceRemoved(Device *device) override; + void executeAction(DeviceActionInfo *info) override; + +private slots: + void onClientConnected(MqttChannel* channel); + void onClientDisconnected(MqttChannel* channel); + void onPublishReceived(MqttChannel* channel, const QString &topic, const QByteArray &payload); + +private: + ZeroConfServiceBrowser *m_zeroconfBrowser = nullptr; + + QHash m_mqttChannels; +}; + +#endif // DEVICEPLUGINSHELLY_H diff --git a/shelly/devicepluginshelly.json b/shelly/devicepluginshelly.json new file mode 100644 index 00000000..0b45d850 --- /dev/null +++ b/shelly/devicepluginshelly.json @@ -0,0 +1,57 @@ +{ + "name": "shelly", + "displayName": "Shelly", + "id": "6162773b-0435-408c-a4f8-7860d38031a9", + "vendors": [ + { + "name": "shelly", + "displayName": "Shelly", + "id": "d8e45fc2-90af-492e-8305-50baa1ec4c18", + "deviceClasses": [ + { + "id": "f810b66a-7177-4397-9771-4229abaabbb6", + "name": "shellyOne", + "displayName": "Shelly One", + "createMethods": ["discovery"], + "interfaces": [ "powerswitch", "connectable" ], + "paramTypes": [ + { + "id": "1d301dc0-5e48-473f-a611-8e407289e545", + "name":"id", + "displayName": "ID", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "e5d41e05-2296-457e-97d8-98a5ac0de615", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "0f6df838-7fc4-4fc0-9247-b9b8fa4ec924", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true + } + ], + "eventTypes": [ + { + "id": "172e6aa3-13d3-4c71-8a4d-112605460863", + "name": "pressed", + "displayName": "Pressed" + } + ] + } + ] + } + ] +} diff --git a/shelly/shelly.pro b/shelly/shelly.pro new file mode 100644 index 00000000..f6ee82c4 --- /dev/null +++ b/shelly/shelly.pro @@ -0,0 +1,9 @@ +include(../plugins.pri) + +QT += network + +SOURCES += \ + devicepluginshelly.cpp \ + +HEADERS += \ + devicepluginshelly.h \ diff --git a/tasmota/deviceplugintasmota.cpp b/tasmota/deviceplugintasmota.cpp index 53b1d5ca..774bdeb5 100644 --- a/tasmota/deviceplugintasmota.cpp +++ b/tasmota/deviceplugintasmota.cpp @@ -84,7 +84,7 @@ Device::DeviceSetupStatus DevicePluginTasmota::setupDevice(Device *device) qCWarning(dcTasmota) << "Not a valid IP address given for IP address parameter"; return Device::DeviceSetupStatusFailure; } - MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(device->id(), deviceAddress); + MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(device->id().toString().remove(QRegExp("[{}-]")), deviceAddress); if (!channel) { qCWarning(dcTasmota) << "Failed to create MQTT channel."; return Device::DeviceSetupStatusFailure; @@ -99,7 +99,7 @@ Device::DeviceSetupStatus DevicePluginTasmota::setupDevice(Device *device) configItems.insert("MqttUser", channel->username()); configItems.insert("MqttPassword", channel->password()); configItems.insert("Topic", "sonoff"); - configItems.insert("FullTopic", channel->topicPrefix() + "/%topic%/"); + configItems.insert("FullTopic", channel->topicPrefixList().first() + "/%topic%/"); QStringList configList; foreach (const QString &key, configItems.keys()) { @@ -222,8 +222,8 @@ Device::DeviceError DevicePluginTasmota::executeAction(Device *device, const Act } ParamTypeId channelParamTypeId = m_channelParamTypeMap.value(device->deviceClassId()); ParamTypeId powerActionParamTypeId = ParamTypeId(m_powerStateTypeMap.value(device->deviceClassId()).toString()); - qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(channelParamTypeId).toString() << (action.param(powerActionParamTypeId).value().toBool() ? "ON" : "OFF"); - channel->publish(channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(channelParamTypeId).toString().toLower(), action.param(powerActionParamTypeId).value().toBool() ? "ON" : "OFF"); + qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(channelParamTypeId).toString() << (action.param(powerActionParamTypeId).value().toBool() ? "ON" : "OFF"); + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(channelParamTypeId).toString().toLower(), action.param(powerActionParamTypeId).value().toBool() ? "ON" : "OFF"); device->setStateValue(m_powerStateTypeMap.value(device->deviceClassId()), action.param(powerActionParamTypeId).value().toBool()); return Device::DeviceErrorNoError; } @@ -237,20 +237,20 @@ Device::DeviceError DevicePluginTasmota::executeAction(Device *device, const Act ParamTypeId openingChannelParamTypeId = m_openingChannelParamTypeMap.value(device->deviceClassId()); ParamTypeId closingChannelParamTypeId = m_closingChannelParamTypeMap.value(device->deviceClassId()); if (action.actionTypeId() == tasmotaShutterOpenActionTypeId) { - qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString() << "OFF"; - channel->publish(channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString().toLower(), "OFF"); - qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString() << "ON"; - channel->publish(channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString().toLower(), "ON"); + qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString() << "ON"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString().toLower(), "ON"); } else if (action.actionTypeId() == tasmotaShutterCloseActionTypeId) { - qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString() << "OFF"; - channel->publish(channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString().toLower(), "OFF"); - qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString() << "ON"; - channel->publish(channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString().toLower(), "ON"); + qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString() << "ON"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString().toLower(), "ON"); } else { // Stop - qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString() << "OFF"; - channel->publish(channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString().toLower(), "OFF"); - qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString() << "OFF"; - channel->publish(channel->topicPrefix() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(openingChannelParamTypeId).toString().toLower(), "OFF"); + qCDebug(dcTasmota) << "Publishing:" << channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString() << "OFF"; + channel->publish(channel->topicPrefixList().first() + "/sonoff/cmnd/" + device->paramValue(closingChannelParamTypeId).toString().toLower(), "OFF"); } return Device::DeviceErrorNoError; } @@ -289,7 +289,7 @@ void DevicePluginTasmota::onPublishReceived(MqttChannel *channel, const QString qCDebug(dcTasmota) << "Publish received from Sonoff device:" << topic << payload; Device *dev = m_mqttChannels.key(channel); if (m_ipAddressParamTypeMap.contains(dev->deviceClassId())) { - if (topic.startsWith(channel->topicPrefix() + "/sonoff/POWER")) { + if (topic.startsWith(channel->topicPrefixList().first() + "/sonoff/POWER")) { QString channelName = topic.split("/").last(); foreach (Device *child, myDevices()) { @@ -308,7 +308,7 @@ void DevicePluginTasmota::onPublishReceived(MqttChannel *channel, const QString } } } - if (topic.startsWith(channel->topicPrefix() + "/sonoff/STATE")) { + if (topic.startsWith(channel->topicPrefixList().first() + "/sonoff/STATE")) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error); if (error.error != QJsonParseError::NoError) {