diff --git a/tuya/integrationplugintuya.cpp b/tuya/integrationplugintuya.cpp index 35a8cc78..c3dab1b4 100644 --- a/tuya/integrationplugintuya.cpp +++ b/tuya/integrationplugintuya.cpp @@ -49,15 +49,25 @@ const int queryInterval = 130; const int discoveryInterval = 610; +QHash idParamTypeIdsMap = { + {tuyaClosableThingClassId, tuyaClosableThingIdParamTypeId}, + {tuyaSwitchThingClassId, tuyaSwitchThingIdParamTypeId}, + {tuyaLightThingClassId, tuyaLightThingIdParamTypeId} +}; + +QHash connectedStateTypeIdsMap = { + {tuyaClosableThingClassId, tuyaClosableConnectedStateTypeId}, + {tuyaSwitchThingClassId, tuyaSwitchConnectedStateTypeId}, + {tuyaLightThingClassId, tuyaLightConnectedStateTypeId} +}; + +QHash powerStateTypeIdsMap = { + {tuyaSwitchThingClassId, tuyaSwitchPowerStateTypeId}, + {tuyaLightThingClassId, tuyaLightPowerStateTypeId} +}; + IntegrationPluginTuya::IntegrationPluginTuya(QObject *parent): IntegrationPlugin(parent) { - m_devIdParamTypeIdsMap[tuyaClosableThingClassId] = tuyaClosableThingIdParamTypeId; - m_devIdParamTypeIdsMap[tuyaSwitchThingClassId] = tuyaSwitchThingIdParamTypeId; - - m_connectedStateTypeIdsMap[tuyaClosableThingClassId] = tuyaClosableConnectedStateTypeId; - m_connectedStateTypeIdsMap[tuyaSwitchThingClassId] = tuyaSwitchConnectedStateTypeId; - - m_powerStateTypeIdsMap[tuyaSwitchThingClassId] = tuyaSwitchPowerStateTypeId; } IntegrationPluginTuya::~IntegrationPluginTuya() @@ -236,19 +246,20 @@ void IntegrationPluginTuya::confirmPairing(ThingPairingInfo *info, const QString void IntegrationPluginTuya::executeAction(ThingActionInfo *info) { - if (info->action().actionTypeId() == tuyaSwitchPowerActionTypeId) { - bool on = info->action().param(tuyaSwitchPowerActionPowerParamTypeId).value().toBool(); - QString devId = info->thing()->paramValue(tuyaSwitchThingIdParamTypeId).toString(); + QString devId = info->thing()->paramValue(idParamTypeIdsMap.value(info->thing()->thingClassId())).toString(); + + if (powerStateTypeIdsMap.values().contains(info->action().actionTypeId())) { + bool on = info->action().paramValue(info->action().actionTypeId()).toBool(); controlTuyaSwitch(devId, "turnOnOff", on ? "1" : "0", info); connect(info, &ThingActionInfo::finished, [info, on](){ if (info->status() == Thing::ThingErrorNoError) { - info->thing()->setStateValue(tuyaSwitchPowerStateTypeId, on); + info->thing()->setStateValue(powerStateTypeIdsMap.value(info->thing()->thingClassId()), on); } }); return; } + if (info->thing()->thingClassId() == tuyaClosableThingClassId) { - QString devId = info->thing()->paramValue(tuyaClosableThingIdParamTypeId).toString(); if (info->action().actionTypeId() == tuyaClosableOpenActionTypeId) { controlTuyaSwitch(devId, "turnOnOff", "1", info); return; @@ -261,7 +272,45 @@ void IntegrationPluginTuya::executeAction(ThingActionInfo *info) controlTuyaSwitch(devId, "startStop", "0", info); return; } + } + if (info->action().actionTypeId() == tuyaLightBrightnessActionTypeId) { + int brightness = info->action().paramValue(tuyaLightBrightnessActionBrightnessParamTypeId).toInt() * 10; + controlTuyaSwitch(devId, "brightnessSet", QString::number(qMax(10, brightness)), info); + connect(info, &ThingActionInfo::finished, [info, brightness](){ + if (info->status() == Thing::ThingErrorNoError) { + info->thing()->setStateValue(tuyaLightBrightnessStateTypeId, brightness / 10); + } + }); + return; + } + + if (info->action().actionTypeId() == tuyaLightColorActionTypeId) { + QColor color = info->action().paramValue(tuyaLightColorActionColorParamTypeId).value(); + QVariantMap colorMap; + colorMap.insert("hue", color.hsvHue()); + colorMap.insert("saturation", color.hsvSaturation()); + colorMap.insert("brightness", qMax(10, info->thing()->stateValue(tuyaLightBrightnessStateTypeId).toInt() * 10)); + controlTuyaSwitch(devId, "colorSet", colorMap, info); + connect(info, &ThingActionInfo::finished, [info, color](){ + if (info->status() == Thing::ThingErrorNoError) { + info->thing()->setStateValue(tuyaLightColorStateTypeId, color); + } + }); + return; + } + + if (info->action().actionTypeId() == tuyaLightColorTemperatureActionTypeId) { + int ct = info->action().paramValue(tuyaLightColorTemperatureStateTypeId).toInt(); + QVariantMap params; + params.insert("value", ct * 10); + controlTuyaSwitch(devId, "colorTemperatureSet", params, info); + connect(info, &ThingActionInfo::finished, [info, ct](){ + if (info->status() == Thing::ThingErrorNoError) { + info->thing()->setStateValue(tuyaLightColorTemperatureStateTypeId, ct); + } + }); + return; } Q_ASSERT_X(false, "tuyaplugin", "Unhandled action type " + info->action().actionTypeId().toByteArray()); } @@ -406,33 +455,50 @@ void IntegrationPluginTuya::updateChildDevices(Thing *thing) QString name = deviceMap.value("name").toString(); if (devType == "switch") { - bool online = deviceMap.value("data").toMap().value("online").toBool(); - bool state = deviceMap.value("data").toMap().value("state").toBool(); - Thing *d = myThings().findByParams(ParamList() << Param(tuyaSwitchThingIdParamTypeId, id)); - if (d) { - qCDebug(dcTuya()) << "Found existing Tuya switch" << d->name() << id << name << (online ? "online:" : "offline") << (state ? "on": "off"); - d->setStateValue(tuyaSwitchConnectedStateTypeId, online); - d->setStateValue(tuyaSwitchPowerStateTypeId, state); - } else { + if (!d) { qCDebug(dcTuya()) << "Found new Tuya switch" << id << name; ThingDescriptor descriptor(tuyaSwitchThingClassId, name, QString(), thing->id()); descriptor.setParams(ParamList() << Param(tuyaSwitchThingIdParamTypeId, id)); unknownDevices.append(descriptor); - } - } else if (devType == "cover") { - bool online = deviceMap.value("data").toMap().value("online").toBool(); - - Thing *d = myThings().findByParams(ParamList() << Param(tuyaClosableThingIdParamTypeId, id)); - if (d) { - qCDebug(dcTuya()) << "Found existing Tuya cover" << d->name() << id << name << (online ? "online" : "offline"); - d->setStateValue(tuyaClosableConnectedStateTypeId, online); } else { + bool online = deviceMap.value("data").toMap().value("online").toBool(); + bool state = deviceMap.value("data").toMap().value("state").toBool(); + qCDebug(dcTuya()) << "Found existing Tuya switch" << d->name() << id << name << (online ? "online:" : "offline") << (state ? "on": "off"); + d->setStateValue(tuyaSwitchConnectedStateTypeId, online); + d->setStateValue(tuyaSwitchPowerStateTypeId, state); + } + + } else if (devType == "cover") { + Thing *d = myThings().findByParams(ParamList() << Param(tuyaClosableThingIdParamTypeId, id)); + if (!d) { qCDebug(dcTuya()) << "Found new Tuya cover" << id << name; ThingDescriptor descriptor(tuyaClosableThingClassId, name, QString(), thing->id()); descriptor.setParams(ParamList() << Param(tuyaClosableThingIdParamTypeId, id)); unknownDevices.append(descriptor); + } else { + bool online = deviceMap.value("data").toMap().value("online").toBool(); + qCDebug(dcTuya()) << "Found existing Tuya cover" << d->name() << id << name << (online ? "online" : "offline"); + d->setStateValue(tuyaClosableConnectedStateTypeId, online); } + + } else if (devType == "light") { + Thing *d = myThings().findByParams(ParamList() << Param(tuyaLightThingIdParamTypeId, id)); + if (!d) { + qCDebug(dcTuya()) << "Found new color Tuya light" << id << name; + ThingDescriptor descriptor(tuyaLightThingClassId, name, QString(), thing->id()); + descriptor.setParams(ParamList() << Param(tuyaLightThingIdParamTypeId, id)); + unknownDevices.append(descriptor); + } else { + bool online = deviceMap.value("data").toMap().value("online").toBool(); + bool state = deviceMap.value("data").toMap().value("state").toBool(); + qCDebug(dcTuya()) << "Found existing Tuya color light" << d->name() << id << name << (online ? "online" : "offline"); + int brightness = deviceMap.value("data").toMap().value("brightness").toInt() / 10; // Apparently the value in the tuya cloud is 10 - 1000 + d->setStateValue(tuyaLightConnectedStateTypeId, online); + d->setStateValue(tuyaLightPowerStateTypeId, state); + d->setStateValue(tuyaLightBrightnessStateTypeId, brightness); + } + } else { qCWarning(dcTuya()) << "Skipping unsupported thing type:" << devType; qCWarning(dcTuya()) << "Please report this including the following data:\n" << qUtf8Printable(QJsonDocument::fromVariant(deviceVariant).toJson()); @@ -451,7 +517,7 @@ void IntegrationPluginTuya::queryDevice(Thing *thing) { qCDebug(dcTuya()) << "Updating thing:" << thing; - QString devId = thing->paramValue(m_devIdParamTypeIdsMap.value(thing->thingClassId())).toString(); + QString devId = thing->paramValue(idParamTypeIdsMap.value(thing->thingClassId())).toString(); pluginStorage()->beginGroup(thing->parentId().toString()); QString accesToken = pluginStorage()->value("accessToken").toString(); @@ -510,15 +576,15 @@ void IntegrationPluginTuya::queryDevice(Thing *thing) bool state = result.value("payload").toMap().value("data").toMap().value("state").toBool(); qCDebug(dcTuya()) << "Device" << thing->name() << "is online:" << connected << "on:" << state; - thing->setStateValue(m_connectedStateTypeIdsMap.value(thing->thingClassId()), connected); + thing->setStateValue(connectedStateTypeIdsMap.value(thing->thingClassId()), connected); - if (m_powerStateTypeIdsMap.contains(thing->thingClassId())) { - thing->setStateValue(m_powerStateTypeIdsMap.value(thing->thingClassId()), state); + if (powerStateTypeIdsMap.contains(thing->thingClassId())) { + thing->setStateValue(powerStateTypeIdsMap.value(thing->thingClassId()), state); } }); } -void IntegrationPluginTuya::controlTuyaSwitch(const QString &devId, const QString &command, const QString &value, ThingActionInfo *info) +void IntegrationPluginTuya::controlTuyaSwitch(const QString &devId, const QString &command, const QVariant &value, ThingActionInfo *info) { Thing *thing = info->thing(); Thing *parentDevice = myThings().findById(thing->parentId()); @@ -550,6 +616,8 @@ void IntegrationPluginTuya::controlTuyaSwitch(const QString &devId, const QStrin QJsonDocument jsonDoc = QJsonDocument::fromVariant(data); + qCDebug(dcTuya()) << "Controlling payload:" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); + QNetworkReply *reply = hardwareManager()->networkManager()->post(request, jsonDoc.toJson(QJsonDocument::Compact)); connect(reply, &QNetworkReply::finished, [reply](){reply->deleteLater();}); connect(reply, &QNetworkReply::finished, info, [info, reply](){ @@ -571,7 +639,7 @@ void IntegrationPluginTuya::controlTuyaSwitch(const QString &devId, const QStrin QVariantMap dataMap = jsonDoc.toVariant().toMap(); bool success = dataMap.value("header").toMap().value("code").toString() == "SUCCESS"; if (!success) { - qCWarning(dcTuya()) << "Tuya response indicates an issue..."; + qCWarning(dcTuya()) << "Tuya response indicates an issue:" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); info->finish(Thing::ThingErrorHardwareFailure); return; } diff --git a/tuya/integrationplugintuya.h b/tuya/integrationplugintuya.h index a10bcf96..f2d3bbe3 100644 --- a/tuya/integrationplugintuya.h +++ b/tuya/integrationplugintuya.h @@ -64,16 +64,12 @@ private: void updateChildDevices(Thing *thing); void queryDevice(Thing *thing); - void controlTuyaSwitch(const QString &devId, const QString &command, const QString &value, ThingActionInfo *info); + void controlTuyaSwitch(const QString &devId, const QString &command, const QVariant &value, ThingActionInfo *info); QHash m_tokenExpiryTimers; PluginTimer *m_pluginTimerQuery = nullptr; PluginTimer *m_pluginTimerDiscovery = nullptr; - QHash m_devIdParamTypeIdsMap; - QHash m_connectedStateTypeIdsMap; - QHash m_powerStateTypeIdsMap; - QHash> m_pollQueue; }; diff --git a/tuya/integrationplugintuya.json b/tuya/integrationplugintuya.json index cf0a1576..d02497f4 100644 --- a/tuya/integrationplugintuya.json +++ b/tuya/integrationplugintuya.json @@ -124,6 +124,79 @@ "displayName": "Stop" } ] + }, + { + "id": "6aaa67b5-b1ef-41e8-af72-0915e529b3c9", + "name": "tuyaLight", + "displayName": "Tuya color light", + "createMethods": ["auto"], + "interfaces": ["colorlight", "connectable"], + "paramTypes": [ + { + "id": "7fda1e07-6e80-465a-8322-872d8ab42583", + "name": "id", + "displayName": "ID", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes": [ + { + "id": "6735b5eb-091b-4cad-aaa4-33ff76690e68", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "5b2f7c3e-6701-447f-9158-53abb5e81395", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalOutput" + }, + { + "id": "e2eba44d-73f9-43bc-97a3-ac4ff347b02c", + "name": "brightness", + "displayName": "Brightness", + "displayNameEvent": "Brightness changed", + "displayNameAction": "Set brightness", + "type": "int", + "minValue": 0, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": "0", + "writable": true + }, + { + "id": "7ccf9dc3-6b3f-4793-b0d5-3c97afb34cf0", + "name": "color", + "displayName": "Color", + "displayNameEvent": "Color changed", + "displayNameAction": "Set color", + "type": "QColor", + "defaultValue": "white", + "writable": true + }, + { + "id": "8a07b6ca-3cb1-43f1-9b49-405349c2f146", + "name": "colorTemperature", + "displayName": "Color temperature", + "displayNameEvent": "Color temperature changed", + "displayNameAction": "Set color temperature", + "type": "int", + "minValue": 0, + "maxValue": 100, + "defaultValue": "50", + "writable": true + } + ] } ] }