diff --git a/debian/control b/debian/control
index e1975c7a..87f28ea3 100644
--- a/debian/control
+++ b/debian/control
@@ -432,6 +432,21 @@ Description: nymea.io plugin for lgsmarttv
This package will install the nymea.io plugin for lgsmarttv
+Package: nymea-plugin-lifx
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ nymea-plugins-translations,
+Description: nymea.io plugin for lifx
+ The nymea daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install the nymea.io plugin for lifx
+
+
Package: nymea-plugin-mailnotification
Architecture: any
Depends: ${shlibs:Depends},
@@ -1045,6 +1060,7 @@ Depends: nymea-plugin-anel,
nymea-plugin-genericthings,
nymea-plugin-kodi,
nymea-plugin-lgsmarttv,
+ nymea-plugin-lifx,
nymea-plugin-mailnotification,
nymea-plugin-texasinstruments,
nymea-plugin-nanoleaf,
diff --git a/debian/nymea-plugin-lifx.install.in b/debian/nymea-plugin-lifx.install.in
new file mode 100644
index 00000000..d8f7c34a
--- /dev/null
+++ b/debian/nymea-plugin-lifx.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginlifx.so
diff --git a/lifx/README.md b/lifx/README.md
new file mode 100644
index 00000000..efe39ff9
--- /dev/null
+++ b/lifx/README.md
@@ -0,0 +1,18 @@
+# Lifx
+
+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/
diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp
new file mode 100644
index 00000000..6f39f9e5
--- /dev/null
+++ b/lifx/integrationpluginlifx.cpp
@@ -0,0 +1,609 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project 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 project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "integrationpluginlifx.h"
+
+#include "integrations/integrationplugin.h"
+#include "types/param.h"
+#include "plugininfo.h"
+#include "platform/platformzeroconfcontroller.h"
+#include "network/zeroconf/zeroconfservicebrowser.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+IntegrationPluginLifx::IntegrationPluginLifx()
+{
+
+}
+
+void IntegrationPluginLifx::init()
+{
+ m_connectedStateTypeIds.insert(colorBulbThingClassId, colorBulbConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(lifxAccountThingClassId, lifxAccountConnectedStateTypeId);
+
+ m_powerStateTypeIds.insert(colorBulbThingClassId, colorBulbPowerStateTypeId);
+ m_powerStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbPowerStateTypeId);
+
+ m_brightnessStateTypeIds.insert(colorBulbThingClassId, colorBulbBrightnessStateTypeId);
+ m_brightnessStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbBrightnessStateTypeId);
+
+ m_colorTemperatureStateTypeIds.insert(colorBulbThingClassId, colorBulbColorTemperatureStateTypeId);
+ m_colorTemperatureStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbColorTemperatureStateTypeId);
+
+ m_idParamTypeIds.insert(colorBulbThingClassId, colorBulbThingIdParamTypeId);
+ m_idParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingIdParamTypeId);
+
+ m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_hap._tcp"); // discovers all homekit devices
+
+ // 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();
+}
+
+void IntegrationPluginLifx::startPairing(ThingPairingInfo *info)
+{
+ QUrl url("https://api.lifx.com/v1");
+ QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
+ connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
+ connect(reply, &QNetworkReply::finished, info, [reply, info] {
+
+ if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError) {
+ info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("LIFX server is not reachable."));
+ } else {
+ info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter your user name and token. Get the token from https://cloud.lifx.com/settings"));
+ }
+ });
+}
+
+void IntegrationPluginLifx::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
+{
+ QNetworkRequest request;
+ request.setUrl(QUrl("https://api.lifx.com/v1/lights/all"));
+ request.setRawHeader("Authorization","Bearer "+secret.toUtf8());
+ QNetworkReply *reply = m_networkManager->get(request);
+ connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
+ connect(reply, &QNetworkReply::finished, info, [info, reply, secret, username, this] {
+
+ int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ // check HTTP status code
+ if (status != 200) {
+ // Error setting up device with invalid token
+ info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("The token is invalid."));
+ return;
+ }
+ qCDebug(dcLifx()) << "Confirm pairing successfull";
+ pluginStorage()->beginGroup(info->thingId().toString());
+ pluginStorage()->setValue("username", username);
+ pluginStorage()->setValue("token", secret);
+ pluginStorage()->endGroup();
+
+ info->finish(Thing::ThingErrorNoError);
+ });
+}
+
+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 descriptors;
+ foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) {
+ if (!avahiEntry.name().contains("lifx", Qt::CaseSensitivity::CaseInsensitive)) {
+ continue;
+ }
+
+ QString id;
+ QString model;
+ foreach (const QString &txt, avahiEntry.txt()) {
+ //qCDebug(dcLifx()) << "txt entry. Key:" << txt.split("=").first() << "value:" << txt.split("=").last();
+ if (txt.startsWith("id", Qt::CaseSensitivity::CaseInsensitive)) {
+ id = txt.split("=").last();
+ } else if (txt.startsWith("md")) {
+ model = txt.split("=").last();
+ }
+ }
+ if (descriptors.contains(id)) {
+ // Might appear multiple times, IPv4 and IPv6
+ continue;
+ }
+ qCDebug(dcLifx()) << "Found LIFX device" << model << "ID" << id;
+ ThingDescriptor descriptor(info->thingClassId(), model, avahiEntry.name() + " (" + avahiEntry.hostAddress().toString() + ")");
+ ParamList params;
+ params << Param(m_idParamTypeIds.value(info->thingClassId()), id);
+ params << Param(m_hostAddressParamTypeIds.value(info->thingClassId()), avahiEntry.hostAddress().toString());
+ params << Param(m_portParamTypeIds.value(info->thingClassId()), avahiEntry.port());
+ descriptor.setParams(params);
+
+ Things existing = myThings().filterByParam(m_idParamTypeIds.value(info->thingClassId()), id);
+ if (existing.count() > 0) {
+ descriptor.setThingId(existing.first()->id());
+ }
+ descriptors.insert(id, descriptor);
+ }
+ info->addThingDescriptors(descriptors.values());
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8());
+ }
+}
+
+void IntegrationPluginLifx::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ if (thing->thingClassId() == colorBulbThingClassId || thing->thingClassId() == dimmableBulbThingClassId) {
+ 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) {
+
+ pluginStorage()->beginGroup(thing->id().toString());
+ QByteArray token = pluginStorage()->value("token").toByteArray();
+ QByteArray username = pluginStorage()->value("username").toByteArray();
+ pluginStorage()->endGroup();
+
+ if (token.isEmpty()) {
+ qCWarning(dcLifx()) << "Lifx setup, token is not stored";
+ return info->finish(Thing::ThingErrorAuthenticationFailure);
+ }
+ thing->setStateValue(lifxAccountUserDisplayNameStateTypeId, username);
+ LifxCloud *lifxCloud = new LifxCloud(hardwareManager()->networkManager(), this);
+ m_asyncCloudSetups.insert(lifxCloud, info);
+ connect(info, &ThingSetupInfo::aborted, info, [lifxCloud, this] {
+ m_asyncCloudSetups.remove(lifxCloud);
+ lifxCloud->deleteLater();
+ });
+ connect(lifxCloud, &LifxCloud::lightsListReceived, this, &IntegrationPluginLifx::onLifxCloudLightsListReceived);
+ connect(lifxCloud, &LifxCloud::scenesListReceived, this, &IntegrationPluginLifx::onLifxCloudScenesListReceived);
+ connect(lifxCloud, &LifxCloud::requestExecuted, this, &IntegrationPluginLifx::onLifxCloudRequestExecuted);
+ connect(lifxCloud, &LifxCloud::connectionChanged, this, &IntegrationPluginLifx::onLifxCloudConnectionChanged);
+ connect(lifxCloud, &LifxCloud::authenticationChanged, this, &IntegrationPluginLifx::onLifxCloudAuthenticationChanged);
+ lifxCloud->setAuthorizationToken(token);
+ lifxCloud->listLights();
+ QTimer::singleShot(2000, info, [this, info] {
+ setupThing(info);
+ });
+ } else {
+ Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
+ }
+}
+
+void IntegrationPluginLifx::postSetupThing(Thing *thing)
+{
+ if (!m_pluginTimer) {
+ m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(15);
+ connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() {
+ foreach (LifxLan *lifx, m_lifxLanConnections) {
+ Q_UNUSED(lifx)
+ //TODO update LAN device states
+ }
+ foreach (LifxCloud *lifx, m_lifxCloudConnections) {
+ lifx->listLights();
+ }
+ });
+ }
+
+ if (thing->thingClassId() == lifxAccountThingClassId) {
+ thing->setStateValue(lifxAccountConnectedStateTypeId, true);
+ thing->setStateValue(lifxAccountLoggedInStateTypeId, true);
+ }
+}
+
+void IntegrationPluginLifx::executeAction(ThingActionInfo *info)
+{
+ Thing *thing = info->thing();
+ Action action = info->action();
+ bool cloudDevice = false;
+ LifxLan *lifx = nullptr;
+ LifxCloud *lifxCloud = nullptr;
+
+ if (m_lifxLanConnections.contains(thing)) {
+ // Local connection first
+ lifx = m_lifxLanConnections.value(thing);
+ } else if (m_lifxCloudConnections.contains(myThings().findById(thing->parentId()))) {
+ lifxCloud = m_lifxCloudConnections.value(myThings().findById(thing->parentId()));
+ cloudDevice = true;
+ } else {
+ qCWarning(dcLifx()) << "Could not find any LIFX connection for thing" << thing->name();
+ return info->finish(Thing::ThingErrorHardwareFailure);
+ }
+
+ if (thing->thingClassId() == colorBulbThingClassId) {
+ QByteArray lightId = thing->paramValue(colorBulbThingIdParamTypeId).toByteArray();
+ if (action.actionTypeId() == colorBulbPowerActionTypeId) {
+ bool power = action.param(colorBulbPowerActionPowerParamTypeId).value().toBool();
+ int requestId;
+ if (cloudDevice) {
+ requestId = lifxCloud->setPower(lightId, power);
+ } else {
+ requestId = lifx->setPower(power);
+ }
+ connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
+ m_asyncActions.insert(requestId, info);
+
+ } else if (action.actionTypeId() == colorBulbBrightnessActionTypeId) {
+
+ 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) {
+ requestId = lifxCloud->setBrightnesss(lightId, brightness);
+ } else {
+ requestId = lifx->setBrightness(brightness);
+ }
+ connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
+ m_asyncActions.insert(requestId, info);
+ } else if (action.actionTypeId() == colorBulbColorActionColorParamTypeId) {
+ QRgb color = QColor(action.param(colorBulbColorActionColorParamTypeId).value().toString()).rgba();
+ if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){
+ if (cloudDevice) {
+ lifxCloud->setPower(lightId, true);
+ } else {
+ lifx->setPower(true);
+ }
+ }
+ int requestId;
+ if (cloudDevice) {
+ requestId = lifxCloud->setColor(lightId, color);
+ } else {
+ requestId = lifx->setColor(color);
+ }
+ connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
+ m_asyncActions.insert(requestId, info);
+ } else if (action.actionTypeId() == colorBulbColorTemperatureActionTypeId) {
+ int colorTemperature = 6500 - (action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toUInt() * 8); //range 2500 to 6500 kelvin
+ if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){
+ if (cloudDevice) {
+ lifxCloud->setPower(lightId, true);
+ } else {
+ lifx->setPower(true);
+ }
+ }
+ int requestId;
+ if (cloudDevice) {
+ requestId = lifxCloud->setColorTemperature(lightId, colorTemperature);
+ } else {
+ requestId = lifx->setColorTemperature(colorTemperature);
+ }
+ 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(colorBulbEffectActionEffectParamTypeId).value().toString();
+ int requestId;
+ LifxCloud::Effect effect = LifxCloud::EffectNone;
+ if (effectString == "None") {
+ effect = LifxCloud::EffectNone;
+ } else if (effectString == "Breathe") {
+ effect = LifxCloud::EffectBreathe;
+ } else if (effectString == "Pulse") {
+ effect = LifxCloud::EffectPulse;
+ }
+ if (cloudDevice) {
+ //QColor color = QColor(thing->stateValue(colorBulbColorStateTypeId).toString());
+ requestId = lifxCloud->setEffect(lightId, effect, "#FFFFFF");
+ } 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());
+ }
+ } else if (thing->thingClassId() == dimmableBulbThingClassId) {
+ QByteArray lightId = thing->paramValue(dimmableBulbThingIdParamTypeId).toByteArray();
+ if (action.actionTypeId() == dimmableBulbPowerActionTypeId) {
+ bool power = action.param(dimmableBulbPowerActionPowerParamTypeId).value().toBool();
+ int requestId;
+ if (cloudDevice) {
+ requestId = lifxCloud->setPower(lightId, power);
+ } else {
+ requestId = lifx->setPower(power);
+ }
+ 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(colorBulbPowerStateTypeId).toBool()){
+ if (cloudDevice) {
+ lifxCloud->setPower(lightId, true);
+ } else {
+ lifx->setPower(true);
+ }
+ }
+ int requestId;
+ if (cloudDevice) {
+ requestId = lifxCloud->setBrightnesss(lightId, brightness);
+ } else {
+ requestId = lifx->setBrightness(brightness);
+ }
+ 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());
+ }
+ } else {
+ Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
+ }
+}
+
+void IntegrationPluginLifx::thingRemoved(Thing *thing)
+{
+ if (thing->thingClassId() == colorBulbThingClassId || thing->thingClassId() == dimmableBulbThingClassId) {
+ 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();
+ }
+
+ if (myThings().isEmpty()) {
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
+ m_pluginTimer = nullptr;
+ }
+}
+
+void IntegrationPluginLifx::browseThing(BrowseResult *result)
+{
+ Thing *thing = result->thing();
+ LifxCloud *lifxCloud = m_lifxCloudConnections.value(thing);
+ if (!lifxCloud)
+ return;
+
+ lifxCloud->listScenes();
+ m_asyncBrowseResults.insert(lifxCloud, result);
+ connect(result, &BrowseResult::aborted, this, [lifxCloud, this]{m_asyncBrowseResults.remove(lifxCloud);});
+}
+
+void IntegrationPluginLifx::browserItem(BrowserItemResult *result)
+{
+ Q_UNUSED(result)
+ qCDebug(dcLifx()) << "BrowserItem called";
+}
+
+void IntegrationPluginLifx::executeBrowserItem(BrowserActionInfo *info)
+{
+ Thing *thing = info->thing();
+ LifxCloud *lifxCloud = m_lifxCloudConnections.value(thing);
+ int requestId = lifxCloud->activateScene(info->browserAction().itemId());
+ m_asyncBrowserItem.insert(requestId, info);
+ connect(info, &BrowserActionInfo::aborted, this, [requestId, this] {m_asyncBrowserItem.remove(requestId);});
+}
+
+void IntegrationPluginLifx::onLifxLanConnectionChanged(bool connected)
+{
+ Q_UNUSED(connected)
+ LifxLan *lifx = static_cast(sender());
+ Thing *thing = m_lifxLanConnections.key(lifx);
+ if (!thing)
+ return;
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), connected);
+}
+
+void IntegrationPluginLifx::onLifxLanRequestExecuted(int requestId, bool success)
+{
+ if (m_asyncActions.contains(requestId)) {
+ ThingActionInfo *info = m_asyncActions.take(requestId);
+ if (success) {
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ } else if (m_asyncBrowserItem.contains(requestId)) {
+ BrowserActionInfo *info = m_asyncBrowserItem.take(requestId);
+ if (success) {
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ }
+}
+
+void IntegrationPluginLifx::onLifxCloudConnectionChanged(bool connected)
+{
+ LifxCloud *lifxCloud = static_cast(sender());
+ Thing *accountThing = m_lifxCloudConnections.key(lifxCloud);
+ if (!accountThing)
+ return;
+ accountThing->setStateValue(m_connectedStateTypeIds.value(accountThing->thingClassId()), connected);
+
+ foreach (Thing *thing, myThings().filterByParentId(accountThing->id())) {
+ if (!connected)
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), connected);
+ }
+}
+
+void IntegrationPluginLifx::onLifxCloudAuthenticationChanged(bool authenticated)
+{
+ LifxCloud *lifxCloud = static_cast(sender());
+ Thing *accountThing = m_lifxCloudConnections.key(lifxCloud);
+ if (!accountThing)
+ return;
+ accountThing->setStateValue(lifxAccountLoggedInStateTypeId, authenticated);
+}
+
+void IntegrationPluginLifx::onLifxCloudLightsListReceived(const QList &lights)
+{
+ LifxCloud *lifxCloud = static_cast(sender());
+ if (m_asyncCloudSetups.contains(lifxCloud)) {
+ ThingSetupInfo *info = m_asyncCloudSetups.take(lifxCloud);
+ m_lifxCloudConnections.insert(info->thing(), lifxCloud);
+ info->finish(Thing::ThingErrorNoError);
+ }
+
+ ThingDescriptors thingDescriptors;
+ Q_FOREACH(LifxCloud::Light light, lights) {
+ Thing *parentThing = m_lifxCloudConnections.key(lifxCloud);
+ if (!parentThing) {
+ qCWarning(dcLifx()) << "Could not find thing to cloud connection";
+ return;
+ }
+ ThingClassId thingClassId;
+ if (light.product.capabilities.color) {
+ thingClassId = colorBulbThingClassId;
+ } else if (light.product.capabilities.colorTemperature) {
+ thingClassId = dimmableBulbThingClassId;
+ } else {
+ qCWarning(dcLifx()) << "LIFX product is not supported";
+ }
+ qCDebug(dcLifx()) << "Light product:" << light.id << light.uuid << light.label << light.product.identifier;
+ 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*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);
+ }
+ thingDescriptor.setThingId(thing->id());
+ break;
+ }
+ ParamList params;
+ params << Param(m_idParamTypeIds.value(thingDescriptor.thingClassId()), light.id);
+ params << Param(m_hostAddressParamTypeIds.value(thingDescriptor.thingClassId()), "-");
+ params << Param(m_portParamTypeIds.value(thingDescriptor.thingClassId()), 0);
+ thingDescriptor.setParams(params);
+ thingDescriptors.append(thingDescriptor);
+ }
+ if (!thingDescriptors.isEmpty())
+ autoThingsAppeared(thingDescriptors);
+}
+
+void IntegrationPluginLifx::onLifxCloudRequestExecuted(int requestId, bool success)
+{
+ if (m_asyncActions.contains(requestId)) {
+ ThingActionInfo *info = m_asyncActions.take(requestId);
+ if (!info) {
+ return;
+ }
+ if (success) {
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ } else if (m_asyncBrowserItem.contains(requestId)) {
+ BrowserActionInfo *info = m_asyncBrowserItem.value(requestId);
+ if (!info) {
+ return;
+ }
+ if (success) {
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ }
+}
+
+void IntegrationPluginLifx::onLifxCloudScenesListReceived(const QList &scenes)
+{
+ LifxCloud *lifxCloud = static_cast(sender());
+ Thing *thing = m_lifxCloudConnections.key(lifxCloud);
+ if (!thing)
+ return;
+ qCDebug(dcLifx()) << "Scene list received, count: " << scenes.length();
+
+ if (m_asyncBrowseResults.contains(lifxCloud)) {
+ BrowseResult *result = m_asyncBrowseResults.take(lifxCloud);
+ foreach (LifxCloud::Scene scene, scenes) {
+ BrowserItem item;
+ item.setId(scene.id);
+ item.setBrowsable(false);
+ item.setExecutable(true);
+ item.setDisplayName(scene.name);
+ item.setDisabled(false);
+ result->addItem(item);
+ }
+ result->finish(Thing::ThingErrorNoError);
+ }
+}
diff --git a/lifx/integrationpluginlifx.h b/lifx/integrationpluginlifx.h
new file mode 100644
index 00000000..2b440d5d
--- /dev/null
+++ b/lifx/integrationpluginlifx.h
@@ -0,0 +1,103 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project 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 project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINLIFX_H
+#define INTEGRATIONPLUGINLIFX_H
+
+#include "integrations/integrationplugin.h"
+#include "plugintimer.h"
+#include "lifxlan.h"
+#include "lifxcloud.h"
+
+#include "network/networkaccessmanager.h"
+#include "network/zeroconf/zeroconfservicebrowser.h"
+#include "network/zeroconf/zeroconfserviceentry.h"
+
+#include
+
+class IntegrationPluginLifx : public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginlifx.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginLifx();
+
+ void init() override;
+ void startPairing(ThingPairingInfo *info) override;
+ void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override;
+
+ void discoverThings(ThingDiscoveryInfo *info) override;
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void executeAction(ThingActionInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+ void browseThing(BrowseResult *result) override;
+ void browserItem(BrowserItemResult *result) override;
+ void executeBrowserItem(BrowserActionInfo *info) override;
+
+private:
+ NetworkAccessManager *m_networkManager = nullptr;
+ PluginTimer *m_pluginTimer = nullptr;
+ QHash m_asyncCloudSetups;
+ QHash m_asyncActions;
+ QHash m_lifxLanConnections;
+ QHash m_lifxCloudConnections;
+ QHash m_asyncBrowseResults;
+ QHash m_asyncBrowserItem;
+
+ ZeroConfServiceBrowser *m_serviceBrowser = nullptr;
+
+ QHash m_connectedStateTypeIds;
+ QHash m_powerStateTypeIds;
+ QHash m_brightnessStateTypeIds;
+ QHash m_colorTemperatureStateTypeIds;
+ QHash m_hostAddressParamTypeIds;
+ QHash m_portParamTypeIds;
+ QHash m_idParamTypeIds;
+
+ QHash m_pendingBrightnessAction;
+ QHash m_lifxProducts;
+
+private slots:
+ void onLifxLanConnectionChanged(bool connected);
+ void onLifxLanRequestExecuted(int requestId, bool success);
+
+ void onLifxCloudConnectionChanged(bool connected);
+ void onLifxCloudAuthenticationChanged(bool authenticated);
+ void onLifxCloudRequestExecuted(int requestId, bool success);
+ void onLifxCloudLightsListReceived(const QList &lights);
+ void onLifxCloudScenesListReceived(const QList &scenes);
+};
+
+#endif // INTEGRATIONPLUGIN_LIFX_H
diff --git a/lifx/integrationpluginlifx.json b/lifx/integrationpluginlifx.json
new file mode 100644
index 00000000..b5e6c52c
--- /dev/null
+++ b/lifx/integrationpluginlifx.json
@@ -0,0 +1,203 @@
+{
+ "displayName": "LIFX",
+ "name": "Lifx",
+ "id": "4e00ee30-79e2-447b-8dcc-c34470f41992",
+ "vendors": [
+ {
+ "name": "lifx",
+ "displayName": "LIFX",
+ "id": "e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87",
+ "thingClasses": [
+ {
+ "id": "387c87f6-3e5b-4d6a-ba4d-372d0efad79f",
+ "name": "lifxAccount",
+ "displayName": "LIFX cloud account",
+ "createMethods": ["user"],
+ "interfaces": ["account"],
+ "setupMethod": "userandpassword",
+ "browsable": true,
+ "paramTypes": [
+ ],
+ "stateTypes": [
+ {
+ "id": "0db34069-5de0-4233-baec-27f039228524",
+ "name": "loggedIn",
+ "displayName": "Logged in",
+ "displayNameEvent": "Logged in changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2",
+ "name": "userDisplayName",
+ "displayName": "User name",
+ "displayNameEvent": "User name changed",
+ "type": "QString",
+ "defaultValue": "-"
+ },
+ {
+ "id": "3e7b358b-d7de-4db4-8a3a-b9860eae186f",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "defaultValue": false,
+ "type": "bool",
+ "cached": false
+ }
+ ]
+ },
+ {
+ "id": "12907c9c-e7f0-47f2-bd58-39d52ffdf24e",
+ "name": "colorBulb",
+ "displayName": "Color",
+ "createMethods": ["auto"],
+ "interfaces": ["colorlight", "connectable"],
+ "paramTypes": [
+ {
+ "id": "976ecea0-ac25-47d4-9dc5-362962ddb6c0",
+ "name": "id",
+ "displayName": "ID",
+ "type" : "QString",
+ "readOnly": true
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "dc4c1640-90f3-4fe0-af9b-db7fa105f18a",
+ "name": "connected",
+ "displayName": "Reachable",
+ "displayNameEvent": "Reachable changed",
+ "defaultValue": false,
+ "type": "bool",
+ "cached": false
+ },
+ {
+ "id": "12de3f8f-2454-4057-aa12-9290296fdbdd",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "dd7d7e70-5552-4531-8789-2d0f750488be",
+ "name": "colorTemperature",
+ "displayName": "Color temperature",
+ "displayNameEvent": "Color temperature changed",
+ "displayNameAction": "Set color temperature",
+ "type": "int",
+ "unit": "Mired",
+ "defaultValue": 170,
+ "minValue": 153,
+ "maxValue": 500,
+ "writable": true
+ },
+ {
+ "id": "a47d8164-5023-4ffb-8298-73293e93e7f6",
+ "name": "color",
+ "displayName": "Color",
+ "displayNameEvent": "Color changed",
+ "displayNameAction": "Set color",
+ "type": "QColor",
+ "defaultValue": "#000000",
+ "writable": true
+ },
+ {
+ "id": "8bd20350-0e79-45dc-b68a-84da99356863",
+ "name": "brightness",
+ "displayName": "Brightness",
+ "displayNameEvent": "Brightness changed",
+ "displayNameAction": "Set brightness",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100,
+ "writable": true
+ },
+ {
+ "id": "65f88396-2958-480e-b0be-c4695400a343",
+ "name": "effect",
+ "displayName": "Effect",
+ "displayNameEvent": "Effect changed",
+ "displayNameAction": "Set effect",
+ "type": "QString",
+ "defaultValue": "None",
+ "possibleValues": [
+ "None",
+ "Breathe",
+ "Pulse"
+ ],
+ "writable": true
+ }
+ ]
+ },
+ {
+ "id": "a5b02af8-7c97-4a78-9c78-bafee7407b5e",
+ "name": "dimmableBulb",
+ "displayName": "Day and Dusk",
+ "createMethods": ["auto"],
+ "interfaces": ["colortemperaturelight", "connectable"],
+ "paramTypes": [
+ {
+ "id": "f157a97b-3fe5-4d9e-b5e3-5636f80d46ed",
+ "name": "id",
+ "displayName": "ID",
+ "type" : "QString",
+ "readOnly": true
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "d33f98ef-5e0f-464c-afed-88b95cc701cd",
+ "name": "connected",
+ "displayName": "Reachable",
+ "displayNameEvent": "Reachable changed",
+ "defaultValue": false,
+ "type": "bool"
+ },
+ {
+ "id": "9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "a0a1bdcc-2761-4d90-85d1-5ce887546611",
+ "name": "brightness",
+ "displayName": "Brightness",
+ "displayNameEvent": "Brightness changed",
+ "displayNameAction": "Set brightness",
+ "type": "int",
+ "unit": "Percentage",
+ "defaultValue": 0,
+ "minValue": 0,
+ "maxValue": 100,
+ "writable": true
+ },
+ {
+ "id": "95797dee-b836-4047-98d5-afbbce4f8c42",
+ "name": "colorTemperature",
+ "displayName": "Color temperature",
+ "displayNameEvent": "Color temperature changed",
+ "displayNameAction": "Set color temperature",
+ "type": "int",
+ "unit": "Mired",
+ "defaultValue": 170,
+ "minValue": 153,
+ "maxValue": 500,
+ "writable": true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/lifx/lifx.png b/lifx/lifx.png
new file mode 100644
index 00000000..89fbeb25
Binary files /dev/null and b/lifx/lifx.png differ
diff --git a/lifx/lifx.pro b/lifx/lifx.pro
new file mode 100644
index 00000000..20671af6
--- /dev/null
+++ b/lifx/lifx.pro
@@ -0,0 +1,14 @@
+include(../plugins.pri)
+
+QT += network
+
+SOURCES += \
+ integrationpluginlifx.cpp \
+ lifxcloud.cpp \
+ lifxlan.cpp \
+
+HEADERS += \
+ integrationpluginlifx.h \
+ lifxcloud.h \
+ lifxlan.h \
+
diff --git a/lifx/lifxcloud.cpp b/lifx/lifxcloud.cpp
new file mode 100644
index 00000000..1aabda98
--- /dev/null
+++ b/lifx/lifxcloud.cpp
@@ -0,0 +1,373 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project 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 project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "lifxcloud.h"
+#include "extern-plugininfo.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+LifxCloud::LifxCloud(NetworkAccessManager *networkManager, QObject *parent) :
+ QObject(parent),
+ m_networkManager(networkManager)
+{
+
+}
+
+void LifxCloud::setAuthorizationToken(const QByteArray &token)
+{
+ m_authorizationToken = token;
+}
+
+bool LifxCloud::cloudAuthenticated()
+{
+ return m_authenticated;
+}
+
+bool LifxCloud::cloudConnected()
+{
+ return m_connected;
+}
+
+void LifxCloud::listLights()
+{
+ if (m_authorizationToken.isEmpty()) {
+ qCWarning(dcLifx()) << "Authorization token is not set";
+ return;
+ }
+ QNetworkRequest request;
+ request.setUrl(QUrl("https://api.lifx.com/v1/lights/all"));
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
+ request.setRawHeader("Authorization","Bearer "+m_authorizationToken);
+
+ QNetworkReply *reply = m_networkManager->get(request);
+ connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
+ connect(reply, &QNetworkReply::finished, this, [reply, this] {
+ if(!checkHttpStatusCode(reply)) {
+ return;
+ }
+ QByteArray rawData = reply->readAll();
+
+ QJsonDocument data; QJsonParseError error;
+ data = QJsonDocument::fromJson(rawData, &error);
+ if (error.error != QJsonParseError::NoError) {
+ qDebug(dcLifx()) << "List lights: Received invalide JSON object" << error.errorString();
+ return;
+ }
+
+ if (!data.isArray())
+ qCWarning(dcLifx()) << "Data is not an array";
+
+ QJsonArray array = data.array();
+ QList descriptors;
+ foreach (QJsonValue jsonValue, array) {
+
+ QJsonObject object = jsonValue.toObject();
+ qCDebug(dcLifx()) << "Light object:" << object;
+ 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();
+ int hue = object["hue"].toObject().value("saturation").toDouble();
+ int saturation = object["color"].toObject().value("saturation").toDouble();
+ light.colorTemperature = object["color"].toObject().value("kelvin").toDouble();
+ light.color = QColor::fromHsv(hue, saturation, light.brightness);
+ Group group;
+ group.name = object["group"].toObject().value("name").toString();
+ group.id = object["group"].toObject().value("id").toString().toUtf8();
+ light.group = group;
+ Location location;
+ location.name = object["location"].toObject().value("name").toString();
+ location.id = object["location"].toObject().value("id").toString().toUtf8();
+ light.location = location;
+ Product product;
+ QJsonObject productObject = object["product"].toObject();
+ product.name = productObject["name"].toString();
+ product.identifier = productObject["identifier"].toString();
+ product.manufacturer = productObject["manufacturer"].toString();
+ product.secondsSinceLastSeen = productObject["seconds_since_seen"].toInt();
+ Capabilities capabilities;
+ QJsonObject capabilitiesObject = productObject["capabilities"].toObject();
+ capabilities.color = capabilitiesObject["has_color"].toBool();
+ capabilities.colorTemperature = capabilitiesObject["has_variable_color_temp"].toBool();
+ capabilities.ir = capabilitiesObject["has_ir"].toBool();
+ capabilities.chain = capabilitiesObject["has_chain"].toBool();
+ capabilities.multizone = capabilitiesObject["has_multizone"].toBool();
+ capabilities.minKelvin= capabilitiesObject["min_kelvin"].toInt();
+ capabilities.maxKelvin = capabilitiesObject["max_kelvin"].toInt();
+ product.capabilities = capabilities;
+ light.product = product;
+ descriptors.append(light);
+ }
+ emit lightsListReceived(descriptors);
+ });
+}
+
+void LifxCloud::listScenes()
+{
+ if (m_authorizationToken.isEmpty()) {
+ qCWarning(dcLifx()) << "Authorization token is not set";
+ return;
+ }
+ QNetworkRequest request;
+ request.setUrl(QUrl("https://api.lifx.com/v1/scenes"));
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
+ request.setRawHeader("Authorization","Bearer "+m_authorizationToken);
+
+ QNetworkReply *reply = m_networkManager->get(request);
+ connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
+ connect(reply, &QNetworkReply::finished, this, [reply, this] {
+ if(!checkHttpStatusCode(reply)) {
+ return;
+ }
+ QByteArray rawData = reply->readAll();
+ qCDebug(dcLifx()) << "Got list scenes reply" << rawData;
+ QJsonDocument data; QJsonParseError error;
+ data = QJsonDocument::fromJson(rawData, &error);
+ if (error.error != QJsonParseError::NoError) {
+ qDebug(dcLifx()) << "List scenes: Received invalide JSON object" << error.errorString();
+ return;
+ }
+ if (!data.isArray())
+ qCWarning(dcLifx()) << "Data is not an array";
+
+ QJsonArray array = data.array();
+ QList scenes;
+ foreach (QJsonValue value, array) {
+ Scene scene;
+ scene.id = value.toObject().value("uuid").toString().toUtf8();
+ scene.name = value.toObject().value("name").toString();
+ scenes.append(scene);
+ }
+ emit scenesListReceived(scenes);
+ });
+}
+
+int LifxCloud::setPower(const QString &lightId, bool power, int duration)
+{
+ return setState("id:"+lightId, StatePower, power, duration);
+}
+
+int LifxCloud::setBrightnesss(const QString &lightId, int brightness, int duration)
+{
+ return setState("id:"+lightId, StateBrightness, brightness/100.00, duration);
+}
+
+int LifxCloud::setColor(const QString &lightId, QColor color, int duration)
+{
+ return setState("id:"+lightId, StateColor, color.name(), duration);
+}
+
+int LifxCloud::setColorTemperature(const QString &lightId, int kelvin, int duration)
+{
+ return setState("id:"+lightId, StateColorTemperature, kelvin, duration);
+}
+
+int LifxCloud::setInfrared(const QString &lightId, int infrared, int duration)
+{
+ return setState("id:"+lightId, StateColor, infrared/100.00, duration);
+}
+
+int LifxCloud::activateScene(const QString &sceneId)
+{
+ if (m_authorizationToken.isEmpty()) {
+ qCWarning(dcLifx()) << "Authorization token is not set";
+ return -1;
+ }
+ int requestId = qrand();
+
+ QNetworkRequest request;
+ request.setUrl(QUrl(QString("https://api.lifx.com/v1/scenes/scene_id:%1/activate").arg(sceneId)));
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
+ request.setRawHeader("Authorization","Bearer "+m_authorizationToken);
+ QNetworkReply *reply = m_networkManager->put(request, "");
+ connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
+ connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
+ 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;
+ QUrlQuery params;
+ 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)));
+ params.addQueryItem("color", color.name().trimmed());
+ params.addQueryItem("period", "2");
+ params.addQueryItem("cycles", "3");
+ 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)));
+ params.addQueryItem("color", color.name().trimmed());
+ params.addQueryItem("period", "2");
+ params.addQueryItem("cycles", "3");
+ break;
+ }
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded.");
+ request.setRawHeader("Authorization","Bearer "+m_authorizationToken);
+ qCDebug(dcLifx()) << "Set effect request" << request.url() << params.toString().toUtf8();
+
+ QNetworkReply *reply = m_networkManager->post(request, params.toString().toUtf8());
+ connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
+ connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
+
+ QByteArray rawData = reply->readAll();
+ qCDebug(dcLifx()) << "Got set effect 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()) {
+ qCWarning(dcLifx()) << "Authorization token is not set";
+ 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");
+ request.setRawHeader("Authorization","Bearer "+m_authorizationToken);
+ QJsonDocument doc;
+ QJsonObject payload;
+ payload["duration"] = duration;
+ payload["fast"] = false;
+ switch (state) {
+ case StatePower:
+ if (stateValue.toBool())
+ payload["power"] = "on";
+ else
+ 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);
+ 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, duration,reply, this] {
+
+ QByteArray rawData = reply->readAll();
+ qCDebug(dcLifx()) << "Got set state reply" << rawData;
+ 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;
+}
diff --git a/lifx/lifxcloud.h b/lifx/lifxcloud.h
new file mode 100644
index 00000000..df95369b
--- /dev/null
+++ b/lifx/lifxcloud.h
@@ -0,0 +1,140 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project 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 project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef LIFXCLOUD_H
+#define LIFXCLOUD_H
+
+#include
+#include
+#include
+#include "network/networkaccessmanager.h"
+
+class LifxCloud : public QObject
+{
+ Q_OBJECT
+public:
+ enum State {
+ StatePower,
+ StateBrightness,
+ StateColor,
+ StateColorTemperature,
+ StateInfrared
+ };
+
+ enum Effect {
+ EffectNone,
+ EffectBreathe,
+ EffectMove,
+ EffectMorph,
+ EffectFlame,
+ EffectPulse
+ };
+
+ struct Group {
+ QByteArray id;
+ QString name;
+ };
+
+ struct Location {
+ QByteArray id;
+ QString name;
+ };
+
+ struct Scene {
+ QByteArray id;
+ QString name;
+ };
+
+ struct Capabilities {
+ bool color;
+ bool colorTemperature;
+ bool ir;
+ bool chain;
+ bool multizone;
+ int minKelvin;
+ int maxKelvin;
+ };
+
+ struct Product {
+ QString name;
+ QString identifier;
+ QString manufacturer;
+ uint secondsSinceLastSeen;
+ Capabilities capabilities;
+ };
+
+ struct Light {
+ QByteArray id;
+ QByteArray uuid;
+ QString label;
+ bool connected;
+ bool power;
+ QColor color;
+ int colorTemperature;
+ double brightness;
+ Group group;
+ Location location;
+ Product product;
+ };
+
+ explicit LifxCloud(NetworkAccessManager *networkManager, QObject *parent = nullptr);
+ void setAuthorizationToken(const QByteArray &token);
+ bool cloudAuthenticated();
+ bool cloudConnected();
+
+ void listLights();
+ void listScenes();
+ int setPower(const QString &lightId, bool power, int duration = 0);
+ int setBrightnesss(const QString &lightId, int brightness, int duration = 0);
+ int setColor(const QString &lightId, QColor color, int duration = 0);
+ int setColorTemperature(const QString &lightId, int kelvin, int duration = 0);
+ int setInfrared(const QString &lightId, int infrared, int duration = 0);
+
+ int activateScene(const QString &sceneId);
+
+ int setEffect(const QString &lightId, Effect effect, QColor color = "#FFFFFF");
+
+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:
+ void connectionChanged(bool m_connected);
+ void authenticationChanged(bool m_authenticated);
+ void lightsListReceived(const QList &lights);
+ void scenesListReceived(const QList &scenes);
+ void requestExecuted(int requestId, bool susccess);
+};
+
+#endif // LIFXCLOUD_H
diff --git a/lifx/lifxlan.cpp b/lifx/lifxlan.cpp
new file mode 100644
index 00000000..2c707627
--- /dev/null
+++ b/lifx/lifxlan.cpp
@@ -0,0 +1,201 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project 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 project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+#include "lifxlan.h"
+#include "extern-plugininfo.h"
+
+#include
+
+LifxLan::LifxLan(const QHostAddress &address, quint16 port, QObject *parent) :
+ QObject(parent),
+ m_host(address),
+ m_port(port)
+{
+ m_clientId = qrand();
+
+ m_socket = new QUdpSocket(this);
+
+ m_socket->setSocketOption(QAbstractSocket::MulticastTtlOption, QVariant(1));
+ m_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1));
+}
+
+LifxLan::~LifxLan()
+{
+ if (m_socket) {
+ m_socket->waitForBytesWritten(1000);
+ m_socket->close();
+ }
+}
+
+bool LifxLan::enable()
+{
+ // Bind udp socket and join multicast group
+ if(!m_socket->bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress)){
+ qCWarning(dcLifx()) << "could not bind to port" << m_port;
+ delete m_socket;
+ m_socket = nullptr;
+ return false;
+ }
+
+ if(!m_socket->joinMulticastGroup(QHostAddress("239.255.255.250"))){
+ qCWarning(dcLifx()) << "could not join multicast group";
+ delete m_socket;
+ m_socket = nullptr;
+ return false;
+ }
+ connect(m_socket, &QUdpSocket::readyRead, this, &LifxLan::onReadyRead);
+ return true;
+}
+
+void LifxLan::setHostAddress(const QHostAddress &address)
+{
+ m_host = address;
+}
+
+void LifxLan::setPort(quint16 port)
+{
+ m_port = port;
+}
+
+int LifxLan::setColorTemperature(uint mirad, uint msFadeTime)
+{
+ Q_UNUSED(mirad)
+ Q_UNUSED(msFadeTime)
+ int requestId = qrand();
+ Message message;
+ sendMessage(message);
+ return requestId;
+}
+
+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 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 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;
+}
+
+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 |= (message.frame.Tagged << 5); // Determines usage of the Frame Address target field
+ protocol &= ~(0x0003); // Message origin indicator: must be zero (0)
+ header.append(protocol >> 8);
+ header.append(protocol & 0xff);
+
+ //Source identifier: unique value set by the client, used by responses
+ header.append(m_clientId);
+
+ // -- FRAME ADDRESS --
+ //Target - frame address starts with 64 bits
+
+
+ //ADD RESERVED SECTION a reserved section of 48 bits (6 bytes)
+ //header.append(6, '\x00'); //that must be all zeros.
+
+ //ADD ACK and RES
+ //header.append(2, '\x01');
+
+ //ADD SEQUENCE NUMBER 1Byte
+ //header.append(m_sequenceNumber++);
+
+ //Protocol header. which begins with 64 reserved bits (8 bytes). Set these all to zero.
+ //header.append(8, '\x00'); //that must be all zeros.
+
+ //ADD MESSAGE TYPE
+ //header.append(static_cast(LightMessages::SetColor));
+
+ // Finally another reserved field of 16 bits (2 bytes).
+ //header.append(2, '\x00');
+
+ //ADD SIZE
+ //header.append(((static_cast(header.length()+1) & 0xff00) >> 8));
+ //header.append((static_cast(header.length()+1) & 0x00ff));
+
+ //Finally another reserved field of 16 bits (2 bytes).
+ //header.append(2, '\x00');
+
+ QByteArray fullMessage;
+ //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 LifxLan::onStateChanged(QAbstractSocket::SocketState state)
+{
+ switch (state) {
+ case QAbstractSocket::SocketState::ConnectedState:
+ emit connectionChanged(true);
+ break;
+ case QAbstractSocket::SocketState::UnconnectedState:
+ m_reconnectTimer->start(10 * 1000);
+ emit connectionChanged(false);
+ break;
+ default:
+ emit connectionChanged(false);
+ break;
+ }
+}
+
+void LifxLan::onReadyRead()
+{
+ QByteArray data = m_socket->readAll();
+ qCDebug(dcLifx()) << "Message received" << data;
+}
diff --git a/lifx/lifxlan.h b/lifx/lifxlan.h
new file mode 100644
index 00000000..e15dea84
--- /dev/null
+++ b/lifx/lifxlan.h
@@ -0,0 +1,154 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project 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 project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef LIFXLAN_H
+#define LIFXLAN_H
+
+#include
+#include
+#include
+#include
+
+#include "network/networkaccessmanager.h"
+
+#include
+
+class LifxLan : public QObject
+{
+ Q_OBJECT
+public:
+
+#pragma pack(push, 1)
+ typedef struct {
+ /* frame */
+ uint16_t size;
+ uint16_t protocol:12;
+ uint8_t addressable:1;
+ uint8_t tagged:1;
+ uint8_t origin:2;
+ uint32_t source;
+ /* frame address */
+ uint8_t target[8];
+ uint8_t reserved[6];
+ uint8_t res_required:1;
+ uint8_t ack_required:1;
+ uint8_t :6;
+ uint8_t sequence;
+ /* protocol header */
+ uint64_t :64;
+ uint16_t type;
+ uint16_t :16;
+ /* variable length payload follows */
+ } ProtocolHeader_t;
+#pragma pack(pop)
+
+ struct Frame {
+ //quint16 Size; //Size of entire message in bytes including this field
+ //quint16 Protocol; //Protocol number: must be 1024 (decimal)
+ //bool Addressable; //Message includes a target address: must be one (1)
+ bool Tagged; //Determines usage of the Frame Address target field
+ //quint8 Origin; //Message origin indicator: must be zero (0)
+ quint32 Source; //Source identifier: unique value set by the client, used by responses
+ };
+
+ struct FrameAddress {
+ quint64 Target; //6 byte device address (MAC address) or zero (0) means all devices. The last two bytes should be 0 bytes.
+ bool ResponseRequired; //Response message required
+ bool AckRequired; //Acknowledgement message required
+ quint8 Sequence; //Wrap around message sequence number
+ };
+
+ struct ProtocolHeader {
+ quint16 Type; //Message type determines the payload being used
+ };
+
+ struct Message {
+ Frame frame;
+ FrameAddress frameAddress;
+ ProtocolHeader protocolHeader;
+ QByteArray payload;
+ };
+
+ enum LightMessages {
+ Get = 101,
+ SetColor = 102,
+ SetWaveform = 103,
+ SetWaveformOptional = 119,
+ State = 107,
+ GetPower = 116,
+ SetPower = 117,
+ StatePower = 118,
+ GetInfrared = 120,
+ StateInfrared = 121,
+ SetInfrared = 122
+ };
+
+ struct LifxProduct {
+ int pid;
+ QString name;
+ bool color;
+ bool infrared;
+ bool matrix;
+ bool multizone;
+ uint minColorTemperature;
+ uint maxColorTemperature;
+ bool chain;
+ };
+
+ explicit LifxLan(const QHostAddress &address, quint16 port = 56700, QObject *parent = nullptr);
+ ~LifxLan();
+ bool enable();
+ void setHostAddress(const QHostAddress &address);
+ void setPort(quint16 port);
+
+ int setColorTemperature(uint kelvin, uint msFadeTime=500);
+ int setColor(QColor color, uint msFadeTime = 500);
+ int setBrightness(uint percentage, uint msFadeTime = 500);
+ int setPower(bool power, uint msFadeTime = 500);
+
+private:
+ quint32 m_clientId = 0;
+ QTimer *m_reconnectTimer = nullptr;
+ QUdpSocket *m_socket = nullptr;
+ QHostAddress m_host;
+ quint16 m_port;
+ quint8 m_sequenceNumber = 0;
+
+ void sendMessage(const Message &message);
+
+private slots:
+ void onStateChanged(QAbstractSocket::SocketState state);
+ void onReadyRead();
+
+signals:
+ void connectionChanged(bool connected);
+ void requestExecuted(int requestId, bool success);
+};
+#endif // LIFXLAN_H
diff --git a/lifx/meta.json b/lifx/meta.json
new file mode 100644
index 00000000..37064e23
--- /dev/null
+++ b/lifx/meta.json
@@ -0,0 +1,13 @@
+{
+ "title": "LIFX",
+ "tagline": "Control LIFX light bulbs.",
+ "icon": "lifx.png",
+ "stability": "consumer",
+ "offline": false,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ "light"
+ ]
+}
diff --git a/lifx/products.json b/lifx/products.json
new file mode 100644
index 00000000..99f8a77a
--- /dev/null
+++ b/lifx/products.json
@@ -0,0 +1,373 @@
+[
+ {
+ "vid": 1,
+ "name": "LIFX",
+ "products": [
+ {
+ "pid": 1,
+ "name": "Original 1000",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 3,
+ "name": "Color 650",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 10,
+ "name": "White 800 (Low Voltage)",
+ "features": {
+ "color": false,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2700, 6500],
+ "chain": false
+ }
+ },
+ {
+ "pid": 11,
+ "name": "White 800 (High Voltage)",
+ "features": {
+ "color": false,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2700, 6500],
+ "chain": false
+ }
+ },
+ {
+ "pid": 18,
+ "name": "White 900 BR30 (Low Voltage)",
+ "features": {
+ "color": false,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2700, 6500],
+ "chain": false
+ }
+ },
+ {
+ "pid": 20,
+ "name": "Color 1000 BR30",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 22,
+ "name": "Color 1000",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 27,
+ "name": "LIFX A19",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 28,
+ "name": "LIFX BR30",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 29,
+ "name": "LIFX+ A19",
+ "features": {
+ "color": true,
+ "infrared": true,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 30,
+ "name": "LIFX+ BR30",
+ "features": {
+ "color": true,
+ "infrared": true,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 31,
+ "name": "LIFX Z",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": true,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 32,
+ "name": "LIFX Z 2",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": true,
+ "temperature_range": [2500, 9000],
+ "chain": false,
+ "min_ext_mz_firmware": 1532997580,
+ "min_ext_mz_firmware_components": [2, 77]
+ }
+ },
+ {
+ "pid": 36,
+ "name": "LIFX Downlight",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 37,
+ "name": "LIFX Downlight",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 38,
+ "name": "LIFX Beam",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": true,
+ "temperature_range": [2500, 9000],
+ "chain": false,
+ "min_ext_mz_firmware": 1532997580,
+ "min_ext_mz_firmware_components": [2, 77]
+ }
+ },
+ {
+ "pid": 43,
+ "name": "LIFX A19",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 44,
+ "name": "LIFX BR30",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 45,
+ "name": "LIFX+ A19",
+ "features": {
+ "color": true,
+ "infrared": true,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 46,
+ "name": "LIFX+ BR30",
+ "features": {
+ "color": true,
+ "infrared": true,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 49,
+ "name": "LIFX Mini",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 50,
+ "name": "LIFX Mini Day and Dusk",
+ "features": {
+ "color": false,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [1500, 4000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 51,
+ "name": "LIFX Mini White",
+ "features": {
+ "color": false,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2700, 2700],
+ "chain": false
+ }
+ },
+ {
+ "pid": 52,
+ "name": "LIFX GU10",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 55,
+ "name": "LIFX Tile",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": true,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": true
+ }
+ },
+ {
+ "pid": 57,
+ "name": "LIFX Candle",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": true,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 59,
+ "name": "LIFX Mini Color",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 60,
+ "name": "LIFX Mini Day and Dusk",
+ "features": {
+ "color": false,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [1500, 4000],
+ "chain": false
+ }
+ },
+ {
+ "pid": 61,
+ "name": "LIFX Mini White",
+ "features": {
+ "color": false,
+ "infrared": false,
+ "matrix": false,
+ "multizone": false,
+ "temperature_range": [2700, 2700],
+ "chain": false
+ }
+ },
+ {
+ "pid": 68,
+ "name": "LIFX Candle",
+ "features": {
+ "color": true,
+ "infrared": false,
+ "matrix": true,
+ "multizone": false,
+ "temperature_range": [2500, 9000],
+ "chain": false
+ }
+ }
+ ]
+ }
+]
+
diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts
new file mode 100644
index 00000000..70809735
--- /dev/null
+++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts
@@ -0,0 +1,227 @@
+
+
+
+
+ IntegrationPluginLifx
+
+ LIFX server is not reachable.
+ LIFX Server ist nicht erreichbar
+
+
+ Please enter your user name and token. Get the token from https://cloud.lifx.com/settings
+ Bitte geben Sie Ihren Lifx Benutzernamen und Token ein. Holen Sie sich Ihren Token von https://cloud.lifx.com/settings
+
+
+ The token is invalid.
+ Der Token ist ungültig.
+
+
+
+ Lifx
+
+ Brightness
+ The name of the ParamType (ThingClass: dimmableBulb, ActionType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611})
+----------
+The name of the ParamType (ThingClass: dimmableBulb, EventType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611})
+----------
+The name of the StateType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb
+----------
+The name of the ParamType (ThingClass: colorBulb, ActionType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863})
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863})
+----------
+The name of the StateType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb
+ Helligkeit
+
+
+ Brightness changed
+ The name of the EventType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb
+----------
+The name of the EventType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb
+ Helligkeit geändert
+
+
+ Color
+ The name of the ParamType (ThingClass: colorBulb, ActionType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6})
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6})
+----------
+The name of the StateType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb
+----------
+The name of the ThingClass ({12907c9c-e7f0-47f2-bd58-39d52ffdf24e})
+ Farbe
+
+
+ Color changed
+ The name of the EventType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb
+ Farbe geändert
+
+
+ Color temperature
+ The name of the ParamType (ThingClass: dimmableBulb, ActionType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42})
+----------
+The name of the ParamType (ThingClass: dimmableBulb, EventType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42})
+----------
+The name of the StateType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb
+----------
+The name of the ParamType (ThingClass: colorBulb, ActionType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be})
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be})
+----------
+The name of the StateType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb
+ Farbtemperatur
+
+
+ Color temperature changed
+ The name of the EventType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb
+----------
+The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb
+ Farbtemperatur geändert
+
+
+ Day and Dusk
+ The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e})
+ Tag und Sonnenaufgang
+
+
+ LIFX
+ The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87})
+----------
+The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992})
+ LIFX
+
+
+ Power
+ The name of the ParamType (ThingClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0})
+----------
+The name of the ParamType (ThingClass: dimmableBulb, EventType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0})
+----------
+The name of the StateType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb
+----------
+The name of the ParamType (ThingClass: colorBulb, ActionType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd})
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd})
+----------
+The name of the StateType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb
+ Eingeschalten
+
+
+ Power changed
+ The name of the EventType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb
+----------
+The name of the EventType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb
+ Eingeschalten changed
+
+
+ Reachable
+ The name of the ParamType (ThingClass: dimmableBulb, EventType: connected, ID: {d33f98ef-5e0f-464c-afed-88b95cc701cd})
+----------
+The name of the StateType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of ThingClass dimmableBulb
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: connected, ID: {dc4c1640-90f3-4fe0-af9b-db7fa105f18a})
+----------
+The name of the StateType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of ThingClass colorBulb
+ Erreichbar
+
+
+ Reachable changed
+ The name of the EventType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of ThingClass dimmableBulb
+----------
+The name of the EventType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of ThingClass colorBulb
+ Erreichbar changed
+
+
+ Set color
+ The name of the ActionType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb
+ Setze Farbe
+
+
+ Set color temperature
+ The name of the ActionType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb
+----------
+The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb
+ Setze Farbtemperatur
+
+
+ Set effect
+ The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb
+ Setze Effekt
+
+
+ Set power
+ The name of the ActionType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb
+----------
+The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb
+ Setze Eingeschalten
+
+
+ Effect
+ 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})
+----------
+The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb
+ Effekt
+
+
+ Effect changed
+ The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb
+ Effekt geändert
+
+
+ ID
+ 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})
+ ID
+
+
+ Set brightness
+ The name of the ActionType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb
+----------
+The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb
+ Setze Helligkeit
+
+
+ Connected
+ 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
+ Verbunden
+
+
+ Connected changed
+ The name of the EventType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount
+ Verbunden geändert
+
+
+ LIFX cloud account
+ The name of the ThingClass ({387c87f6-3e5b-4d6a-ba4d-372d0efad79f})
+ LIFX Cloud-Account
+
+
+ Logged in
+ 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
+ Eingelogged
+
+
+ Logged in changed
+ The name of the EventType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount
+ Eingelogged geändert
+
+
+ User name
+ 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
+ Benutzername
+
+
+ User name changed
+ The name of the EventType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount
+ Benutzername geändert
+
+
+
diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts
new file mode 100644
index 00000000..f869d2e1
--- /dev/null
+++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts
@@ -0,0 +1,227 @@
+
+
+
+
+ IntegrationPluginLifx
+
+ LIFX server is not reachable.
+
+
+
+ Please enter your user name and token. Get the token from https://cloud.lifx.com/settings
+
+
+
+ The token is invalid.
+
+
+
+
+ Lifx
+
+ Brightness
+ The name of the ParamType (ThingClass: dimmableBulb, ActionType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611})
+----------
+The name of the ParamType (ThingClass: dimmableBulb, EventType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611})
+----------
+The name of the StateType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb
+----------
+The name of the ParamType (ThingClass: colorBulb, ActionType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863})
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863})
+----------
+The name of the StateType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb
+
+
+
+ Brightness changed
+ The name of the EventType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb
+----------
+The name of the EventType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb
+
+
+
+ Color
+ The name of the ParamType (ThingClass: colorBulb, ActionType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6})
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6})
+----------
+The name of the StateType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb
+----------
+The name of the ThingClass ({12907c9c-e7f0-47f2-bd58-39d52ffdf24e})
+
+
+
+ Color changed
+ The name of the EventType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb
+
+
+
+ Color temperature
+ The name of the ParamType (ThingClass: dimmableBulb, ActionType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42})
+----------
+The name of the ParamType (ThingClass: dimmableBulb, EventType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42})
+----------
+The name of the StateType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb
+----------
+The name of the ParamType (ThingClass: colorBulb, ActionType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be})
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be})
+----------
+The name of the StateType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb
+
+
+
+ Color temperature changed
+ The name of the EventType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb
+----------
+The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb
+
+
+
+ Day and Dusk
+ The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e})
+
+
+
+ LIFX
+ The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87})
+----------
+The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992})
+
+
+
+ Power
+ The name of the ParamType (ThingClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0})
+----------
+The name of the ParamType (ThingClass: dimmableBulb, EventType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0})
+----------
+The name of the StateType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb
+----------
+The name of the ParamType (ThingClass: colorBulb, ActionType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd})
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd})
+----------
+The name of the StateType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb
+
+
+
+ Power changed
+ The name of the EventType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb
+----------
+The name of the EventType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb
+
+
+
+ Reachable
+ The name of the ParamType (ThingClass: dimmableBulb, EventType: connected, ID: {d33f98ef-5e0f-464c-afed-88b95cc701cd})
+----------
+The name of the StateType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of ThingClass dimmableBulb
+----------
+The name of the ParamType (ThingClass: colorBulb, EventType: connected, ID: {dc4c1640-90f3-4fe0-af9b-db7fa105f18a})
+----------
+The name of the StateType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of ThingClass colorBulb
+
+
+
+ Reachable changed
+ The name of the EventType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of ThingClass dimmableBulb
+----------
+The name of the EventType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of ThingClass colorBulb
+
+
+
+ Set color
+ The name of the ActionType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb
+
+
+
+ Set color temperature
+ The name of the ActionType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb
+----------
+The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb
+
+
+
+ Set effect
+ The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb
+
+
+
+ Set power
+ The name of the ActionType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb
+----------
+The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb
+
+
+
+ Effect
+ 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})
+----------
+The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb
+
+
+
+ Effect changed
+ The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb
+
+
+
+ ID
+ 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})
+
+
+
+ Set brightness
+ The name of the ActionType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb
+----------
+The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb
+
+
+
+ Connected
+ 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
+
+
+
+ Connected changed
+ The name of the EventType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount
+
+
+
+ LIFX cloud account
+ The name of the ThingClass ({387c87f6-3e5b-4d6a-ba4d-372d0efad79f})
+
+
+
+ Logged in
+ 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
+
+
+
+ Logged in changed
+ The name of the EventType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount
+
+
+
+ User name
+ 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
+
+
+
+ User name changed
+ The name of the EventType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount
+
+
+
+
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 1194d487..5605b2b5 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -29,6 +29,7 @@ PLUGIN_DIRS = \
keba \
kodi \
lgsmarttv \
+ lifx \
mailnotification \
mqttclient \
nanoleaf \