cloud actions do now work

master
bernhard.trinnes 2020-07-06 19:36:38 +02:00
parent 8907fb7b47
commit 5e40675829
12 changed files with 488 additions and 309 deletions

View File

@ -1,9 +1,18 @@
# Lifx
This plug-in implements the LAN API for Lifx devices
This plug-in integrates LIFX lights to nymea.
## Supported Things
* All LIFX lights
## Requirements
* LIFX cloud access token.
** Get the token from https://cloud.lifx.com/settings
* Internet connection
* The package 'nymea-plugin-lifx' must be installed.
## More
https://www.lifx.com/

View File

@ -63,52 +63,45 @@ void IntegrationPluginLifx::init()
m_brightnessStateTypeIds.insert(colorBulbThingClassId, colorBulbBrightnessStateTypeId);
m_brightnessStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbBrightnessStateTypeId);
m_colorTemperatureStateTypeIds.insert(colorBulbThingClassId, colorBulbColorTemperatureStateTypeId);
m_colorTemperatureStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbColorTemperatureStateTypeId);
m_hostAddressParamTypeIds.insert(colorBulbThingClassId, colorBulbThingHostParamTypeId);
m_hostAddressParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingHostParamTypeId);
m_portParamTypeIds.insert(colorBulbThingClassId, colorBulbThingPortParamTypeId);
m_portParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingPortParamTypeId);
m_idParamTypeIds.insert(colorBulbThingClassId, colorBulbThingIdParamTypeId);
m_idParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingIdParamTypeId);
m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_hap._tcp"); // discovers all homekit devices
QFile file;
file.setFileName("/tmp/products.json");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCWarning(dcLifx()) << "Could not open products file" << file.errorString() << "file name:" << file.fileName();
} else {
QJsonDocument productsJson = QJsonDocument::fromJson(file.readAll());
file.close();
if (!productsJson.isArray()) {
qCWarning(dcLifx()) << "Products JSON is not a valid array";
} else {
QJsonArray productsArray = productsJson.array().first().toObject().value("products").toArray();
foreach (QJsonValue value, productsArray) {
QJsonObject object = value.toObject();
Lifx::LifxProduct product;
product.pid = object["pid"].toInt();
product.name = object["name"].toString();
qCDebug(dcLifx()) << "Lifx product JSON, found product. PID:" << product.pid << "Name" << product.name;
QJsonObject features = object["features"].toObject();
product.color = features["color"].toBool();
product.infrared = features["infrared"].toBool();
product.matrix = features["matrix"].toBool();
product.multizone = features["multizone"].toBool();
product.minColorTemperature = features["temperature_range"].toArray().first().toInt();
product.maxColorTemperature = features["temperature_range"].toArray().last().toInt();
product.chain = features["chain"].toBool();
m_lifxProducts.insert(product.pid, product);
}
}
}
// TODO for LAN connection, get id and device features
// QFile file;
// file.setFileName("/tmp/products.json");
// if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
// qCWarning(dcLifx()) << "Could not open products file" << file.errorString() << "file name:" << file.fileName();
// } else {
// QJsonDocument productsJson = QJsonDocument::fromJson(file.readAll());
// file.close();
// if (!productsJson.isArray()) {
// qCWarning(dcLifx()) << "Products JSON is not a valid array";
// } else {
// QJsonArray productsArray = productsJson.array().first().toObject().value("products").toArray();
// foreach (QJsonValue value, productsArray) {
// QJsonObject object = value.toObject();
// LifxLan::LifxProduct product;
// product.pid = object["pid"].toInt();
// product.name = object["name"].toString();
// qCDebug(dcLifx()) << "Lifx product JSON, found product. PID:" << product.pid << "Name" << product.name;
// QJsonObject features = object["features"].toObject();
// product.color = features["color"].toBool();
// product.infrared = features["infrared"].toBool();
// product.matrix = features["matrix"].toBool();
// product.multizone = features["multizone"].toBool();
// product.minColorTemperature = features["temperature_range"].toArray().first().toInt();
// product.maxColorTemperature = features["temperature_range"].toArray().last().toInt();
// product.chain = features["chain"].toBool();
// m_lifxProducts.insert(product.pid, product);
// }
// }
// }
m_networkManager = hardwareManager()->networkManager();
}
@ -156,6 +149,7 @@ void IntegrationPluginLifx::confirmPairing(ThingPairingInfo *info, const QString
void IntegrationPluginLifx::discoverThings(ThingDiscoveryInfo *info)
{
// NOTE: the LAN API is not yet finished, to enable LAN discovery add "discovery" to the createMethods
if ((info->thingClassId() == colorBulbThingClassId) || (info->thingClassId() == dimmableBulbThingClassId)) {
QHash<QString, ThingDescriptor> descriptors;
foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) {
@ -203,15 +197,20 @@ void IntegrationPluginLifx::setupThing(ThingSetupInfo *info)
Thing *thing = info->thing();
if (thing->thingClassId() == colorBulbThingClassId || thing->thingClassId() == dimmableBulbThingClassId) {
Lifx *lifx = new Lifx(QHostAddress(thing->paramValue(m_hostAddressParamTypeIds.value(thing->thingClassId())).toString()),
thing->paramValue(m_portParamTypeIds.value(thing->thingClassId())).toInt(), this);
if(lifx->enable()) {
m_lifxConnections.insert(thing, lifx);
//TODO async setup
info->finish(Thing::ThingErrorNoError);
} else {
lifx->deleteLater();
if (thing->parentId().isNull()) {
// Lifx LAN
//LifxLan *lifx = new LifxLan(, this);
//if(lifx->enable()) {
// m_lifxLanConnections.insert(thing, lifx);
//TODO async setup for LAN devices
// info->finish(Thing::ThingErrorNoError);
//} else {
// lifx->deleteLater();
info->finish(Thing::ThingErrorSetupFailed);
//}
} else {
// Lifx Cloud
info->finish(Thing::ThingErrorNoError);
}
} else if (thing->thingClassId() == lifxAccountThingClassId) {
@ -250,8 +249,9 @@ void IntegrationPluginLifx::postSetupThing(Thing *thing)
if (!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() {
foreach (Lifx *lifx, m_lifxConnections) {
foreach (LifxLan *lifx, m_lifxLanConnections) {
Q_UNUSED(lifx)
//TODO update LAN device states
}
foreach (LifxCloud *lifx, m_lifxCloudConnections) {
@ -271,11 +271,11 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
Thing *thing = info->thing();
Action action = info->action();
bool cloudDevice = false;
Lifx *lifx;
LifxLan *lifx;
LifxCloud *lifxCloud;
if (m_lifxConnections.contains(thing)) {
lifx = m_lifxConnections.value(thing);
if (m_lifxLanConnections.contains(thing)) {
lifx = m_lifxLanConnections.value(thing);
} else if (m_lifxCloudConnections.contains(myThings().findById(thing->parentId()))) {
lifxCloud = m_lifxCloudConnections.value(myThings().findById(thing->parentId()));
cloudDevice = true;
@ -299,9 +299,13 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
} else if (action.actionTypeId() == colorBulbBrightnessActionTypeId) {
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool())
lifx->setPower(true);
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){
if (cloudDevice) {
lifxCloud->setPower(lightId, true);
} else {
lifx->setPower(true);
}
}
int brightness = info->action().param(colorBulbBrightnessActionBrightnessParamTypeId).value().toInt();
int requestId;
if (cloudDevice) {
@ -313,8 +317,13 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
m_asyncActions.insert(requestId, info);
} else if (action.actionTypeId() == colorBulbColorActionColorParamTypeId) {
QRgb color = QColor(action.param(colorBulbColorActionColorParamTypeId).value().toString()).rgba();
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool())
lifx->setPower(true);
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){
if (cloudDevice) {
lifxCloud->setPower(lightId, true);
} else {
lifx->setPower(true);
}
}
int requestId;
if (cloudDevice) {
requestId = lifxCloud->setColor(lightId, color);
@ -325,8 +334,13 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
m_asyncActions.insert(requestId, info);
} else if (action.actionTypeId() == colorBulbColorTemperatureActionTypeId) {
int colorTemperature = 6500 - (action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toUInt() * -11.12);
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool())
lifx->setPower(true);
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){
if (cloudDevice) {
lifxCloud->setPower(lightId, true);
} else {
lifx->setPower(true);
}
}
int requestId;
if (cloudDevice) {
requestId = lifxCloud->setColorTemperature(lightId, colorTemperature);
@ -335,6 +349,40 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
}
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
} else if (action.actionTypeId() == colorBulbEffectStateTypeId) {
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){
if (cloudDevice) {
lifxCloud->setPower(lightId, true);
} else {
lifx->setPower(true);
}
}
QString effectString = action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toString();
int requestId;
LifxCloud::Effect effect;
if (effectString == "None") {
effect = LifxCloud::EffectNone;
} else if (effectString == "Breathe") {
effect = LifxCloud::EffectBreathe;
} else if (effectString == "Move") {
effect = LifxCloud::EffectMove;
} else if (effectString == "Morph") {
effect = LifxCloud::EffectMove;
} else if (effectString == "Flame") {
effect = LifxCloud::EffectMove;
} else if (effectString == "Pulse") {
effect = LifxCloud::EffectMove;
}
if (cloudDevice) {
QColor color = QColor(thing->stateValue(colorBulbColorStateTypeId).toString());
requestId = lifxCloud->setEffect(lightId, effect, color);
} else {
qCWarning(dcLifx()) << "LAN devices are not yet supported";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
} else {
Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8());
}
@ -351,8 +399,13 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == dimmableBulbBrightnessActionTypeId) {
int brightness = action.param(dimmableBulbBrightnessActionBrightnessParamTypeId).value().toInt();
if (!thing->stateValue(dimmableBulbPowerStateTypeId).toBool())
lifx->setPower(true);
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){
if (cloudDevice) {
lifxCloud->setPower(lightId, true);
} else {
lifx->setPower(true);
}
}
int requestId;
if (cloudDevice) {
requestId = lifxCloud->setBrightnesss(lightId, brightness);
@ -361,6 +414,39 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
}
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
} else if (action.actionTypeId() == dimmableBulbEffectStateTypeId) {
if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){
if (cloudDevice) {
lifxCloud->setPower(lightId, true);
} else {
lifx->setPower(true);
}
}
QString effectString = action.param(dimmableBulbColorTemperatureActionColorTemperatureParamTypeId).value().toString();
int requestId;
LifxCloud::Effect effect;
if (effectString == "None") {
effect = LifxCloud::EffectNone;
} else if (effectString == "Breathe") {
effect = LifxCloud::EffectBreathe;
} else if (effectString == "Move") {
effect = LifxCloud::EffectMove;
} else if (effectString == "Morph") {
effect = LifxCloud::EffectMove;
} else if (effectString == "Flame") {
effect = LifxCloud::EffectMove;
} else if (effectString == "Pulse") {
effect = LifxCloud::EffectMove;
}
if (cloudDevice) {
requestId = lifxCloud->setEffect(lightId, effect, QColor("0xffffff"));
} else {
qCWarning(dcLifx()) << "LAN devices are not yet supported";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
} else {
Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8());
}
@ -372,8 +458,8 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
void IntegrationPluginLifx::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == colorBulbThingClassId || thing->thingClassId() == dimmableBulbThingClassId) {
if (m_lifxConnections.contains(thing))
m_lifxConnections.take(thing)->deleteLater();
if (m_lifxLanConnections.contains(thing))
m_lifxLanConnections.take(thing)->deleteLater();
} else if (thing->thingClassId() == lifxAccountThingClassId) {
if (m_lifxCloudConnections.contains(thing))
m_lifxCloudConnections.take(thing)->deleteLater();
@ -412,17 +498,17 @@ void IntegrationPluginLifx::executeBrowserItem(BrowserActionInfo *info)
connect(info, &BrowserActionInfo::aborted, this, [requestId, this] {m_asyncBrowserItem.remove(requestId);});
}
void IntegrationPluginLifx::onConnectionChanged(bool connected)
void IntegrationPluginLifx::onLifxLanConnectionChanged(bool connected)
{
Q_UNUSED(connected)
Lifx *lifx = static_cast<Lifx *>(sender());
Thing *thing = m_lifxConnections.key(lifx);
LifxLan *lifx = static_cast<LifxLan *>(sender());
Thing *thing = m_lifxLanConnections.key(lifx);
if (!thing)
return;
thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), connected);
}
void IntegrationPluginLifx::onRequestExecuted(int requestId, bool success)
void IntegrationPluginLifx::onLifxLanRequestExecuted(int requestId, bool success)
{
if (m_asyncActions.contains(requestId)) {
ThingActionInfo *info = m_asyncActions.take(requestId);
@ -492,8 +578,8 @@ void IntegrationPluginLifx::onLifxCloudLightsListReceived(const QList<LifxCloud:
ThingDescriptor thingDescriptor(thingClassId, light.product.name, light.location.name, parentThing->id());
foreach (Thing * thing, myThings().filterByParam(m_idParamTypeIds.value(thingClassId), light.id)) {
thing->setStateValue(m_connectedStateTypeIds.value(thingClassId), light.connected);
thing->setStateValue(m_brightnessStateTypeIds.value(thingClassId), light.brightness);
thing->setStateValue(m_colorTemperatureStateTypeIds.value(thingClassId), light.colorTemperature);
thing->setStateValue(m_brightnessStateTypeIds.value(thingClassId), light.brightness*100.00);
thing->setStateValue(m_colorTemperatureStateTypeIds.value(thingClassId), light.colorTemperature); //TODO Kelvin to mired
thing->setStateValue(m_powerStateTypeIds.value(thingClassId), light.power);
if (thingClassId == colorBulbThingClassId) {
thing->setStateValue(colorBulbColorStateTypeId, light.color);
@ -537,7 +623,6 @@ void IntegrationPluginLifx::onLifxCloudRequestExecuted(int requestId, bool succe
}
}
void IntegrationPluginLifx::onLifxCloudScenesListReceived(const QList<LifxCloud::Scene> &scenes)
{
LifxCloud *lifxCloud = static_cast<LifxCloud *>(sender());

View File

@ -33,7 +33,7 @@
#include "integrations/integrationplugin.h"
#include "plugintimer.h"
#include "lifx.h"
#include "lifxlan.h"
#include "lifxcloud.h"
#include "network/networkaccessmanager.h"
@ -71,7 +71,7 @@ private:
PluginTimer *m_pluginTimer = nullptr;
QHash<LifxCloud *, ThingSetupInfo *> m_asyncCloudSetups;
QHash<int, ThingActionInfo *> m_asyncActions;
QHash<Thing *, Lifx *> m_lifxConnections;
QHash<Thing *, LifxLan *> m_lifxLanConnections;
QHash<Thing *, LifxCloud *> m_lifxCloudConnections;
QHash<LifxCloud *, BrowseResult *> m_asyncBrowseResults;
QHash<int, BrowserActionInfo *> m_asyncBrowserItem;
@ -87,11 +87,11 @@ private:
QHash<ThingClassId, ParamTypeId> m_idParamTypeIds;
QHash<ThingId, ThingActionInfo *> m_pendingBrightnessAction;
QHash<int, Lifx::LifxProduct> m_lifxProducts;
QHash<int, LifxLan::LifxProduct> m_lifxProducts;
private slots:
void onConnectionChanged(bool connected);
void onRequestExecuted(int requestId, bool success);
void onLifxLanConnectionChanged(bool connected);
void onLifxLanRequestExecuted(int requestId, bool success);
void onLifxCloudConnectionChanged(bool connected);
void onLifxCloudAuthenticationChanged(bool authenticated);

View File

@ -51,23 +51,9 @@
"id": "12907c9c-e7f0-47f2-bd58-39d52ffdf24e",
"name": "colorBulb",
"displayName": "Color",
"createMethods": ["auto", "discovery"],
"createMethods": ["auto"],
"interfaces": ["colorlight", "connectable"],
"paramTypes": [
{
"id": "fd1c4817-5111-433a-b5b9-fd9f49d4975c",
"name": "host",
"displayName": "Host address",
"type" : "QString",
"readOnly": true
},
{
"id": "44c13745-300c-491f-b617-3a8d53472998",
"name": "port",
"displayName": "Port",
"type" : "int",
"readOnly": true
},
{
"id": "976ecea0-ac25-47d4-9dc5-362962ddb6c0",
"name": "id",
@ -139,10 +125,14 @@
"displayNameEvent": "Effect changed",
"displayNameAction": "Set effect",
"type": "QString",
"defaultValue": "none",
"defaultValue": "None",
"possibleValues": [
"none",
"color loop"
"None",
"Breathe",
"Move",
"Morph",
"Flame",
"Pulse"
],
"writable": true
}
@ -152,23 +142,9 @@
"id": "a5b02af8-7c97-4a78-9c78-bafee7407b5e",
"name": "dimmableBulb",
"displayName": "Day and Dusk",
"createMethods": ["auto", "discovery"],
"createMethods": ["auto"],
"interfaces": ["colortemperaturelight", "connectable"],
"paramTypes": [
{
"id": "cc0a765b-a753-4e07-a6e5-47e9272c4346",
"name": "host",
"displayName": "Host address",
"type" : "QString",
"readOnly": true
},
{
"id": "d233d9bf-6662-414d-92f6-dd3e267051b5",
"name": "port",
"displayName": "Port",
"type" : "int",
"readOnly": true
},
{
"id": "f157a97b-3fe5-4d9e-b5e3-5636f80d46ed",
"name": "id",
@ -221,6 +197,24 @@
"minValue": 153,
"maxValue": 500,
"writable": true
},
{
"id": "be47c474-eca1-479e-9393-68281a43d72a",
"name": "effect",
"displayName": "Effect",
"displayNameEvent": "Effect changed",
"displayNameAction": "Set effect",
"type": "QString",
"defaultValue": "None",
"possibleValues": [
"None",
"Breathe",
"Move",
"Morph",
"Flame",
"Pulse"
],
"writable": true
}
]
}

View File

@ -1,14 +0,0 @@
include(../plugins.pri)
QT += network
SOURCES += \
integrationpluginlifx.cpp \
lifx.cpp \
lifxcloud.cpp \
HEADERS += \
integrationpluginlifx.h \
lifx.h \
lifxcloud.h \

View File

@ -74,21 +74,9 @@ void LifxCloud::listLights()
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// check HTTP status code
if (status > 207) {
qCWarning(dcLifx()) << "Error get lights list" << status << reply->errorString();
if(!checkHttpStatusCode(reply)) {
return;
}
if (!m_authenticated) {
m_authenticated = true;
emit authenticationChanged(true);
}
if (!m_connected) {
m_connected = true;
emit authenticationChanged(true);
}
QByteArray rawData = reply->readAll();
QJsonDocument data; QJsonParseError error;
@ -110,6 +98,11 @@ void LifxCloud::listLights()
Light light;
light.id = object["id"].toString().toUtf8();
light.uuid = object["uuid"].toString().toUtf8();
if (object["power"].toString() == "on") {
light.power = true;
} else {
light.power = false;
}
light.label = object["label"].toString();
light.connected = object["connected"].toBool();
light.brightness = object["brightness"].toDouble();
@ -162,21 +155,9 @@ void LifxCloud::listScenes()
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// check HTTP status code
if (status != 200) {
qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString();
if(!checkHttpStatusCode(reply)) {
return;
}
if (!m_authenticated) {
m_authenticated = true;
emit authenticationChanged(true);
}
if (!m_connected) {
m_connected = true;
emit authenticationChanged(true);
}
QByteArray rawData = reply->readAll();
qCDebug(dcLifx()) << "Got list scenes reply" << rawData;
QJsonDocument data; QJsonParseError error;
@ -202,27 +183,27 @@ void LifxCloud::listScenes()
int LifxCloud::setPower(const QString &lightId, bool power, int duration)
{
return setState(lightId, StatePower, power, duration);
return setState("id:"+lightId, StatePower, power, duration);
}
int LifxCloud::setBrightnesss(const QString &lightId, int brightness, int duration)
{
return setState(lightId, StateBrightness, brightness/100.00, duration);
return setState("id:"+lightId, StateBrightness, brightness/100.00, duration);
}
int LifxCloud::setColor(const QString &lightId, QColor color, int duration)
{
return setState(lightId, StateColor, color.name(), duration);
return setState("id:"+lightId, StateColor, color.name(), duration);
}
int LifxCloud::setColorTemperature(const QString &selector, int kelvin, int duration)
int LifxCloud::setColorTemperature(const QString &lightId, int kelvin, int duration)
{
return setState(selector, StateColorTemperature, kelvin, duration);
return setState("id:"+lightId, StateColorTemperature, kelvin, duration);
}
int LifxCloud::setInfrared(const QString &lightId, int infrared, int duration)
{
return setState(lightId, StateColor, infrared/100.00, duration);
return setState("id:"+lightId, StateColor, infrared/100.00, duration);
}
int LifxCloud::activateScene(const QString &sceneId)
@ -240,34 +221,59 @@ int LifxCloud::activateScene(const QString &sceneId)
QNetworkReply *reply = m_networkManager->put(request, "");
connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// check HTTP status code
if (status == 401 || status == 403) {
if (m_authenticated) {
m_authenticated = false;
emit authenticationChanged(false);
}
}
if (status > 207) {
qCWarning(dcLifx()) << "Error activate Scene" << status << reply->errorString();
emit requestExecuted(requestId, false);
return;
}
if (!m_authenticated) {
m_authenticated = true;
emit authenticationChanged(true);
}
if (!m_connected) {
m_connected = true;
emit authenticationChanged(true);
}
emit requestExecuted(requestId, true);
emit requestExecuted(requestId, checkHttpStatusCode(reply));
QByteArray rawData = reply->readAll();
qCDebug(dcLifx()) << "Got activate scene reply" << rawData;
});
return requestId;
}
int LifxCloud::setEffect(const QString &lightId, LifxCloud::Effect effect, QColor color)
{
if (m_authorizationToken.isEmpty()) {
qCWarning(dcLifx()) << "Authorization token is not set";
return -1;
}
int requestId = qrand();
QNetworkRequest request;
QJsonObject payload;
switch (effect) {
case LifxCloud::EffectNone:
request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/off").arg(lightId)));
break;
case LifxCloud::EffectBreathe:
request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/breathe").arg(lightId)));
payload["color"] = color.name();
break;
case LifxCloud::EffectMove:
request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/move").arg(lightId)));
break;
case LifxCloud::EffectMorph:
request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/morph").arg(lightId)));
break;
case LifxCloud::EffectFlame:
request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/flame").arg(lightId)));
break;
case LifxCloud::EffectPulse:
request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/pulse").arg(lightId)));
payload["color"] = color.name();
break;
}
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization","Bearer "+m_authorizationToken);
QJsonDocument doc;
doc.setObject(payload);
QNetworkReply *reply = m_networkManager->put(request, doc.toJson());
connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
QByteArray rawData = reply->readAll();
qCDebug(dcLifx()) << "Got set state reply" << rawData;
emit requestExecuted(requestId, checkHttpStatusCode(reply));
});
return requestId;
}
int LifxCloud::setState(const QString &selector, State state, QVariant stateValue, int duration)
{
if (m_authorizationToken.isEmpty()) {
@ -275,7 +281,6 @@ int LifxCloud::setState(const QString &selector, State state, QVariant stateValu
return -1;
}
int requestId = qrand();
QNetworkRequest request;
request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/%1/state").arg(selector)));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
@ -287,45 +292,77 @@ int LifxCloud::setState(const QString &selector, State state, QVariant stateValu
switch (state) {
case StatePower:
if (stateValue.toBool())
payload["power"] = "ON";
payload["power"] = "on";
else
payload["power"] = "OFF";
payload["power"] = "off";
qCDebug(dcLifx()) << "Set state power" << stateValue.toBool();
break;
case StateBrightness:
payload["brightness"] = stateValue.toDouble();
qCDebug(dcLifx()) << "Set state brightness" << stateValue;
break;
case StateColor:
payload["color"] = stateValue.toString();
qCDebug(dcLifx()) << "Set state color" << stateValue;
break;
case StateColorTemperature:
payload["color"] = "kelvin:"+stateValue.toString();
qCDebug(dcLifx()) << "Set state color" << stateValue;
break;
case StateInfrared:
payload["infrared"] = stateValue.toDouble();
qCDebug(dcLifx()) << "Set state infrared" << stateValue;
}
doc.setObject(payload);
QNetworkReply *reply = m_networkManager->post(request, doc.toJson());
qCDebug(dcLifx()) << "Set state request" << request.url() << doc.toJson();
QNetworkReply *reply = m_networkManager->put(request, doc.toJson());
connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// check HTTP status code
if (status > 207) {
qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString();
emit requestExecuted(requestId, false);
return;
}
if (!m_authenticated) {
m_authenticated = true;
emit authenticationChanged(true);
}
if (!m_connected) {
m_connected = true;
emit authenticationChanged(true);
}
connect(reply, &QNetworkReply::finished, this, [requestId, duration,reply, this] {
QByteArray rawData = reply->readAll();
qCDebug(dcLifx()) << "Got set state reply" << rawData;
emit requestExecuted(requestId, true);
if (checkHttpStatusCode(reply)) {
emit requestExecuted(requestId, true);
QTimer::singleShot(duration*1000+500, this, [=] {listLights();});
} else {
emit requestExecuted(requestId, false);
}
});
return requestId;
}
bool LifxCloud::checkHttpStatusCode(QNetworkReply *reply)
{
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcLifx()) << "Request error:" << status << reply->errorString();
if (m_connected) {
m_connected = false;
emit connectionChanged(false);
}
return false;
}
// check HTTP status code
if (status == 401 || status == 403) {
if (m_authenticated) {
m_authenticated = false;
emit authenticationChanged(false);
}
}
if (status > 207) {
qCWarning(dcLifx()) << "Error get scene list" << status;
return false;
}
if (!m_authenticated) {
m_authenticated = true;
emit authenticationChanged(true);
}
if (!m_connected) {
m_connected = true;
emit connectionChanged(true);
}
return true;
}

View File

@ -47,6 +47,16 @@ public:
StateColorTemperature,
StateInfrared
};
enum Effect {
EffectNone,
EffectBreathe,
EffectMove,
EffectMorph,
EffectFlame,
EffectPulse
};
struct Group {
QByteArray id;
QString name;
@ -101,19 +111,22 @@ public:
void listLights();
void listScenes();
int setPower(const QString &lightId, bool power, int duration = 3);
int setBrightnesss(const QString &lightId, int brightness, int duration = 3);
int setColor(const QString &selector, QColor color, int duration = 3);
int setColorTemperature(const QString &selector, int kelvin, int duration = 3);
int setInfrared(const QString &lightId, int infrared, int duration = 3);
int setPower(const QString &lightId, bool power, int duration = 5);
int setBrightnesss(const QString &lightId, int brightness, int duration = 5);
int setColor(const QString &lightId, QColor color, int duration = 5);
int setColorTemperature(const QString &lightId, int kelvin, int duration = 5);
int setInfrared(const QString &lightId, int infrared, int duration = 5);
int activateScene(const QString &sceneId);
int setEffect(const QString &lightId, Effect effect, QColor color);
private:
NetworkAccessManager *m_networkManager = nullptr;
QByteArray m_authorizationToken;
int setState(const QString &lightId, State state, QVariant stateValue, int duration);
bool checkHttpStatusCode(QNetworkReply *reply);
bool m_authenticated = false;
bool m_connected = false;
signals:

View File

@ -29,20 +29,16 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "lifx.h"
#include "lifxlan.h"
#include "extern-plugininfo.h"
#include <QColor>
Lifx::Lifx(const QHostAddress &address, quint16 port, QObject *parent) :
LifxLan::LifxLan(const QHostAddress &address, quint16 port, QObject *parent) :
QObject(parent),
m_host(address),
m_port(port)
{
m_reconnectTimer = new QTimer(this);
m_reconnectTimer->setSingleShot(true);
connect(m_reconnectTimer, &QTimer::timeout, this, &Lifx::onReconnectTimer);
m_clientId = qrand();
m_socket = new QUdpSocket(this);
@ -51,7 +47,7 @@ Lifx::Lifx(const QHostAddress &address, quint16 port, QObject *parent) :
m_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1));
}
Lifx::~Lifx()
LifxLan::~LifxLan()
{
if (m_socket) {
m_socket->waitForBytesWritten(1000);
@ -59,7 +55,7 @@ Lifx::~Lifx()
}
}
bool Lifx::enable()
bool LifxLan::enable()
{
// Bind udp socket and join multicast group
if(!m_socket->bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress)){
@ -75,21 +71,21 @@ bool Lifx::enable()
m_socket = nullptr;
return false;
}
connect(m_socket, &QUdpSocket::readyRead, this, &Lifx::onReadyRead);
connect(m_socket, &QUdpSocket::readyRead, this, &LifxLan::onReadyRead);
return true;
}
void Lifx::setHostAddress(const QHostAddress &address)
void LifxLan::setHostAddress(const QHostAddress &address)
{
m_host = address;
}
void Lifx::setPort(quint16 port)
void LifxLan::setPort(quint16 port)
{
m_port = port;
}
int Lifx::setColorTemperature(uint mirad, uint msFadeTime)
int LifxLan::setColorTemperature(uint mirad, uint msFadeTime)
{
Q_UNUSED(mirad)
Q_UNUSED(msFadeTime)
@ -99,59 +95,48 @@ int Lifx::setColorTemperature(uint mirad, uint msFadeTime)
return requestId;
}
int Lifx::setColor(QColor color, uint msFadeTime)
int LifxLan::setColor(QColor color, uint msFadeTime)
{
Q_UNUSED(color)
Q_UNUSED(msFadeTime)
int requestId = qrand();
Message message;
//TODO create LAN message
sendMessage(message);
return requestId;
}
int Lifx::setBrightness(uint percentage, uint msFadeTime)
int LifxLan::setBrightness(uint percentage, uint msFadeTime)
{
Q_UNUSED(percentage)
Q_UNUSED(msFadeTime)
int requestId = qrand();
Message message;
sendMessage(message);
//TODO create LAN message
return requestId;
}
int Lifx::setPower(bool power, uint msFadeTime)
int LifxLan::setPower(bool power, uint msFadeTime)
{
Q_UNUSED(power)
Q_UNUSED(msFadeTime)
int requestId = qrand();
Message message;
sendMessage(message);
//TODO create LAN message
return requestId;
}
int Lifx::flash()
{
int requestId = qrand();
return requestId;
}
int Lifx::flash15s()
{
int requestId = qrand();
return requestId;
}
void Lifx::sendMessage(const Lifx::Message &message)
void LifxLan::sendMessage(const LifxLan::Message &message)
{
QByteArray header;
// -- FRAME --
// Protocol number: must be 1024 (decimal)
quint16 protocol = 1024;
protocol |= (0x0001 << 4); //Message includes a target address: must be one (1)
protocol |= (0x0001 << 4); //Message includes a target address: must be one (1)
protocol |= (message.frame.Tagged << 5); // Determines usage of the Frame Address target field
protocol &= ~(0x0003); // Message origin indicator: must be zero (0)
protocol &= ~(0x0003); // Message origin indicator: must be zero (0)
header.append(protocol >> 8);
header.append(protocol & 0xff);
@ -188,15 +173,12 @@ void Lifx::sendMessage(const Lifx::Message &message)
header.append(2, '0');
QByteArray fullMessage;
//message.append(header);
//message.append(payload);
//message.append(message.length());
fullMessage = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000");
std::reverse(fullMessage.begin(), fullMessage.end());
//fullMessage = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); // test message - set all lights green
//std::reverse(fullMessage.begin(), fullMessage.end());
m_socket->writeDatagram(fullMessage, m_host, m_port);
}
void Lifx::onStateChanged(QAbstractSocket::SocketState state)
void LifxLan::onStateChanged(QAbstractSocket::SocketState state)
{
switch (state) {
case QAbstractSocket::SocketState::ConnectedState:
@ -212,13 +194,8 @@ void Lifx::onStateChanged(QAbstractSocket::SocketState state)
}
}
void Lifx::onReadyRead()
void LifxLan::onReadyRead()
{
QByteArray data = m_socket->readAll();
qCDebug(dcLifx()) << "Message received" << data;
}
void Lifx::onReconnectTimer()
{
}

