Merge PR #211: New Plugin: LIFX
This commit is contained in:
commit
b388b917f7
16
debian/control
vendored
16
debian/control
vendored
@ -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,
|
||||
|
||||
1
debian/nymea-plugin-lifx.install.in
vendored
Normal file
1
debian/nymea-plugin-lifx.install.in
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginlifx.so
|
||||
18
lifx/README.md
Normal file
18
lifx/README.md
Normal file
@ -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/
|
||||
609
lifx/integrationpluginlifx.cpp
Normal file
609
lifx/integrationpluginlifx.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QDebug>
|
||||
#include <QColor>
|
||||
#include <QRgb>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
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<QString, ThingDescriptor> 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<LifxLan *>(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<LifxCloud *>(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<LifxCloud *>(sender());
|
||||
Thing *accountThing = m_lifxCloudConnections.key(lifxCloud);
|
||||
if (!accountThing)
|
||||
return;
|
||||
accountThing->setStateValue(lifxAccountLoggedInStateTypeId, authenticated);
|
||||
}
|
||||
|
||||
void IntegrationPluginLifx::onLifxCloudLightsListReceived(const QList<LifxCloud::Light> &lights)
|
||||
{
|
||||
LifxCloud *lifxCloud = static_cast<LifxCloud *>(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<LifxCloud::Scene> &scenes)
|
||||
{
|
||||
LifxCloud *lifxCloud = static_cast<LifxCloud *>(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);
|
||||
}
|
||||
}
|
||||
103
lifx/integrationpluginlifx.h
Normal file
103
lifx/integrationpluginlifx.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QTimer>
|
||||
|
||||
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<LifxCloud *, ThingSetupInfo *> m_asyncCloudSetups;
|
||||
QHash<int, ThingActionInfo *> m_asyncActions;
|
||||
QHash<Thing *, LifxLan *> m_lifxLanConnections;
|
||||
QHash<Thing *, LifxCloud *> m_lifxCloudConnections;
|
||||
QHash<LifxCloud *, BrowseResult *> m_asyncBrowseResults;
|
||||
QHash<int, BrowserActionInfo *> m_asyncBrowserItem;
|
||||
|
||||
ZeroConfServiceBrowser *m_serviceBrowser = nullptr;
|
||||
|
||||
QHash<ThingClassId, StateTypeId> m_connectedStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_powerStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_brightnessStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_colorTemperatureStateTypeIds;
|
||||
QHash<ThingClassId, ParamTypeId> m_hostAddressParamTypeIds;
|
||||
QHash<ThingClassId, ParamTypeId> m_portParamTypeIds;
|
||||
QHash<ThingClassId, ParamTypeId> m_idParamTypeIds;
|
||||
|
||||
QHash<ThingId, ThingActionInfo *> m_pendingBrightnessAction;
|
||||
QHash<int, LifxLan::LifxProduct> 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<LifxCloud::Light> &lights);
|
||||
void onLifxCloudScenesListReceived(const QList<LifxCloud::Scene> &scenes);
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGIN_LIFX_H
|
||||
203
lifx/integrationpluginlifx.json
Normal file
203
lifx/integrationpluginlifx.json
Normal file
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
lifx/lifx.png
Normal file
BIN
lifx/lifx.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
14
lifx/lifx.pro
Normal file
14
lifx/lifx.pro
Normal file
@ -0,0 +1,14 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
QT += network
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginlifx.cpp \
|
||||
lifxcloud.cpp \
|
||||
lifxlan.cpp \
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginlifx.h \
|
||||
lifxcloud.h \
|
||||
lifxlan.h \
|
||||
|
||||
373
lifx/lifxcloud.cpp
Normal file
373
lifx/lifxcloud.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
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<Light> 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<Scene> 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;
|
||||
}
|
||||
140
lifx/lifxcloud.h
Normal file
140
lifx/lifxcloud.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QObject>
|
||||
#include <QColor>
|
||||
#include <QTimer>
|
||||
#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<Light> &lights);
|
||||
void scenesListReceived(const QList<Scene> &scenes);
|
||||
void requestExecuted(int requestId, bool susccess);
|
||||
};
|
||||
|
||||
#endif // LIFXCLOUD_H
|
||||
201
lifx/lifxlan.cpp
Normal file
201
lifx/lifxlan.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QColor>
|
||||
|
||||
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<uint16_t>(LightMessages::SetColor));
|
||||
|
||||
// Finally another reserved field of 16 bits (2 bytes).
|
||||
//header.append(2, '\x00');
|
||||
|
||||
//ADD SIZE
|
||||
//header.append(((static_cast<uint16_t>(header.length()+1) & 0xff00) >> 8));
|
||||
//header.append((static_cast<uint16_t>(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;
|
||||
}
|
||||
154
lifx/lifxlan.h
Normal file
154
lifx/lifxlan.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QObject>
|
||||
#include <QTimer>
|
||||
#include <QHostAddress>
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include "network/networkaccessmanager.h"
|
||||
|
||||
#include <QColor>
|
||||
|
||||
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
|
||||
13
lifx/meta.json
Normal file
13
lifx/meta.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"title": "LIFX",
|
||||
"tagline": "Control LIFX light bulbs.",
|
||||
"icon": "lifx.png",
|
||||
"stability": "consumer",
|
||||
"offline": false,
|
||||
"technologies": [
|
||||
"network"
|
||||
],
|
||||
"categories": [
|
||||
"light"
|
||||
]
|
||||
}
|
||||
373
lifx/products.json
Normal file
373
lifx/products.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
227
lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts
Normal file
227
lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts
Normal file
@ -0,0 +1,227 @@
|
||||
<?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>LIFX Server ist nicht erreichbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please enter your user name and token. Get the token from https://cloud.lifx.com/settings</source>
|
||||
<translation>Bitte geben Sie Ihren Lifx Benutzernamen und Token ein. Holen Sie sich Ihren Token von https://cloud.lifx.com/settings</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The token is invalid.</source>
|
||||
<translation>Der Token ist ungültig.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Lifx</name>
|
||||
<message>
|
||||
<source>Brightness</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Helligkeit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Brightness changed</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Helligkeit geändert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color</source>
|
||||
<extracomment>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})</extracomment>
|
||||
<translation>Farbe</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color changed</source>
|
||||
<extracomment>The name of the EventType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb</extracomment>
|
||||
<translation>Farbe geändert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color temperature</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Farbtemperatur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color temperature changed</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Farbtemperatur geändert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Day and Dusk</source>
|
||||
<extracomment>The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e})</extracomment>
|
||||
<translation>Tag und Sonnenaufgang</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>LIFX</source>
|
||||
<extracomment>The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87})
|
||||
----------
|
||||
The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992})</extracomment>
|
||||
<translation>LIFX</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Power</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Eingeschalten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Power changed</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Eingeschalten changed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reachable</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Erreichbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reachable changed</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Erreichbar changed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set color</source>
|
||||
<extracomment>The name of the ActionType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb</extracomment>
|
||||
<translation>Setze Farbe</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set color temperature</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Setze Farbtemperatur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set effect</source>
|
||||
<extracomment>The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
|
||||
<translation>Setze Effekt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set power</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Setze Eingeschalten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Effect</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation>Effekt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Effect changed</source>
|
||||
<extracomment>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})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0})</extracomment>
|
||||
<translation>ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set brightness</source>
|
||||
<extracomment>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</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>Verbunden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connected changed</source>
|
||||
<extracomment>The name of the EventType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount</extracomment>
|
||||
<translation>Verbunden geändert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>LIFX cloud account</source>
|
||||
<extracomment>The name of the ThingClass ({387c87f6-3e5b-4d6a-ba4d-372d0efad79f})</extracomment>
|
||||
<translation>LIFX Cloud-Account</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>Eingelogged</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Logged in changed</source>
|
||||
<extracomment>The name of the EventType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount</extracomment>
|
||||
<translation>Eingelogged geändert</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>Benutzername</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>User name changed</source>
|
||||
<extracomment>The name of the EventType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount</extracomment>
|
||||
<translation>Benutzername geändert</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
227
lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts
Normal file
227
lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts
Normal file
@ -0,0 +1,227 @@
|
||||
<?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>Please enter your user name and token. Get the token from https://cloud.lifx.com/settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The token is invalid.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Lifx</name>
|
||||
<message>
|
||||
<source>Brightness</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Brightness changed</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color</source>
|
||||
<extracomment>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})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color changed</source>
|
||||
<extracomment>The name of the EventType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color temperature</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color temperature changed</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Day and Dusk</source>
|
||||
<extracomment>The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>LIFX</source>
|
||||
<extracomment>The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87})
|
||||
----------
|
||||
The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992})</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})
|
||||
----------
|
||||
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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Power changed</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reachable</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reachable changed</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set color</source>
|
||||
<extracomment>The name of the ActionType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set color temperature</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set effect</source>
|
||||
<extracomment>The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set power</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Effect</source>
|
||||
<extracomment>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</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Effect changed</source>
|
||||
<extracomment>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})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set brightness</source>
|
||||
<extracomment>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</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>
|
||||
@ -29,6 +29,7 @@ PLUGIN_DIRS = \
|
||||
keba \
|
||||
kodi \
|
||||
lgsmarttv \
|
||||
lifx \
|
||||
mailnotification \
|
||||
mqttclient \
|
||||
nanoleaf \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user