Tuya: Add light devices

master
Michael Zanetti 2021-01-27 23:35:46 +01:00
parent 64dfb1beb7
commit ea2171cea2
3 changed files with 176 additions and 39 deletions

View File

@ -49,15 +49,25 @@
const int queryInterval = 130; const int queryInterval = 130;
const int discoveryInterval = 610; const int discoveryInterval = 610;
QHash<ThingClassId, ParamTypeId> idParamTypeIdsMap = {
{tuyaClosableThingClassId, tuyaClosableThingIdParamTypeId},
{tuyaSwitchThingClassId, tuyaSwitchThingIdParamTypeId},
{tuyaLightThingClassId, tuyaLightThingIdParamTypeId}
};
QHash<ThingClassId, StateTypeId> connectedStateTypeIdsMap = {
{tuyaClosableThingClassId, tuyaClosableConnectedStateTypeId},
{tuyaSwitchThingClassId, tuyaSwitchConnectedStateTypeId},
{tuyaLightThingClassId, tuyaLightConnectedStateTypeId}
};
QHash<ActionTypeId, ThingClassId> powerStateTypeIdsMap = {
{tuyaSwitchThingClassId, tuyaSwitchPowerStateTypeId},
{tuyaLightThingClassId, tuyaLightPowerStateTypeId}
};
IntegrationPluginTuya::IntegrationPluginTuya(QObject *parent): IntegrationPlugin(parent) 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() IntegrationPluginTuya::~IntegrationPluginTuya()
@ -236,19 +246,20 @@ void IntegrationPluginTuya::confirmPairing(ThingPairingInfo *info, const QString
void IntegrationPluginTuya::executeAction(ThingActionInfo *info) void IntegrationPluginTuya::executeAction(ThingActionInfo *info)
{ {
if (info->action().actionTypeId() == tuyaSwitchPowerActionTypeId) { QString devId = info->thing()->paramValue(idParamTypeIdsMap.value(info->thing()->thingClassId())).toString();
bool on = info->action().param(tuyaSwitchPowerActionPowerParamTypeId).value().toBool();
QString devId = info->thing()->paramValue(tuyaSwitchThingIdParamTypeId).toString(); if (powerStateTypeIdsMap.values().contains(info->action().actionTypeId())) {
bool on = info->action().paramValue(info->action().actionTypeId()).toBool();
controlTuyaSwitch(devId, "turnOnOff", on ? "1" : "0", info); controlTuyaSwitch(devId, "turnOnOff", on ? "1" : "0", info);
connect(info, &ThingActionInfo::finished, [info, on](){ connect(info, &ThingActionInfo::finished, [info, on](){
if (info->status() == Thing::ThingErrorNoError) { if (info->status() == Thing::ThingErrorNoError) {
info->thing()->setStateValue(tuyaSwitchPowerStateTypeId, on); info->thing()->setStateValue(powerStateTypeIdsMap.value(info->thing()->thingClassId()), on);
} }
}); });
return; return;
} }
if (info->thing()->thingClassId() == tuyaClosableThingClassId) { if (info->thing()->thingClassId() == tuyaClosableThingClassId) {
QString devId = info->thing()->paramValue(tuyaClosableThingIdParamTypeId).toString();
if (info->action().actionTypeId() == tuyaClosableOpenActionTypeId) { if (info->action().actionTypeId() == tuyaClosableOpenActionTypeId) {
controlTuyaSwitch(devId, "turnOnOff", "1", info); controlTuyaSwitch(devId, "turnOnOff", "1", info);
return; return;
@ -261,7 +272,45 @@ void IntegrationPluginTuya::executeAction(ThingActionInfo *info)
controlTuyaSwitch(devId, "startStop", "0", info); controlTuyaSwitch(devId, "startStop", "0", info);
return; 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<QColor>();
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()); 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(); QString name = deviceMap.value("name").toString();
if (devType == "switch") { 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)); Thing *d = myThings().findByParams(ParamList() << Param(tuyaSwitchThingIdParamTypeId, id));
if (d) { 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 {
qCDebug(dcTuya()) << "Found new Tuya switch" << id << name; qCDebug(dcTuya()) << "Found new Tuya switch" << id << name;
ThingDescriptor descriptor(tuyaSwitchThingClassId, name, QString(), thing->id()); ThingDescriptor descriptor(tuyaSwitchThingClassId, name, QString(), thing->id());
descriptor.setParams(ParamList() << Param(tuyaSwitchThingIdParamTypeId, id)); descriptor.setParams(ParamList() << Param(tuyaSwitchThingIdParamTypeId, id));
unknownDevices.append(descriptor); 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 { } 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; qCDebug(dcTuya()) << "Found new Tuya cover" << id << name;
ThingDescriptor descriptor(tuyaClosableThingClassId, name, QString(), thing->id()); ThingDescriptor descriptor(tuyaClosableThingClassId, name, QString(), thing->id());
descriptor.setParams(ParamList() << Param(tuyaClosableThingIdParamTypeId, id)); descriptor.setParams(ParamList() << Param(tuyaClosableThingIdParamTypeId, id));
unknownDevices.append(descriptor); 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 { } else {
qCWarning(dcTuya()) << "Skipping unsupported thing type:" << devType; qCWarning(dcTuya()) << "Skipping unsupported thing type:" << devType;
qCWarning(dcTuya()) << "Please report this including the following data:\n" << qUtf8Printable(QJsonDocument::fromVariant(deviceVariant).toJson()); 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; 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()); pluginStorage()->beginGroup(thing->parentId().toString());
QString accesToken = pluginStorage()->value("accessToken").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(); bool state = result.value("payload").toMap().value("data").toMap().value("state").toBool();
qCDebug(dcTuya()) << "Device" << thing->name() << "is online:" << connected << "on:" << state; 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())) { if (powerStateTypeIdsMap.contains(thing->thingClassId())) {
thing->setStateValue(m_powerStateTypeIdsMap.value(thing->thingClassId()), state); 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 *thing = info->thing();
Thing *parentDevice = myThings().findById(thing->parentId()); Thing *parentDevice = myThings().findById(thing->parentId());
@ -550,6 +616,8 @@ void IntegrationPluginTuya::controlTuyaSwitch(const QString &devId, const QStrin
QJsonDocument jsonDoc = QJsonDocument::fromVariant(data); QJsonDocument jsonDoc = QJsonDocument::fromVariant(data);
qCDebug(dcTuya()) << "Controlling payload:" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, jsonDoc.toJson(QJsonDocument::Compact)); QNetworkReply *reply = hardwareManager()->networkManager()->post(request, jsonDoc.toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, [reply](){reply->deleteLater();}); connect(reply, &QNetworkReply::finished, [reply](){reply->deleteLater();});
connect(reply, &QNetworkReply::finished, info, [info, reply](){ connect(reply, &QNetworkReply::finished, info, [info, reply](){
@ -571,7 +639,7 @@ void IntegrationPluginTuya::controlTuyaSwitch(const QString &devId, const QStrin
QVariantMap dataMap = jsonDoc.toVariant().toMap(); QVariantMap dataMap = jsonDoc.toVariant().toMap();
bool success = dataMap.value("header").toMap().value("code").toString() == "SUCCESS"; bool success = dataMap.value("header").toMap().value("code").toString() == "SUCCESS";
if (!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); info->finish(Thing::ThingErrorHardwareFailure);
return; return;
} }

View File

@ -64,16 +64,12 @@ private:
void updateChildDevices(Thing *thing); void updateChildDevices(Thing *thing);
void queryDevice(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<ThingId, QTimer*> m_tokenExpiryTimers; QHash<ThingId, QTimer*> m_tokenExpiryTimers;
PluginTimer *m_pluginTimerQuery = nullptr; PluginTimer *m_pluginTimerQuery = nullptr;
PluginTimer *m_pluginTimerDiscovery = nullptr; PluginTimer *m_pluginTimerDiscovery = nullptr;
QHash<ThingClassId, ParamTypeId> m_devIdParamTypeIdsMap;
QHash<ThingClassId, StateTypeId> m_connectedStateTypeIdsMap;
QHash<ThingClassId, StateTypeId> m_powerStateTypeIdsMap;
QHash<Thing*, QList<Thing*>> m_pollQueue; QHash<Thing*, QList<Thing*>> m_pollQueue;
}; };

View File

@ -124,6 +124,79 @@
"displayName": "Stop" "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
}
]
} }
] ]
} }