View File

@ -28,8 +28,8 @@
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef LIFX_H
#define LIFX_H
#ifndef LIFXLAN_H
#define LIFXLAN_H
#include <QObject>
#include <QTimer>
@ -40,7 +40,7 @@
#include <QColor>
class Lifx : public QObject
class LifxLan : public QObject
{
Q_OBJECT
public:
@ -122,8 +122,8 @@ public:
bool chain;
};
explicit Lifx(const QHostAddress &address, quint16 port = 56700, QObject *parent = nullptr);
~Lifx();
explicit LifxLan(const QHostAddress &address, quint16 port = 56700, QObject *parent = nullptr);
~LifxLan();
bool enable();
void setHostAddress(const QHostAddress &address);
void setPort(quint16 port);
@ -132,8 +132,6 @@ public:
int setColor(QColor color, uint msFadeTime = 500);
int setBrightness(uint percentage, uint msFadeTime = 500);
int setPower(bool power, uint msFadeTime = 500);
int flash();
int flash15s();
private:
quint32 m_clientId = 0;
@ -148,19 +146,9 @@ private:
private slots:
void onStateChanged(QAbstractSocket::SocketState state);
void onReadyRead();
void onReconnectTimer();
signals:
void connectionChanged(bool connected);
void requestExecuted(int requestId, bool success);
//void errorReceived(int code, const QString &message);
//void powerNotificationReceived(bool status);
//void brightnessNotificationReceived(int percentage);
//void colorTemperatureNotificationReceived(int kelvin);
//void rgbNotificationReceived(QRgb rgbColor);
//void hueNotificationReceived(int hueColor);
//void nameNotificationReceived(const QString &name);
//void saturationNotificationReceived(int percentage);
};
#endif // LIFX_H
#endif // LIFXLAN_H

