From 765b2c6c75436c65cf4bac1158c99c3a1f0ce75c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 15 Feb 2019 14:50:52 +0100 Subject: [PATCH] SonoffTasmota: Fix Tasmota device setup web command and add support for Shutters on 2CH switches --- tasmota/deviceplugintasmota.cpp | 90 +++++++++++++++++++++++++++----- tasmota/deviceplugintasmota.h | 2 + tasmota/deviceplugintasmota.json | 52 +++++++++++++++++- 3 files changed, 128 insertions(+), 16 deletions(-) diff --git a/tasmota/deviceplugintasmota.cpp b/tasmota/deviceplugintasmota.cpp index 6d8a9a6b..9b0c2215 100644 --- a/tasmota/deviceplugintasmota.cpp +++ b/tasmota/deviceplugintasmota.cpp @@ -81,16 +81,20 @@ DevicePluginTasmota::DevicePluginTasmota() // Helper maps for virtual childs (aka tasmota*) m_channelParamTypeMap[tasmotaSwitchDeviceClassId] = tasmotaSwitchDeviceChannelNameParamTypeId; m_channelParamTypeMap[tasmotaLightDeviceClassId] = tasmotaLightDeviceChannelNameParamTypeId; + m_openingChannelParamTypeMap[tasmotaShutterDeviceClassId] = tasmotaShutterDeviceOpeningChannelParamTypeId; + m_closingChannelParamTypeMap[tasmotaShutterDeviceClassId] = tasmotaShutterDeviceClosingChannelParamTypeId; m_powerStateTypeMap[tasmotaSwitchDeviceClassId] = tasmotaSwitchPowerStateTypeId; m_powerStateTypeMap[tasmotaLightDeviceClassId] = tasmotaLightPowerStateTypeId; + // Helper maps for all devices m_connectedStateTypeMap[sonoff_basicDeviceClassId] = sonoff_basicConnectedStateTypeId; m_connectedStateTypeMap[sonoff_dualDeviceClassId] = sonoff_dualConnectedStateTypeId; m_connectedStateTypeMap[sonoff_quadDeviceClassId] = sonoff_quadConnectedStateTypeId; m_connectedStateTypeMap[tasmotaSwitchDeviceClassId] = tasmotaSwitchConnectedStateTypeId; m_connectedStateTypeMap[tasmotaLightDeviceClassId] = tasmotaLightConnectedStateTypeId; + m_connectedStateTypeMap[tasmotaShutterDeviceClassId] = tasmotaShutterConnectedStateTypeId; } DevicePluginTasmota::~DevicePluginTasmota() @@ -117,17 +121,27 @@ DeviceManager::DeviceSetupStatus DevicePluginTasmota::setupDevice(Device *device return DeviceManager::DeviceSetupStatusFailure; } - QUrl url("http://10.10.10.90/sv"); + QUrl url(QString("http://%1/cm").arg(deviceAddress.toString())); QUrlQuery query; - query.addQueryItem("w", "2%2C1"); - query.addQueryItem("mh", channel->serverAddress().toString()); - query.addQueryItem("ml", QString::number(channel->serverPort())); - query.addQueryItem("mc", channel->clientId()); - query.addQueryItem("mu", channel->username()); - query.addQueryItem("mp", channel->password()); - query.addQueryItem("mt", "sonoff"); - query.addQueryItem("mf", channel->topicPrefix() + "/%topic%/"); + 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->topicPrefix() + "/%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); + qCDebug(dcTasmota) << "Configuring Tasmota device:" << url.toString(); QNetworkRequest request(url); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, this, [this, device, channel, reply](){ @@ -167,9 +181,13 @@ DeviceManager::DeviceSetupStatus DevicePluginTasmota::setupDevice(Device *device qCDebug(dcTasmota) << "Adding Tasmota connected devices"; deviceDescriptors.clear(); + int shutterUpChannel = -1; + int shutterDownChannel = -1; for (int i = 0; i < m_attachedDeviceParamTypeIdMap.value(device->deviceClassId()).count(); i++) { ParamTypeId attachedDeviceParamTypeId = m_attachedDeviceParamTypeIdMap.value(device->deviceClassId()).at(i); - if (device->paramValue(attachedDeviceParamTypeId).toString() == "Light") { + QString deviceType = device->paramValue(attachedDeviceParamTypeId).toString(); + qCDebug(dcTasmota) << "Connected Device" << i + 1 << deviceType; + if (deviceType == "Light") { DeviceDescriptor descriptor(tasmotaLightDeviceClassId, device->name() + " CH" + QString::number(i+1), QString(), device->id()); descriptor.setParentDeviceId(device->id()); if (m_attachedDeviceParamTypeIdMap.value(device->deviceClassId()).count() == 1) { @@ -178,11 +196,27 @@ DeviceManager::DeviceSetupStatus DevicePluginTasmota::setupDevice(Device *device descriptor.setParams(ParamList() << Param(tasmotaLightDeviceChannelNameParamTypeId, "POWER" + QString::number(i+1))); } deviceDescriptors << descriptor; + } else if (deviceType == "Roller Shutter Up") { + shutterUpChannel = i+1; + } else if (deviceType == "Roller Shutter Down") { + shutterDownChannel = i+1; } } if (!deviceDescriptors.isEmpty()) { emit autoDevicesAppeared(tasmotaLightDeviceClassId, deviceDescriptors); } + deviceDescriptors.clear(); + if (shutterUpChannel != -1 && shutterDownChannel != -1) { + qCDebug(dcTasmota) << "Adding Shutter device"; + DeviceDescriptor descriptor(tasmotaShutterDeviceClassId, device->name() + " Shutter", QString(), device->id()); + descriptor.setParams(ParamList() + << Param(tasmotaShutterDeviceOpeningChannelParamTypeId, "POWER" + QString::number(shutterUpChannel)) + << Param(tasmotaShutterDeviceClosingChannelParamTypeId, "POWER" + QString::number(shutterDownChannel))); + deviceDescriptors << descriptor; + } + if (!deviceDescriptors.isEmpty()) { + emit autoDevicesAppeared(tasmotaShutterDeviceClassId, deviceDescriptors); + } }); return DeviceManager::DeviceSetupStatusAsync; } @@ -220,6 +254,30 @@ DeviceManager::DeviceError DevicePluginTasmota::executeAction(Device *device, co device->setStateValue(m_powerStateTypeMap.value(device->deviceClassId()), action.param(powerActionParamTypeId).value().toBool()); return DeviceManager::DeviceErrorNoError; } + if (device->deviceClassId() == tasmotaShutterDeviceClassId) { + Device *parentDev = myDevices().findById(device->parentId()); + MqttChannel *channel = m_mqttChannels.value(parentDev); + 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"); + } 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"); + } 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"); + } + return DeviceManager::DeviceErrorNoError; + } + qCWarning(dcTasmota) << "Unhandled execute action call for device" << device; return DeviceManager::DeviceErrorDeviceClassNotFound; } @@ -264,7 +322,9 @@ void DevicePluginTasmota::onPublishReceived(MqttChannel *channel, const QString if (child->paramValue(m_channelParamTypeMap.value(child->deviceClassId())).toString() != channelName) { continue; } - child->setStateValue(m_powerStateTypeMap.value(child->deviceClassId()), payload == "ON"); + if (m_powerStateTypeMap.contains(child->deviceClassId())) { + child->setStateValue(m_powerStateTypeMap.value(child->deviceClassId()), payload == "ON"); + } } } if (topic.startsWith(channel->topicPrefix() + "/sonoff/STATE")) { @@ -278,9 +338,11 @@ void DevicePluginTasmota::onPublishReceived(MqttChannel *channel, const QString if (child->parentId() != dev->id()) { continue; } - QString childChannel = child->paramValue(m_channelParamTypeMap.value(child->deviceClassId())).toString(); - QString valueString = jsonDoc.toVariant().toMap().value(childChannel).toString(); - child->setStateValue(m_powerStateTypeMap.value(child->deviceClassId()), valueString == "ON"); + if (m_powerStateTypeMap.contains(child->deviceClassId())) { + QString childChannel = child->paramValue(m_channelParamTypeMap.value(child->deviceClassId())).toString(); + QString valueString = jsonDoc.toVariant().toMap().value(childChannel).toString(); + child->setStateValue(m_powerStateTypeMap.value(child->deviceClassId()), valueString == "ON"); + } } } diff --git a/tasmota/deviceplugintasmota.h b/tasmota/deviceplugintasmota.h index f73bde9b..d98260bd 100644 --- a/tasmota/deviceplugintasmota.h +++ b/tasmota/deviceplugintasmota.h @@ -57,6 +57,8 @@ private: // Helpers for child devices (virtual ones, starting with tasmota) QHash m_channelParamTypeMap; + QHash m_openingChannelParamTypeMap; + QHash m_closingChannelParamTypeMap; QHash m_powerStateTypeMap; // Helpers for both devices diff --git a/tasmota/deviceplugintasmota.json b/tasmota/deviceplugintasmota.json index 99c343b0..2ce49ca4 100644 --- a/tasmota/deviceplugintasmota.json +++ b/tasmota/deviceplugintasmota.json @@ -60,7 +60,7 @@ "name": "attachedDeviceCH1", "displayName": "Connected device 1", "type": "QString", - "allowedValues": ["None", "Light"], + "allowedValues": ["None", "Light", "Roller Shutter Up", "Roller Shutter Down"], "defaultValue": "None" }, { @@ -68,7 +68,7 @@ "name": "attachedDeviceCH2", "displayName": "Connected device 2", "type": "QString", - "allowedValues": ["None", "Light"], + "allowedValues": ["None", "Light", "Roller Shutter Up", "Roller Shutter Down"], "defaultValue": "None" } ], @@ -213,6 +213,54 @@ "writable": true } ] + }, + { + "id": "c63b02f2-3695-4e8c-9789-1b8a705f3a53", + "name": "tasmotaShutter", + "displayName": "Tasmota shutter", + "createMethods": ["auto"], + "interfaces": ["shutter", "closable", "connectable"], + "paramTypes": [ + { + "id": "4d8f113d-f816-4356-b1ff-31df3f4b515f", + "name": "openingChannel", + "displayName": "Opening channel", + "type": "int" + }, + { + "id": "600c00fd-6a2c-46cd-8031-2d9a1b1bc710", + "name": "closingChannel", + "displayName": "Closing channel", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "7bbf0bbf-abb3-487e-b5e9-077f7b00d8ef", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + { + "id": "8e7fb2f4-2819-4d14-a5ae-95624b097bf7", + "name": "open", + "displayName": "Open" + }, + { + "id": "efbe9290-affd-4902-abb7-dd4ea74ccd1b", + "name": "close", + "displayName": "Close" + }, + { + "id": "c9c9c569-e224-4f63-abed-782cba04d61b", + "name": "stop", + "displayName": "Stop" + } + ] } ] }