View File

@ -3,7 +3,7 @@
"tagline": "Control LIFX light bulbs.",
"icon": "lifx.png",
"stability": "consumer",
"offline": true,
"offline": false,
"technologies": [
"network"
],

View File

@ -1,6 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de">
<context>
<name>IntegrationPluginLifx</name>
<message>
<source>LIFX server is not reachable.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>This token is invalid.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Lifx</name>
<message>
@ -68,18 +79,6 @@ The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass
<extracomment>The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e})</extracomment>
<translation>Tag und Sonnenaufgang</translation>
</message>
<message>
<source>Host address</source>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {cc0a765b-a753-4e07-a6e5-47e9272c4346})
----------
The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-5111-433a-b5b9-fd9f49d4975c})</extracomment>
<translation>Adresse</translation>
</message>
<message>
<source>Id</source>
<extracomment>The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0})</extracomment>
<translation>ID</translation>
</message>
<message>
<source>LIFX</source>
<extracomment>The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87})
@ -87,13 +86,6 @@ The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-511
The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992})</extracomment>
<translation>LIFX</translation>
</message>
<message>
<source>Port</source>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {d233d9bf-6662-414d-92f6-dd3e267051b5})
----------
The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {44c13745-300c-491f-b617-3a8d53472998})</extracomment>
<translation>Port</translation>
</message>
<message>
<source>Power</source>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0})
@ -148,7 +140,9 @@ The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClas
</message>
<message>
<source>Set effect</source>
<extracomment>The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
<extracomment>The name of the ActionType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb
----------
The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
<translation>Setze Effekt</translation>
</message>
<message>
@ -160,7 +154,13 @@ The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClas
</message>
<message>
<source>Effect</source>
<extracomment>The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343})
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, ActionType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a})
----------
The name of the ParamType (ThingClass: dimmableBulb, EventType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a})
----------
The name of the StateType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb
----------
The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343})
----------
The name of the ParamType (ThingClass: colorBulb, EventType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343})
----------
@ -169,12 +169,16 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass
</message>
<message>
<source>Effect changed</source>
<extracomment>The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
<extracomment>The name of the EventType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb
----------
The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
<translation>Effekt geändert</translation>
</message>
<message>
<source>ID</source>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed})</extracomment>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed})
----------
The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0})</extracomment>
<translation>ID</translation>
</message>
<message>
@ -184,5 +188,46 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass
The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb</extracomment>
<translation>Setze Helligkeit</translation>
</message>
<message>
<source>Connected</source>
<extracomment>The name of the ParamType (ThingClass: lifxAccount, EventType: connected, ID: {3e7b358b-d7de-4db4-8a3a-b9860eae186f})
----------
The name of the StateType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Connected changed</source>
<extracomment>The name of the EventType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>LIFX cloud account</source>
<extracomment>The name of the ThingClass ({387c87f6-3e5b-4d6a-ba4d-372d0efad79f})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Logged in</source>
<extracomment>The name of the ParamType (ThingClass: lifxAccount, EventType: loggedIn, ID: {0db34069-5de0-4233-baec-27f039228524})
----------
The name of the StateType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Logged in changed</source>
<extracomment>The name of the EventType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>User name</source>
<extracomment>The name of the ParamType (ThingClass: lifxAccount, EventType: userDisplayName, ID: {554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2})
----------
The name of the StateType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>User name changed</source>
<extracomment>The name of the EventType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -1,6 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginLifx</name>
<message>
<source>LIFX server is not reachable.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>This token is invalid.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Lifx</name>
<message>
@ -68,18 +79,6 @@ The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass
<extracomment>The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Host address</source>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {cc0a765b-a753-4e07-a6e5-47e9272c4346})
----------
The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-5111-433a-b5b9-fd9f49d4975c})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Id</source>
<extracomment>The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>LIFX</source>
<extracomment>The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87})
@ -87,13 +86,6 @@ The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-511
The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Port</source>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {d233d9bf-6662-414d-92f6-dd3e267051b5})
----------
The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {44c13745-300c-491f-b617-3a8d53472998})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Power</source>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0})
@ -148,7 +140,9 @@ The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClas
</message>
<message>
<source>Set effect</source>
<extracomment>The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
<extracomment>The name of the ActionType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb
----------
The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
@ -160,7 +154,13 @@ The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClas
</message>
<message>
<source>Effect</source>
<extracomment>The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343})
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, ActionType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a})
----------
The name of the ParamType (ThingClass: dimmableBulb, EventType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a})
----------
The name of the StateType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb
----------
The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343})
----------
The name of the ParamType (ThingClass: colorBulb, EventType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343})
----------
@ -169,12 +169,16 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass
</message>
<message>
<source>Effect changed</source>
<extracomment>The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
<extracomment>The name of the EventType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb
----------
The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>ID</source>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed})</extracomment>
<extracomment>The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed})
----------
The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
@ -184,5 +188,46 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass
The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Connected</source>
<extracomment>The name of the ParamType (ThingClass: lifxAccount, EventType: connected, ID: {3e7b358b-d7de-4db4-8a3a-b9860eae186f})
----------
The name of the StateType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Connected changed</source>
<extracomment>The name of the EventType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>LIFX cloud account</source>
<extracomment>The name of the ThingClass ({387c87f6-3e5b-4d6a-ba4d-372d0efad79f})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Logged in</source>
<extracomment>The name of the ParamType (ThingClass: lifxAccount, EventType: loggedIn, ID: {0db34069-5de0-4233-baec-27f039228524})
----------
The name of the StateType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Logged in changed</source>
<extracomment>The name of the EventType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>User name</source>
<extracomment>The name of the ParamType (ThingClass: lifxAccount, EventType: userDisplayName, ID: {554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2})
----------
The name of the StateType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>User name changed</source>
<extracomment>The name of the EventType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>