From d84e0face7d97eaf08bb0bf0cde1b0dd38951f3d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 25 Dec 2019 20:31:43 +0100 Subject: [PATCH 01/15] added lifx --- lifx/devicepluginlifx.cpp | 0 lifx/devicepluginlifx.h | 0 lifx/devicepluginlifx.json | 0 lifx/lifx.cpp | 0 lifx/lifx.h | 0 lifx/lifx.pro | 14 ++++++++++++++ nymea-plugins.pro | 1 + 7 files changed, 15 insertions(+) create mode 100644 lifx/devicepluginlifx.cpp create mode 100644 lifx/devicepluginlifx.h create mode 100644 lifx/devicepluginlifx.json create mode 100644 lifx/lifx.cpp create mode 100644 lifx/lifx.h create mode 100644 lifx/lifx.pro diff --git a/lifx/devicepluginlifx.cpp b/lifx/devicepluginlifx.cpp new file mode 100644 index 00000000..e69de29b diff --git a/lifx/devicepluginlifx.h b/lifx/devicepluginlifx.h new file mode 100644 index 00000000..e69de29b diff --git a/lifx/devicepluginlifx.json b/lifx/devicepluginlifx.json new file mode 100644 index 00000000..e69de29b diff --git a/lifx/lifx.cpp b/lifx/lifx.cpp new file mode 100644 index 00000000..e69de29b diff --git a/lifx/lifx.h b/lifx/lifx.h new file mode 100644 index 00000000..e69de29b diff --git a/lifx/lifx.pro b/lifx/lifx.pro new file mode 100644 index 00000000..0ddd32b8 --- /dev/null +++ b/lifx/lifx.pro @@ -0,0 +1,14 @@ +include(../plugins.pri) + +QT += network + +TARGET = $$qtLibraryTarget(nymea_devicepluginlifx) + +SOURCES += \ + devicepluginlifx.cpp \ + lifx.cpp \ + +HEADERS += \ + devicepluginlifx.h \ + lifx.h \ + diff --git a/nymea-plugins.pro b/nymea-plugins.pro index c734ab56..9f72f33e 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -27,6 +27,7 @@ PLUGIN_DIRS = \ keba \ kodi \ lgsmarttv \ + lifx \ mailnotification \ mqttclient \ nanoleaf \ From 6af09273e14b8bf0e2d9c10f22bd1986b157bc1d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Fri, 3 Jan 2020 10:19:52 +0100 Subject: [PATCH 02/15] added some more bit shifting --- debian/control | 16 + debian/nymea-plugin-lifx.install.in | 1 + lifx/README.md | 3 + lifx/devicepluginlifx.cpp | 220 +++++++++++ lifx/devicepluginlifx.h | 78 ++++ lifx/devicepluginlifx.json | 192 +++++++++ lifx/lifx.cpp | 207 ++++++++++ lifx/lifx.h | 151 +++++++ lifx/lifx.pro | 4 +- lifx/products.json | 373 ++++++++++++++++++ ...0ee30-79e2-447b-8dcc-c34470f41992-en_US.ts | 185 +++++++++ 11 files changed, 1428 insertions(+), 2 deletions(-) create mode 100644 debian/nymea-plugin-lifx.install.in create mode 100644 lifx/README.md create mode 100644 lifx/products.json create mode 100644 lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts diff --git a/debian/control b/debian/control index f545e939..5d021ad5 100644 --- a/debian/control +++ b/debian/control @@ -400,6 +400,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}, @@ -981,6 +996,7 @@ Depends: nymea-plugin-anel, nymea-plugin-genericthings, nymea-plugin-kodi, nymea-plugin-lgsmarttv, + nymea-plugin-lifx, nymea-plugin-mailnotification, nymea-plugin-texasinstruments, nymea-plugin-nanoleaf, diff --git a/debian/nymea-plugin-lifx.install.in b/debian/nymea-plugin-lifx.install.in new file mode 100644 index 00000000..ea0623b7 --- /dev/null +++ b/debian/nymea-plugin-lifx.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginlifx.so diff --git a/lifx/README.md b/lifx/README.md new file mode 100644 index 00000000..088654d0 --- /dev/null +++ b/lifx/README.md @@ -0,0 +1,3 @@ +# Lifx + +This plug-in implements the LAN API for Lifx devices diff --git a/lifx/devicepluginlifx.cpp b/lifx/devicepluginlifx.cpp index e69de29b..245a788e 100644 --- a/lifx/devicepluginlifx.cpp +++ b/lifx/devicepluginlifx.cpp @@ -0,0 +1,220 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* This project may also contain libraries licensed under the open source software license GNU GPL v.3. +* Alternatively, this project may be redistributed and/or modified under the terms of the GNU +* Lesser General Public License as published by the Free Software Foundation; version 3. +* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "devicepluginlifx.h" + +#include "devices/device.h" +#include "types/param.h" +#include "plugininfo.h" + +#include +#include +#include + +DevicePluginLifx::DevicePluginLifx() +{ + +} + +void DevicePluginLifx::init() +{ + m_connectedStateTypeIds.insert(colorBulbDeviceClassId, colorBulbConnectedStateTypeId); + m_connectedStateTypeIds.insert(dimmableBulbDeviceClassId, dimmableBulbConnectedStateTypeId); + + m_powerStateTypeIds.insert(colorBulbDeviceClassId, colorBulbPowerStateTypeId); + m_powerStateTypeIds.insert(dimmableBulbDeviceClassId, dimmableBulbPowerStateTypeId); + + m_brightnessStateTypeIds.insert(colorBulbDeviceClassId, colorBulbBrightnessStateTypeId); + m_brightnessStateTypeIds.insert(dimmableBulbDeviceClassId, dimmableBulbBrightnessStateTypeId); +} + +void DevicePluginLifx::discoverDevices(DeviceDiscoveryInfo *info) +{ + if (!m_lifxConnection) { + m_lifxConnection = new Lifx(this); + m_lifxConnection->enable(); + } + + if (info->deviceClassId() == colorBulbDeviceClassId) { + } +} + +void DevicePluginLifx::setupDevice(DeviceSetupInfo *info) +{ + Device *device = info->device(); + + if (!m_lifxConnection) { + m_lifxConnection = new Lifx(this); + m_lifxConnection->enable(); + } + + if (device->deviceClassId() == colorBulbDeviceClassId) { + + info->finish(Device::DeviceErrorNoError); + } +} + +void DevicePluginLifx::postSetupDevice(Device *device) +{ + connect(device, &Device::nameChanged, this, &DevicePluginLifx::onDeviceNameChanged); + + if (!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() { + + }); + } + m_pluginTimer->timeout(); +} + +void DevicePluginLifx::executeAction(DeviceActionInfo *info) +{ + Device *device = info->device(); + Action action = info->action(); + + if (!m_lifxConnection) + return info->finish(Device::DeviceErrorHardwareFailure); + + if (device->deviceClassId() == colorBulbDeviceClassId) { + + if (action.actionTypeId() == colorBulbPowerActionTypeId) { + bool power = action.param(colorBulbPowerActionPowerParamTypeId).value().toBool(); + int requestId = m_lifxConnection->setPower(power); + connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + m_asyncActions.insert(requestId, info); + } else if (action.actionTypeId() == colorBulbBrightnessActionTypeId) { + + if (!device->stateValue(colorBulbPowerStateTypeId).toBool()) + m_lifxConnection->setPower(true); + + if (m_pendingBrightnessAction.contains(device->id())) { + //Scrap old info and insert new one + m_pendingBrightnessAction.insert(device->id(), info); + } else { + m_pendingBrightnessAction.insert(device->id(), info); + QTimer::singleShot(500, this, [info, this]{ + int brightness = info->action().param(colorBulbBrightnessActionBrightnessParamTypeId).value().toInt(); + m_pendingBrightnessAction.remove(info->device()->id()); + int requestId = m_lifxConnection->setBrightness(brightness); + connect(info, &DeviceActionInfo::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 (!device->stateValue(colorBulbPowerStateTypeId).toBool()) + m_lifxConnection->setPower(true); + int requestId = m_lifxConnection->setColor(color); + connect(info, &DeviceActionInfo::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() * -11.12); + if (!device->stateValue(colorBulbPowerStateTypeId).toBool()) + m_lifxConnection->setPower(true); + int requestId = m_lifxConnection->setColorTemperature(colorTemperature); + connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + m_asyncActions.insert(requestId, info); + } else { + qCWarning(dcLifx()) << "Unhandled action"; + } + } else if (device->deviceClassId() == dimmableBulbDeviceClassId) { + if (action.actionTypeId() == dimmableBulbPowerActionTypeId) { + bool power = action.param(dimmableBulbPowerActionPowerParamTypeId).value().toBool(); + int requestId = m_lifxConnection->setPower(power); + connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + } else if (action.actionTypeId() == dimmableBulbBrightnessActionTypeId) { + int brightness = action.param(dimmableBulbBrightnessActionBrightnessParamTypeId).value().toInt(); + if (!device->stateValue(dimmableBulbPowerStateTypeId).toBool()) + m_lifxConnection->setPower(true); + int requestId = m_lifxConnection->setBrightness(brightness); + connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + m_asyncActions.insert(requestId, info); + } else { + qCWarning(dcLifx()) << "Unhandled action"; + } + } else { + qCWarning(dcLifx()) << "Unhandled device class"; + } +} + +void DevicePluginLifx::deviceRemoved(Device *device) +{ + Q_UNUSED(device) + + if (myDevices().isEmpty()) { + m_lifxConnection->deleteLater(); + m_lifxConnection = nullptr; + } +} + +void DevicePluginLifx::onDeviceNameChanged() +{ + Device *device = static_cast(sender()); + Q_UNUSED(device) +} + +void DevicePluginLifx::onConnectionChanged(bool connected) +{ + Q_UNUSED(connected) +// device->setStateValue(m_connectedStateTypeIds.value(device->deviceClassId()), connected); +} + +void DevicePluginLifx::onRequestExecuted(int requestId, bool success) +{ + if (m_asyncActions.contains(requestId)) { + DeviceActionInfo *info = m_asyncActions.take(requestId); + if (success) { + info->finish(Device::DeviceErrorNoError); + } else { + info->finish(Device::DeviceErrorHardwareFailure); + } + } +} + +/*void DevicePluginLifx::onPowerNotificationReceived(bool status) +{ + Q_UNUSED(status) + Lifx *Lifx = static_cast(sender()); + Device * device = myDevices().findById(m_LifxConnections.key(Lifx)); + if(!device) + return; + + device->setStateValue(m_powerStateTypeIds.value(device->deviceClassId()), status); + +} + +void DevicePluginLifx::onBrightnessNotificationReceived(int percentage) +{ + Q_UNUSED(percentage) + Lifx *lifx = static_cast(sender()); + Device * device = myDevices().findById(m_lifxConnections.key(lifx)); + if(!device) + return; + + device->setStateValue(m_brightnessStateTypeIds.value(device->deviceClassId()), percentage); +}*/ diff --git a/lifx/devicepluginlifx.h b/lifx/devicepluginlifx.h index e69de29b..0cfb91d9 100644 --- a/lifx/devicepluginlifx.h +++ b/lifx/devicepluginlifx.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* This project may also contain libraries licensed under the open source software license GNU GPL v.3. +* Alternatively, this project may be redistributed and/or modified under the terms of the GNU +* Lesser General Public License as published by the Free Software Foundation; version 3. +* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINLIFX_H +#define DEVICEPLUGINLIFX_H + +#include "devices/deviceplugin.h" +#include "plugintimer.h" +#include "lifx.h" + +#include + +class DevicePluginLifx : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginlifx.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginLifx(); + + void init() override; + void discoverDevices(DeviceDiscoveryInfo *info) override; + void setupDevice(DeviceSetupInfo *info) override; + void postSetupDevice(Device *device) override; + void executeAction(DeviceActionInfo *info) override; + void deviceRemoved(Device *device) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + QHash m_asyncActions; + Lifx *m_lifxConnection; + + QHash m_connectedStateTypeIds; + QHash m_powerStateTypeIds; + QHash m_brightnessStateTypeIds; + + QHash m_pendingBrightnessAction; + +private slots: + void onDeviceNameChanged(); + void onConnectionChanged(bool connected); + void onRequestExecuted(int requestId, bool success); + + /*void onPowerNotificationReceived(bool status); + void onBrightnessNotificationReceived(int percentage); + void onColorTemperatureNotificationReceived(int kelvin); + void onRgbNotificationReceived(QRgb rgbColor); + void onNameNotificationReceived(const QString &name);*/ +}; + +#endif // DEVICEPLUGINLIFX_H diff --git a/lifx/devicepluginlifx.json b/lifx/devicepluginlifx.json index e69de29b..28ba9556 100644 --- a/lifx/devicepluginlifx.json +++ b/lifx/devicepluginlifx.json @@ -0,0 +1,192 @@ +{ + "displayName": "LIFX", + "name": "Lifx", + "id": "4e00ee30-79e2-447b-8dcc-c34470f41992", + "vendors": [ + { + "name": "lifx", + "displayName": "LIFX", + "id": "e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87", + "deviceClasses": [ + { + "id": "12907c9c-e7f0-47f2-bd58-39d52ffdf24e", + "name": "colorBulb", + "displayName": "Color", + "createMethods": ["user", "discovery"], + "interfaces": ["colorlight", "connectable"], + "paramTypes": [ + { + "id": "fd1c4817-5111-433a-b5b9-fd9f49d4975c", + "name": "host", + "displayName": "Host address", + "type" : "QString", + "inputType": "IPv4Address", + "readOnly": true + }, + { + "id": "44c13745-300c-491f-b617-3a8d53472998", + "name": "port", + "displayName": "Port", + "type" : "int", + "readOnly": true + }, + { + "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" + }, + { + "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 brigtness", + "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", + "color loop" + ], + "writable": true + } + ] + }, + { + "id": "a5b02af8-7c97-4a78-9c78-bafee7407b5e", + "name": "dimmableBulb", + "displayName": "Day and Dusk", + "createMethods": ["user", "discovery"], + "interfaces": ["colortemperaturelight", "connectable"], + "paramTypes": [ + { + "id": "cc0a765b-a753-4e07-a6e5-47e9272c4346", + "name": "host", + "displayName": "Host address", + "type" : "QString", + "inputType": "IPv4Address", + "readOnly": true + }, + { + "id": "d233d9bf-6662-414d-92f6-dd3e267051b5", + "name": "port", + "displayName": "Port", + "type" : "int", + "readOnly": true + }, + { + "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 brigtness", + "type": "int", + "unit": "Percentage", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100, + "writable": true + }, + { + "id": "95797dee-b836-4047-98d5-afbbce4f8c42", + "name": "colorTemperature", + "displayName": "Color temperature", + "displayNameEvent": "Color temperature changed", + "displayNameAction": "Set color temperature", + "type": "int", + "unit": "Mired", + "defaultValue": 170, + "minValue": 153, + "maxValue": 500, + "writable": true + } + ] + } + ] + } + ] +} diff --git a/lifx/lifx.cpp b/lifx/lifx.cpp index e69de29b..24a037d9 100644 --- a/lifx/lifx.cpp +++ b/lifx/lifx.cpp @@ -0,0 +1,207 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* This project may also contain libraries licensed under the open source software license GNU GPL v.3. +* Alternatively, this project may be redistributed and/or modified under the terms of the GNU +* Lesser General Public License as published by the Free Software Foundation; version 3. +* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "lifx.h" +#include "extern-plugininfo.h" + +#include +#include + +Lifx::Lifx(QObject *parent) : + QObject(parent) +{ + m_reconnectTimer = new QTimer(this); + m_reconnectTimer->setSingleShot(true); + connect(m_reconnectTimer, &QTimer::timeout, this, &Lifx::onReconnectTimer); +} + +Lifx::~Lifx() +{ + if (m_socket) { + m_socket->waitForBytesWritten(1000); + m_socket->close(); + } +} + +bool Lifx::enable() +{ + // Clean up + if (m_socket) { + delete m_socket; + m_socket = nullptr; + } + + // Bind udp socket and join multicast group + m_socket = new QUdpSocket(this); + m_port = 56700; + m_host = QHostAddress("239.255.255.250"); + + m_socket->setSocketOption(QAbstractSocket::MulticastTtlOption,QVariant(1)); + m_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption,QVariant(1)); + + 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(m_host)){ + qCWarning(dcLifx()) << "could not join multicast group" << m_host; + delete m_socket; + m_socket = nullptr; + return false; + } + connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError))); + connect(m_socket, &QUdpSocket::readyRead, this, &Lifx::onReadyRead); + return true; +} + +void Lifx::discoverDevices() +{ + Message message; + sendMessage(message); +} + +int Lifx::setColorTemperature(int mirad, int msFadeTime) +{ + Q_UNUSED(mirad) + Q_UNUSED(msFadeTime) + int requestId = static_cast(QRandomGenerator::global()->generate()); + + return requestId; +} + +int Lifx::setColor(QColor color, int msFadeTime) +{ + Q_UNUSED(color) + Q_UNUSED(msFadeTime) + int requestId = static_cast(QRandomGenerator::global()->generate()); + + return requestId; +} + +int Lifx::setBrightness(int percentage, int msFadeTime) +{ + Q_UNUSED(percentage) + Q_UNUSED(msFadeTime) + int requestId = static_cast(QRandomGenerator::global()->generate()); + Message message; + sendMessage(message); + return requestId; +} + +int Lifx::setPower(bool power, int msFadeTime) +{ + Q_UNUSED(power) + Q_UNUSED(msFadeTime) + int requestId = static_cast(QRandomGenerator::global()->generate()); + + return requestId; +} + +int Lifx::flash() +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + + return requestId; +} + +int Lifx::flash15s() +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + + return requestId; +} + +void Lifx::sendMessage(const Lifx::Message &message) +{ + QByteArray data; + // -- 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) + data.append(protocol >> 8); + data.append(protocol & 0xff); + + //Source identifier: unique value set by the client, used by responses + data.append("nyma"); + + // -- FRAME ADDRESS -- + //Target - frame address starts with 64 bits + + + //ADD RESERVED SECTION a reserved section of 48 bits (6 bytes) + data.append(6, '0'); + + //ADD ACK and RES + + //ADD SEQUENCE NUMBER 1Byte + data.append(m_sequenceNumber + 1); + + //ADD MESSAGE TYPE + + //ADD SIZE + data.append(((static_cast(data.length()+1) & 0xff00) >> 8)); + data.append((static_cast(data.length()+1) & 0x00ff)); + + //Finally another reserved field of 16 bits (2 bytes). + data.append(2, '0'); + data = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); + m_socket->writeDatagram(data, m_host, m_port); + Q_UNUSED(message) +} + +void Lifx::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 Lifx::onReadyRead() +{ + QByteArray data = m_socket->readAll(); + qCDebug(dcLifx()) << "Message received" << data; +} + +void Lifx::onReconnectTimer() +{ + +} diff --git a/lifx/lifx.h b/lifx/lifx.h index e69de29b..c0aabe83 100644 --- a/lifx/lifx.h +++ b/lifx/lifx.h @@ -0,0 +1,151 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* This project may also contain libraries licensed under the open source software license GNU GPL v.3. +* Alternatively, this project may be redistributed and/or modified under the terms of the GNU +* Lesser General Public License as published by the Free Software Foundation; version 3. +* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#ifndef LIFX_H +#define LIFX_H + +#include +#include +#include +#include + +#include "network/networkaccessmanager.h" +#include "devices/device.h" + +#include + +class Lifx : 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 + }; + + explicit Lifx(QObject *parent = nullptr); + ~Lifx(); + bool enable(); + + void discoverDevices(); + int setColorTemperature(int kelvin, int msFadeTime=500); + int setColor(QColor color, int msFadeTime = 500); + int setBrightness(int percentage, int msFadeTime = 500); + int setPower(bool power, int msFadeTime = 500); + int flash(); + int flash15s(); + +private: + 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(); + void onReconnectTimer(); + +signals: + void connectionChanged(bool connected); + void devicesDiscovered(QHostAddress address, int port); + //void requestExecuted(int requestId, bool success); + //void errorReceived(int code, const QString &message); + + //void powerNotificationReceived(bool status); + //void brightnessNotificationReceived(int percentage); + //void colorTemperatureNotificationReceived(int kelvin); + //void rgbNotificationReceived(QRgb rgbColor); + //void hueNotificationReceived(int hueColor); + //void nameNotificationReceived(const QString &name); + //void saturationNotificationReceived(int percentage); +}; +#endif // LIFX_H diff --git a/lifx/lifx.pro b/lifx/lifx.pro index 0ddd32b8..564f54d1 100644 --- a/lifx/lifx.pro +++ b/lifx/lifx.pro @@ -1,9 +1,9 @@ include(../plugins.pri) -QT += network - TARGET = $$qtLibraryTarget(nymea_devicepluginlifx) +QT += network + SOURCES += \ devicepluginlifx.cpp \ lifx.cpp \ diff --git a/lifx/products.json b/lifx/products.json new file mode 100644 index 00000000..99f8a77a --- /dev/null +++ b/lifx/products.json @@ -0,0 +1,373 @@ +[ + { + "vid": 1, + "name": "LIFX", + "products": [ + { + "pid": 1, + "name": "Original 1000", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 3, + "name": "Color 650", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 10, + "name": "White 800 (Low Voltage)", + "features": { + "color": false, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2700, 6500], + "chain": false + } + }, + { + "pid": 11, + "name": "White 800 (High Voltage)", + "features": { + "color": false, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2700, 6500], + "chain": false + } + }, + { + "pid": 18, + "name": "White 900 BR30 (Low Voltage)", + "features": { + "color": false, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2700, 6500], + "chain": false + } + }, + { + "pid": 20, + "name": "Color 1000 BR30", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 22, + "name": "Color 1000", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 27, + "name": "LIFX A19", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 28, + "name": "LIFX BR30", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 29, + "name": "LIFX+ A19", + "features": { + "color": true, + "infrared": true, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 30, + "name": "LIFX+ BR30", + "features": { + "color": true, + "infrared": true, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 31, + "name": "LIFX Z", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": true, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 32, + "name": "LIFX Z 2", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": true, + "temperature_range": [2500, 9000], + "chain": false, + "min_ext_mz_firmware": 1532997580, + "min_ext_mz_firmware_components": [2, 77] + } + }, + { + "pid": 36, + "name": "LIFX Downlight", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 37, + "name": "LIFX Downlight", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 38, + "name": "LIFX Beam", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": true, + "temperature_range": [2500, 9000], + "chain": false, + "min_ext_mz_firmware": 1532997580, + "min_ext_mz_firmware_components": [2, 77] + } + }, + { + "pid": 43, + "name": "LIFX A19", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 44, + "name": "LIFX BR30", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 45, + "name": "LIFX+ A19", + "features": { + "color": true, + "infrared": true, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 46, + "name": "LIFX+ BR30", + "features": { + "color": true, + "infrared": true, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 49, + "name": "LIFX Mini", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 50, + "name": "LIFX Mini Day and Dusk", + "features": { + "color": false, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [1500, 4000], + "chain": false + } + }, + { + "pid": 51, + "name": "LIFX Mini White", + "features": { + "color": false, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2700, 2700], + "chain": false + } + }, + { + "pid": 52, + "name": "LIFX GU10", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 55, + "name": "LIFX Tile", + "features": { + "color": true, + "infrared": false, + "matrix": true, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": true + } + }, + { + "pid": 57, + "name": "LIFX Candle", + "features": { + "color": true, + "infrared": false, + "matrix": true, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 59, + "name": "LIFX Mini Color", + "features": { + "color": true, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + }, + { + "pid": 60, + "name": "LIFX Mini Day and Dusk", + "features": { + "color": false, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [1500, 4000], + "chain": false + } + }, + { + "pid": 61, + "name": "LIFX Mini White", + "features": { + "color": false, + "infrared": false, + "matrix": false, + "multizone": false, + "temperature_range": [2700, 2700], + "chain": false + } + }, + { + "pid": 68, + "name": "LIFX Candle", + "features": { + "color": true, + "infrared": false, + "matrix": true, + "multizone": false, + "temperature_range": [2500, 9000], + "chain": false + } + } + ] + } +] + diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts new file mode 100644 index 00000000..6d128630 --- /dev/null +++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts @@ -0,0 +1,185 @@ + + + + + Lifx + + Brightness + The name of the ParamType (DeviceClass: dimmableBulb, ActionType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611}) +---------- +The name of the ParamType (DeviceClass: dimmableBulb, EventType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611}) +---------- +The name of the StateType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of DeviceClass dimmableBulb +---------- +The name of the ParamType (DeviceClass: colorBulb, ActionType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863}) +---------- +The name of the ParamType (DeviceClass: colorBulb, EventType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863}) +---------- +The name of the StateType ({8bd20350-0e79-45dc-b68a-84da99356863}) of DeviceClass colorBulb + + + + Brightness changed + The name of the EventType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of DeviceClass dimmableBulb +---------- +The name of the EventType ({8bd20350-0e79-45dc-b68a-84da99356863}) of DeviceClass colorBulb + + + + Color + The name of the ParamType (DeviceClass: colorBulb, ActionType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6}) +---------- +The name of the ParamType (DeviceClass: colorBulb, EventType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6}) +---------- +The name of the StateType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of DeviceClass colorBulb +---------- +The name of the DeviceClass ({12907c9c-e7f0-47f2-bd58-39d52ffdf24e}) + + + + Color changed + The name of the EventType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of DeviceClass colorBulb + + + + Color temperature + The name of the ParamType (DeviceClass: dimmableBulb, ActionType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42}) +---------- +The name of the ParamType (DeviceClass: dimmableBulb, EventType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42}) +---------- +The name of the StateType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of DeviceClass dimmableBulb +---------- +The name of the ParamType (DeviceClass: colorBulb, ActionType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be}) +---------- +The name of the ParamType (DeviceClass: colorBulb, EventType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be}) +---------- +The name of the StateType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of DeviceClass colorBulb + + + + Color temperature changed + The name of the EventType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of DeviceClass dimmableBulb +---------- +The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of DeviceClass colorBulb + + + + Day and Dusk + The name of the DeviceClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e}) + + + + Host address + The name of the ParamType (DeviceClass: dimmableBulb, Type: device, ID: {cc0a765b-a753-4e07-a6e5-47e9272c4346}) +---------- +The name of the ParamType (DeviceClass: colorBulb, Type: device, ID: {fd1c4817-5111-433a-b5b9-fd9f49d4975c}) + + + + Id + The name of the ParamType (DeviceClass: dimmableBulb, Type: device, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed}) +---------- +The name of the ParamType (DeviceClass: colorBulb, Type: device, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0}) + + + + LIFX + The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87}) +---------- +The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992}) + + + + Port + The name of the ParamType (DeviceClass: dimmableBulb, Type: device, ID: {d233d9bf-6662-414d-92f6-dd3e267051b5}) +---------- +The name of the ParamType (DeviceClass: colorBulb, Type: device, ID: {44c13745-300c-491f-b617-3a8d53472998}) + + + + Power + The name of the ParamType (DeviceClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) +---------- +The name of the ParamType (DeviceClass: dimmableBulb, EventType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) +---------- +The name of the StateType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of DeviceClass dimmableBulb +---------- +The name of the ParamType (DeviceClass: colorBulb, ActionType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd}) +---------- +The name of the ParamType (DeviceClass: colorBulb, EventType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd}) +---------- +The name of the StateType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of DeviceClass colorBulb + + + + Power changed + The name of the EventType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of DeviceClass dimmableBulb +---------- +The name of the EventType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of DeviceClass colorBulb + + + + Reachable + The name of the ParamType (DeviceClass: dimmableBulb, EventType: connected, ID: {d33f98ef-5e0f-464c-afed-88b95cc701cd}) +---------- +The name of the StateType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of DeviceClass dimmableBulb +---------- +The name of the ParamType (DeviceClass: colorBulb, EventType: connected, ID: {dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) +---------- +The name of the StateType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of DeviceClass colorBulb + + + + Reachable changed + The name of the EventType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of DeviceClass dimmableBulb +---------- +The name of the EventType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of DeviceClass colorBulb + + + + Set brigtness + The name of the ActionType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of DeviceClass dimmableBulb +---------- +The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of DeviceClass colorBulb + + + + Set color + The name of the ActionType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of DeviceClass colorBulb + + + + Set color temperature + The name of the ActionType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of DeviceClass dimmableBulb +---------- +The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of DeviceClass colorBulb + + + + Set effect + The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of DeviceClass colorBulb + + + + Set power + The name of the ActionType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of DeviceClass dimmableBulb +---------- +The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of DeviceClass colorBulb + + + + effect + The name of the ParamType (DeviceClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) +---------- +The name of the ParamType (DeviceClass: colorBulb, EventType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) +---------- +The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of DeviceClass colorBulb + + + + effect changed + The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of DeviceClass colorBulb + + + + From 227b3abddc1b1eec35abcf3bddee265a136f321e Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Mon, 23 Mar 2020 11:39:56 +0100 Subject: [PATCH 03/15] devices to things --- lifx/README.md | 6 + lifx/devicepluginlifx.cpp | 220 ----------------- lifx/devicepluginlifx.h | 78 ------ lifx/integrationpluginlifx.cpp | 222 ++++++++++++++++++ lifx/integrationpluginlifx.h | 80 +++++++ ...inlifx.json => integrationpluginlifx.json} | 2 +- lifx/lifx.cpp | 50 ++-- lifx/lifx.h | 39 +-- lifx/lifx.pro | 6 +- 9 files changed, 359 insertions(+), 344 deletions(-) delete mode 100644 lifx/devicepluginlifx.cpp delete mode 100644 lifx/devicepluginlifx.h create mode 100644 lifx/integrationpluginlifx.cpp create mode 100644 lifx/integrationpluginlifx.h rename lifx/{devicepluginlifx.json => integrationpluginlifx.json} (99%) diff --git a/lifx/README.md b/lifx/README.md index 088654d0..3ffe992e 100644 --- a/lifx/README.md +++ b/lifx/README.md @@ -1,3 +1,9 @@ # Lifx This plug-in implements the LAN API for Lifx devices + +## Supported Things + +## Requirements + +## More diff --git a/lifx/devicepluginlifx.cpp b/lifx/devicepluginlifx.cpp deleted file mode 100644 index 245a788e..00000000 --- a/lifx/devicepluginlifx.cpp +++ /dev/null @@ -1,220 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* 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 -* This project may also contain libraries licensed under the open source software license GNU GPL v.3. -* Alternatively, this project may be redistributed and/or modified under the terms of the GNU -* Lesser General Public License as published by the Free Software Foundation; version 3. -* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -* See the GNU Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License along with this project. -* If not, see . -* -* For any further details and any questions please contact us under contact@nymea.io -* or see our FAQ/Licensing Information on https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "devicepluginlifx.h" - -#include "devices/device.h" -#include "types/param.h" -#include "plugininfo.h" - -#include -#include -#include - -DevicePluginLifx::DevicePluginLifx() -{ - -} - -void DevicePluginLifx::init() -{ - m_connectedStateTypeIds.insert(colorBulbDeviceClassId, colorBulbConnectedStateTypeId); - m_connectedStateTypeIds.insert(dimmableBulbDeviceClassId, dimmableBulbConnectedStateTypeId); - - m_powerStateTypeIds.insert(colorBulbDeviceClassId, colorBulbPowerStateTypeId); - m_powerStateTypeIds.insert(dimmableBulbDeviceClassId, dimmableBulbPowerStateTypeId); - - m_brightnessStateTypeIds.insert(colorBulbDeviceClassId, colorBulbBrightnessStateTypeId); - m_brightnessStateTypeIds.insert(dimmableBulbDeviceClassId, dimmableBulbBrightnessStateTypeId); -} - -void DevicePluginLifx::discoverDevices(DeviceDiscoveryInfo *info) -{ - if (!m_lifxConnection) { - m_lifxConnection = new Lifx(this); - m_lifxConnection->enable(); - } - - if (info->deviceClassId() == colorBulbDeviceClassId) { - } -} - -void DevicePluginLifx::setupDevice(DeviceSetupInfo *info) -{ - Device *device = info->device(); - - if (!m_lifxConnection) { - m_lifxConnection = new Lifx(this); - m_lifxConnection->enable(); - } - - if (device->deviceClassId() == colorBulbDeviceClassId) { - - info->finish(Device::DeviceErrorNoError); - } -} - -void DevicePluginLifx::postSetupDevice(Device *device) -{ - connect(device, &Device::nameChanged, this, &DevicePluginLifx::onDeviceNameChanged); - - if (!m_pluginTimer) { - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); - connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() { - - }); - } - m_pluginTimer->timeout(); -} - -void DevicePluginLifx::executeAction(DeviceActionInfo *info) -{ - Device *device = info->device(); - Action action = info->action(); - - if (!m_lifxConnection) - return info->finish(Device::DeviceErrorHardwareFailure); - - if (device->deviceClassId() == colorBulbDeviceClassId) { - - if (action.actionTypeId() == colorBulbPowerActionTypeId) { - bool power = action.param(colorBulbPowerActionPowerParamTypeId).value().toBool(); - int requestId = m_lifxConnection->setPower(power); - connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); - m_asyncActions.insert(requestId, info); - } else if (action.actionTypeId() == colorBulbBrightnessActionTypeId) { - - if (!device->stateValue(colorBulbPowerStateTypeId).toBool()) - m_lifxConnection->setPower(true); - - if (m_pendingBrightnessAction.contains(device->id())) { - //Scrap old info and insert new one - m_pendingBrightnessAction.insert(device->id(), info); - } else { - m_pendingBrightnessAction.insert(device->id(), info); - QTimer::singleShot(500, this, [info, this]{ - int brightness = info->action().param(colorBulbBrightnessActionBrightnessParamTypeId).value().toInt(); - m_pendingBrightnessAction.remove(info->device()->id()); - int requestId = m_lifxConnection->setBrightness(brightness); - connect(info, &DeviceActionInfo::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 (!device->stateValue(colorBulbPowerStateTypeId).toBool()) - m_lifxConnection->setPower(true); - int requestId = m_lifxConnection->setColor(color); - connect(info, &DeviceActionInfo::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() * -11.12); - if (!device->stateValue(colorBulbPowerStateTypeId).toBool()) - m_lifxConnection->setPower(true); - int requestId = m_lifxConnection->setColorTemperature(colorTemperature); - connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); - m_asyncActions.insert(requestId, info); - } else { - qCWarning(dcLifx()) << "Unhandled action"; - } - } else if (device->deviceClassId() == dimmableBulbDeviceClassId) { - if (action.actionTypeId() == dimmableBulbPowerActionTypeId) { - bool power = action.param(dimmableBulbPowerActionPowerParamTypeId).value().toBool(); - int requestId = m_lifxConnection->setPower(power); - connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); - } else if (action.actionTypeId() == dimmableBulbBrightnessActionTypeId) { - int brightness = action.param(dimmableBulbBrightnessActionBrightnessParamTypeId).value().toInt(); - if (!device->stateValue(dimmableBulbPowerStateTypeId).toBool()) - m_lifxConnection->setPower(true); - int requestId = m_lifxConnection->setBrightness(brightness); - connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); - m_asyncActions.insert(requestId, info); - } else { - qCWarning(dcLifx()) << "Unhandled action"; - } - } else { - qCWarning(dcLifx()) << "Unhandled device class"; - } -} - -void DevicePluginLifx::deviceRemoved(Device *device) -{ - Q_UNUSED(device) - - if (myDevices().isEmpty()) { - m_lifxConnection->deleteLater(); - m_lifxConnection = nullptr; - } -} - -void DevicePluginLifx::onDeviceNameChanged() -{ - Device *device = static_cast(sender()); - Q_UNUSED(device) -} - -void DevicePluginLifx::onConnectionChanged(bool connected) -{ - Q_UNUSED(connected) -// device->setStateValue(m_connectedStateTypeIds.value(device->deviceClassId()), connected); -} - -void DevicePluginLifx::onRequestExecuted(int requestId, bool success) -{ - if (m_asyncActions.contains(requestId)) { - DeviceActionInfo *info = m_asyncActions.take(requestId); - if (success) { - info->finish(Device::DeviceErrorNoError); - } else { - info->finish(Device::DeviceErrorHardwareFailure); - } - } -} - -/*void DevicePluginLifx::onPowerNotificationReceived(bool status) -{ - Q_UNUSED(status) - Lifx *Lifx = static_cast(sender()); - Device * device = myDevices().findById(m_LifxConnections.key(Lifx)); - if(!device) - return; - - device->setStateValue(m_powerStateTypeIds.value(device->deviceClassId()), status); - -} - -void DevicePluginLifx::onBrightnessNotificationReceived(int percentage) -{ - Q_UNUSED(percentage) - Lifx *lifx = static_cast(sender()); - Device * device = myDevices().findById(m_lifxConnections.key(lifx)); - if(!device) - return; - - device->setStateValue(m_brightnessStateTypeIds.value(device->deviceClassId()), percentage); -}*/ diff --git a/lifx/devicepluginlifx.h b/lifx/devicepluginlifx.h deleted file mode 100644 index 0cfb91d9..00000000 --- a/lifx/devicepluginlifx.h +++ /dev/null @@ -1,78 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* 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 -* This project may also contain libraries licensed under the open source software license GNU GPL v.3. -* Alternatively, this project may be redistributed and/or modified under the terms of the GNU -* Lesser General Public License as published by the Free Software Foundation; version 3. -* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -* See the GNU Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License along with this project. -* If not, see . -* -* For any further details and any questions please contact us under contact@nymea.io -* or see our FAQ/Licensing Information on https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef DEVICEPLUGINLIFX_H -#define DEVICEPLUGINLIFX_H - -#include "devices/deviceplugin.h" -#include "plugintimer.h" -#include "lifx.h" - -#include - -class DevicePluginLifx : public DevicePlugin -{ - Q_OBJECT - - Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginlifx.json") - Q_INTERFACES(DevicePlugin) - -public: - explicit DevicePluginLifx(); - - void init() override; - void discoverDevices(DeviceDiscoveryInfo *info) override; - void setupDevice(DeviceSetupInfo *info) override; - void postSetupDevice(Device *device) override; - void executeAction(DeviceActionInfo *info) override; - void deviceRemoved(Device *device) override; - -private: - PluginTimer *m_pluginTimer = nullptr; - QHash m_asyncActions; - Lifx *m_lifxConnection; - - QHash m_connectedStateTypeIds; - QHash m_powerStateTypeIds; - QHash m_brightnessStateTypeIds; - - QHash m_pendingBrightnessAction; - -private slots: - void onDeviceNameChanged(); - void onConnectionChanged(bool connected); - void onRequestExecuted(int requestId, bool success); - - /*void onPowerNotificationReceived(bool status); - void onBrightnessNotificationReceived(int percentage); - void onColorTemperatureNotificationReceived(int kelvin); - void onRgbNotificationReceived(QRgb rgbColor); - void onNameNotificationReceived(const QString &name);*/ -}; - -#endif // DEVICEPLUGINLIFX_H diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp new file mode 100644 index 00000000..2c73c9d0 --- /dev/null +++ b/lifx/integrationpluginlifx.cpp @@ -0,0 +1,222 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginlifx.h" + +#include "integrations/integrationplugin.h" +#include "types/param.h" +#include "plugininfo.h" + +#include +#include +#include + +IntegrationPluginLifx::IntegrationPluginLifx() +{ + +} + +void IntegrationPluginLifx::init() +{ + m_connectedStateTypeIds.insert(colorBulbThingClassId, colorBulbConnectedStateTypeId); + m_connectedStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbConnectedStateTypeId); + + m_powerStateTypeIds.insert(colorBulbThingClassId, colorBulbPowerStateTypeId); + m_powerStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbPowerStateTypeId); + + m_brightnessStateTypeIds.insert(colorBulbThingClassId, colorBulbBrightnessStateTypeId); + m_brightnessStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbBrightnessStateTypeId); +} + +void IntegrationPluginLifx::discoverThings(ThingDiscoveryInfo *info) +{ + if (!m_lifxConnection) { + m_lifxConnection = new Lifx(this); + m_lifxConnection->enable(); + } + + if (info->thingClassId() == colorBulbThingClassId) { + } +} + +void IntegrationPluginLifx::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + if (!m_lifxConnection) { + m_lifxConnection = new Lifx(this); + m_lifxConnection->enable(); + } + + if (thing->thingClassId() == colorBulbThingClassId) { + + info->finish(Thing::ThingErrorNoError); + } +} + +void IntegrationPluginLifx::postSetupThing(Thing *thing) +{ + connect(thing, &Thing::nameChanged, this, &IntegrationPluginLifx::onDeviceNameChanged); + + if (!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() { + + }); + } + m_pluginTimer->timeout(); +} + +void IntegrationPluginLifx::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + Action action = info->action(); + + if (!m_lifxConnection) + return info->finish(Thing::ThingErrorHardwareFailure); + + if (thing->thingClassId() == colorBulbThingClassId) { + + if (action.actionTypeId() == colorBulbPowerActionTypeId) { + bool power = action.param(colorBulbPowerActionPowerParamTypeId).value().toBool(); + int requestId = m_lifxConnection->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()) + m_lifxConnection->setPower(true); + + if (m_pendingBrightnessAction.contains(thing->id())) { + //Scrap old info and insert new one + m_pendingBrightnessAction.insert(thing->id(), info); + } else { + m_pendingBrightnessAction.insert(thing->id(), info); + QTimer::singleShot(500, this, [info, this]{ + int brightness = info->action().param(colorBulbBrightnessActionBrightnessParamTypeId).value().toInt(); + m_pendingBrightnessAction.remove(info->thing()->id()); + int requestId = m_lifxConnection->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()) + m_lifxConnection->setPower(true); + int requestId = m_lifxConnection->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() * -11.12); + if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()) + m_lifxConnection->setPower(true); + int requestId = m_lifxConnection->setColorTemperature(colorTemperature); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + m_asyncActions.insert(requestId, info); + } else { + qCWarning(dcLifx()) << "Unhandled action"; + } + } else if (thing->thingClassId() == dimmableBulbThingClassId) { + if (action.actionTypeId() == dimmableBulbPowerActionTypeId) { + bool power = action.param(dimmableBulbPowerActionPowerParamTypeId).value().toBool(); + int requestId = m_lifxConnection->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(dimmableBulbPowerStateTypeId).toBool()) + m_lifxConnection->setPower(true); + int requestId = m_lifxConnection->setBrightness(brightness); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + m_asyncActions.insert(requestId, info); + } else { + qCWarning(dcLifx()) << "Unhandled action"; + } + } else { + qCWarning(dcLifx()) << "Unhandled device class"; + } +} + +void IntegrationPluginLifx::thingRemoved(Thing *thing) +{ + Q_UNUSED(thing) + + if (myThings().isEmpty()) { + m_lifxConnection->deleteLater(); + m_lifxConnection = nullptr; + } +} + +void IntegrationPluginLifx::onDeviceNameChanged() +{ + Thing *thing = static_cast(sender()); + Q_UNUSED(thing) +} + +void IntegrationPluginLifx::onConnectionChanged(bool connected) +{ + Q_UNUSED(connected) +// thing->setStateValue(m_connectedStateTypeIds.value(thing->ThingClassId()), connected); +} + +void IntegrationPluginLifx::onRequestExecuted(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); + } + } +} + +/*void IntegrationPluginLifx::onPowerNotificationReceived(bool status) +{ + Q_UNUSED(status) + Lifx *Lifx = static_cast(sender()); + Device * device = myThings().findById(m_LifxConnections.key(Lifx)); + if(!device) + return; + + thing->setStateValue(m_powerStateTypeIds.value(thing->ThingClassId()), status); + +} + +void IntegrationPluginLifx::onBrightnessNotificationReceived(int percentage) +{ + Q_UNUSED(percentage) + Lifx *lifx = static_cast(sender()); + Device * device = myThings().findById(m_lifxConnections.key(lifx)); + if(!device) + return; + + thing->setStateValue(m_brightnessStateTypeIds.value(thing->ThingClassId()), percentage); +}*/ diff --git a/lifx/integrationpluginlifx.h b/lifx/integrationpluginlifx.h new file mode 100644 index 00000000..551ea103 --- /dev/null +++ b/lifx/integrationpluginlifx.h @@ -0,0 +1,80 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINLIFX_H +#define INTEGRATIONPLUGINLIFX_H + +#include "integrations/integrationplugin.h" +#include "plugintimer.h" +#include "lifx.h" + +#include + +class IntegrationPluginLifx : public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginlifx.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginLifx(); + + void init() override; + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; + void thingRemoved(Thing *thing) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + QHash m_asyncActions; + Lifx *m_lifxConnection; + + QHash m_connectedStateTypeIds; + QHash m_powerStateTypeIds; + QHash m_brightnessStateTypeIds; + + QHash m_pendingBrightnessAction; + +private slots: + void onDeviceNameChanged(); + void onConnectionChanged(bool connected); + void onRequestExecuted(int requestId, bool success); + + /*void onPowerNotificationReceived(bool status); + void onBrightnessNotificationReceived(int percentage); + void onColorTemperatureNotificationReceived(int kelvin); + void onRgbNotificationReceived(QRgb rgbColor); + void onNameNotificationReceived(const QString &name);*/ +}; + +#endif // INTEGRATIONPLUGIN_LIFX_H diff --git a/lifx/devicepluginlifx.json b/lifx/integrationpluginlifx.json similarity index 99% rename from lifx/devicepluginlifx.json rename to lifx/integrationpluginlifx.json index 28ba9556..862ee47e 100644 --- a/lifx/devicepluginlifx.json +++ b/lifx/integrationpluginlifx.json @@ -7,7 +7,7 @@ "name": "lifx", "displayName": "LIFX", "id": "e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87", - "deviceClasses": [ + "thingClasses": [ { "id": "12907c9c-e7f0-47f2-bd58-39d52ffdf24e", "name": "colorBulb", diff --git a/lifx/lifx.cpp b/lifx/lifx.cpp index 24a037d9..859a1bbf 100644 --- a/lifx/lifx.cpp +++ b/lifx/lifx.cpp @@ -1,36 +1,38 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 +* 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 -* This project may also contain libraries licensed under the open source software license GNU GPL v.3. -* 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. +* 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 . +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . * -* For any further details and any questions please contact us under contact@nymea.io -* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* 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 "lifx.h" #include "extern-plugininfo.h" #include -#include Lifx::Lifx(QObject *parent) : QObject(parent) @@ -92,7 +94,7 @@ int Lifx::setColorTemperature(int mirad, int msFadeTime) { Q_UNUSED(mirad) Q_UNUSED(msFadeTime) - int requestId = static_cast(QRandomGenerator::global()->generate()); + int requestId = qrand(); return requestId; } @@ -101,7 +103,7 @@ int Lifx::setColor(QColor color, int msFadeTime) { Q_UNUSED(color) Q_UNUSED(msFadeTime) - int requestId = static_cast(QRandomGenerator::global()->generate()); + int requestId = qrand(); return requestId; } @@ -110,7 +112,7 @@ int Lifx::setBrightness(int percentage, int msFadeTime) { Q_UNUSED(percentage) Q_UNUSED(msFadeTime) - int requestId = static_cast(QRandomGenerator::global()->generate()); + int requestId = qrand(); Message message; sendMessage(message); return requestId; @@ -120,21 +122,21 @@ int Lifx::setPower(bool power, int msFadeTime) { Q_UNUSED(power) Q_UNUSED(msFadeTime) - int requestId = static_cast(QRandomGenerator::global()->generate()); + int requestId = qrand(); return requestId; } int Lifx::flash() { - int requestId = static_cast(QRandomGenerator::global()->generate()); + int requestId = qrand(); return requestId; } int Lifx::flash15s() { - int requestId = static_cast(QRandomGenerator::global()->generate()); + int requestId = qrand(); return requestId; } diff --git a/lifx/lifx.h b/lifx/lifx.h index c0aabe83..ceec40a8 100644 --- a/lifx/lifx.h +++ b/lifx/lifx.h @@ -1,30 +1,34 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 +* 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 -* This project may also contain libraries licensed under the open source software license GNU GPL v.3. -* 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. +* 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 . +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . * -* For any further details and any questions please contact us under contact@nymea.io -* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* 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 LIFX_H #define LIFX_H @@ -34,7 +38,6 @@ #include #include "network/networkaccessmanager.h" -#include "devices/device.h" #include diff --git a/lifx/lifx.pro b/lifx/lifx.pro index 564f54d1..02a48086 100644 --- a/lifx/lifx.pro +++ b/lifx/lifx.pro @@ -1,14 +1,14 @@ include(../plugins.pri) -TARGET = $$qtLibraryTarget(nymea_devicepluginlifx) +TARGET = $$qtLibraryTarget(nymea_integrationpluginlifx) QT += network SOURCES += \ - devicepluginlifx.cpp \ + integrationpluginlifx.cpp \ lifx.cpp \ HEADERS += \ - devicepluginlifx.h \ + integrationpluginlifx.h \ lifx.h \ From 9c90a612d12589363b6a260b0a4e5c7e6575725f Mon Sep 17 00:00:00 2001 From: Boernsman Date: Mon, 29 Jun 2020 11:17:41 +0200 Subject: [PATCH 04/15] added german translation --- lifx/integrationpluginlifx.json | 13 +- lifx/lifx.png | Bin 0 -> 7522 bytes lifx/lifx.pro | 2 - lifx/meta.json | 13 ++ ...4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts | 188 ++++++++++++++++++ ...0ee30-79e2-447b-8dcc-c34470f41992-en_US.ts | 127 ++++++------ 6 files changed, 273 insertions(+), 70 deletions(-) create mode 100644 lifx/lifx.png create mode 100644 lifx/meta.json create mode 100644 lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts diff --git a/lifx/integrationpluginlifx.json b/lifx/integrationpluginlifx.json index 862ee47e..74007705 100644 --- a/lifx/integrationpluginlifx.json +++ b/lifx/integrationpluginlifx.json @@ -45,7 +45,8 @@ "displayName": "Reachable", "displayNameEvent": "Reachable changed", "defaultValue": false, - "type": "bool" + "type": "bool", + "cached": false }, { "id": "12de3f8f-2454-4057-aa12-9290296fdbdd", @@ -85,7 +86,7 @@ "name": "brightness", "displayName": "Brightness", "displayNameEvent": "Brightness changed", - "displayNameAction": "Set brigtness", + "displayNameAction": "Set brightness", "type": "int", "unit": "Percentage", "defaultValue": 0, @@ -96,8 +97,8 @@ { "id": "65f88396-2958-480e-b0be-c4695400a343", "name": "effect", - "displayName": "effect", - "displayNameEvent": "effect changed", + "displayName": "Effect", + "displayNameEvent": "Effect changed", "displayNameAction": "Set effect", "type": "QString", "defaultValue": "none", @@ -134,7 +135,7 @@ { "id": "f157a97b-3fe5-4d9e-b5e3-5636f80d46ed", "name": "id", - "displayName": "Id", + "displayName": "ID", "type" : "QString", "readOnly": true } @@ -163,7 +164,7 @@ "name": "brightness", "displayName": "Brightness", "displayNameEvent": "Brightness changed", - "displayNameAction": "Set brigtness", + "displayNameAction": "Set brightness", "type": "int", "unit": "Percentage", "defaultValue": 0, diff --git a/lifx/lifx.png b/lifx/lifx.png new file mode 100644 index 0000000000000000000000000000000000000000..89fbeb253ecf2294929ed80d3eb9af11818dbb2b GIT binary patch literal 7522 zcmd6MWl$STxHUzB1}hpUZY@rc;!xZv6iRTnyf_4kLy)$(yE_E;q5%rUJy`JucM25k z<$nLa|99q@m6_d{-Luc^dFGt2+L}s0JP;ll8X8bVSzZSX?FHBKI28x*+*%to&pdZ8 zJam-aqt#5%?mtg3?PN7%(a`GS@EBK2M1f+?$ zDwxT7+Qj;zAQl{45|S^rtixlT$N#PmsdpOhyKgG}*0OFFM91SZjPzOqYwxeCD~(#^ z4U~g1|4)U07K8FO&frKj^MTDjdyX3(_Vyk^FHf((lSbeEu_H_$j(?opgiFRR<)&>6 z$GGob3&uu@C7|K{W#roc;14<&5)TAhy=y;cdvd>}3V{=9H>olNa#G*!#(Z?mRqxRw z*5SsLNk5jKIKOIQuMA1~H$*1OIDLPxRCt=N9yP~FhKze_tIFn~QSo^N0f#!din%iB znPZ(Nf0rzwKZ$OQ+J>G3)%(G3JX5`{=g?U>dUB;A3Px-nWn(SP|wrr}91b&G3;1 z%u0e53)ebNh6yI5PeE_Udj$!3VSzXQhLGeqB!a=-`aZTU%{=U-s6>1yNV&68=+V;; zpLDEE%eJ}-xwpuJ8y}n_^B)Y$%1b^+5Dpy3u{^CwEMAEfVxhwJcF7@YF)Et#IzD0$ zb(JrmdQJZzN5y~2Nj2Y`5{*1iUA{bJkwKqlB_1``NM@r7`{h;c?t$bolHOK#Vew_`na2)>P!5i+dnhbx1WP3rX{GOo! zk;ycE8p_^?MZdwk<$ZLG*jSx=a~0Jx99}!%GNk`Dy3QLvaR}DV`3$zLQEf8Q3(_+U zhD1c4<_0#gH7+mnm-d%?xiF>XQj_J=0n6*S0HRW3KdX}4Gk}|&)yS<@xspHdv`v4_)JZKhJ$nW`91sVy|^w9~obD))seN$E(HhtQM6Wi3D}M zo@O=iYz_OX&OPqKWa0E^p=#{H1smXV2Df3lKDD}6q#^7ds;ej=grRZfmQ3+blD z&ZD%fcC<_O#bB(m3X|=|Go7vfa5qHdQk@piDDo3^Uo=H4&ScR0@ZoKbvv59^I40=2L-<)@?>f*RyYck@J?6t62LXeQnOaulI1yx(Bj%=skl3wl+$T zE&~0w9GdcJ0kTMZ4G+76D z8#uoVR_&8Y$Z)MgWVFk~BPcYyT7SKPIB0nvS)hq`aaPZc_xDLl#S-nVgF&#d$@-F> zL5?gif0sYGZol)?2oYPl@CWD5a<M*(ps_D5GB*7rx&b|&5c;5L@DP}V4j6PP6McyAVb97?CP)^uXy@b z=s>GQ1Ee{k<1WAXtu_0Z`-iaR9>L*#Lkc=W{w<_7Q%NO%lM)Fjl`A@q(4O#B7L&&5 zJZ4THnMaSzQ)h$tg#r=2hoUYA21P%eOPch*pK|6$2!EYbEJV6y!k-FaJzMB$JT zw=_1*A`plms;b^uU#GeQDTfD#_BD?rn_)8xg>K7e1^dNNdZ#2#GwKl{vWTD7b-i!m zstI2%6pSTVO@cx;Vq9In(amFmpV%IHYJXhcudoIwv4TV~6}o|?d(|nJSEuQOTfVgg zCVf?hs~!}1=~8qq32IeiGHj=6oJn7NO_I|3J~(`qy}2T}I#ufA6PF0p?>z>eoCY-E zt~6Lj8DFTEw4fI!X1pk<{X`n(?&qE;?of^ymAW00JYp8Y|GiM4af42NBx}+_&v}w< zlXKgCPCZBX_(%sl`3{fw{ynWp$GFtifokdrnS#CM6r_yTchXt7&!1;X?%(tww6;LW zHC}15DK=IJzG1Hxc{UKJVnv?#ULDCyVas9AC+oVYzt~ofBFRyN!Y%yfmS^4nEPKW5hV&SNg%OTaBeXR9gd#(Jvo5Cp<_LCfhef>gPb-Ufh3gOBZ+*PD+60_ivm zqNskK@<)Iz5(+;7OK#AXX!#gScm_}s&`iwMbA)8ifhD;R$D9eeW2NC2Rf$7}9HSe| z+S<${n`@y5#8X@xXEiMf4wBgTkzwbh?}@6y9AL&_Vc`?WO?>EPvvTDn+OW=CI(_bt zh4R{QfnAZ;XAN|-7!w~N;Sd8hSomv3Nx0_s7rCu5*rc~ve-#5Y_Q*>#OBK?z#dYH9nqcQ-U^|$#wS~ct+oA7uW2joyS_5!88@pDi~#9&9Ww! zrhMcputM9j@mdMM1Ii#6LOVFd+!t4sD5**0G^{91#u^r34!L|eqop$1Hs*XY!`9Ca%5hYjPh1WPyA9M}~X$0VZg3b1ijARLfr#E}e6H4;faJYYRsm=WXz;fv$v@%;q) zlKjRH1M2?vdqXsLEMr~2zo(08IU<#K_?4qKC->`_%Q-L~Kw5AA{4-YFz3#-e--qq77-L?2HYPv0}{^J$+5R z=8F-oCNf+uRKLdAX!OE0ZM_R*Y;g&30e!Fe7{9@`ew@1OhZ%JrVETrnSz5@TM@U}` z+B2j^8WT{eGv?b*OtGW!xlu<{`Hj$ToZmKWY*QVhH@)>W!Oc}mrpTY3Kzv}kv{CYa z(A+kcI}8k{xg^=}infV1xS^TZFXoQsNTNf}`u*@vciXZVn__%=t?NqX6Ms^933tkH zcq%U+%DZ7FuoarC(B4Ta)7Ox{j?dZ=i@R3!6+$7Zy^B!E*MJ64GW#JG_V`BapGnwH zO+&9U3|)Wf4`L>YoNa{LSuc{cY=$7fS(!TFYDQaKTefzp$1PAu{wLKGh0u5&r5fJ#{ za-J_@^xOkKdZzK}Vn-jiF-tk|<$)An71f=8tR)s`#*1wlaWvXCyu)7J3N7TiXSyiQ z6VgxZ`Z3e0$0j)AoT#xTIfO6%Uol4tA^Pps$0;bIhC~eh?j}XC?)H2@wSF0*Z`C0) zyL%j&FHWQgSh+v{=Bvramt=12jF`-`Sh++^5f7sSTly#6f+-Ojn9Sy`>v6tzjL92f z#eHBS-V?BpZ}1qa7ap>)beAxlv+0aerw<@#|9NiwfcNEBeE%VyP8_H+g48wPW#nqLE2**_6AJCvTx|VkNrU&1PkB z|3VfPRh_bV)Cl|QLuslN$upGGo{L?HL|t62f4O+*UhXWNbcbnfIw>$T3)$JEG3a+{ zxW>$1$?TxFbV!1=FGRaQ$AWuMgfFYFoPgAx^dN*W+CpJFo-DE}5G_tcDr!OB$HaU8 z4jkllqh`9g*jVkfTl`8FLh)P|hTt|lXe?f7NoYj9o0;WX`Fd>Q_10jSKhP~p??ukU z{OHf1<2UptJKP=h495lx3x~g|P8|)ej*TXA3J&dXzy?bmABb}|<^AuC9h~R^{_|`Z zmtlIj?ks%qm>crt^_j+yS7)LbHBctao-P99i>1%vQpGFBG2}I;I3811j4T+i0{6VA z7JKK@YQx08r3i`cy1)iOk&4mET;|?`k7xFNdEqXLHve1WE1`++4_SiCkZXEwz29Xc zO#T@J+z1$~SW7|iU<%OW7gCESG$8%_Lph%xYm!YjU~ZLHQG$Iok3ted&1g{`u$E9? zqV45Eps<5#LT@iA4ofZm@t~FM{M7vTbHYfGHKR-cYBas%D99PJYG~cIss+}Ed##F2 zO{-OElH`@I=MW}&3er6mG@?hgqv)8M$G*Ptb`?H|GHGYk&yEL`yCV}3A3x0tN(HBv zd5zssJegLn4(#gsA{CwH^8C-Cs4kx~-uKg8o2U|J`oJ>NUwjWegN_!{J&s_~7&%<2 zb*8twG~rb|gV9B+Bt+(e)A%C8V=I+Uz0AS`SF#o|Jm`ieU-`I2#Snyo&eBD)*doE7rwf=GtDyo5jQKY{b zTCq{2JnUxqE=5zR08xlIIL-OOR}>e~`n%Zn?SDWWx=9~-+YBDhV-M9u{zaJx<}QeK zO1L%L?^sD1lvdIay&Zr5K;`p#cWcFU(>9}h%@O|blg8<{JEHAo%f_+qq)z2DPjpn= zUZO5?^j|MMZXN-yKRFI+w36yoBZrGA_F1*F7^ZQyrSJJjthAwSh;Dz}mlxP#LbM-L zkk{TfcoG#^Br+}TMaCctkr||~na)87%4Ypz7_af+=jvmSP~Evum8OOn9YrkUbBByo zUejsR1#7W2v8@1+8BU&->Q~*q`25EUGJLlimjB+vr^42%JC3U_9y++q9&9AJoDAZQ zdPm=e6+Qf65(!BJ;GY73=FqKLuFH`*d4w zgt=92oQDBd8f3Y66UHrYzwXak)JjlOHfER+gof{5#h=@_>6C-hf2ExbH9vwrOJO!N zzujFb%(nLthAigH!IvGrEE7peEHuKSN7b6k%wf`cGqdB3+2=yk@VY8!+in9~r&aPP zFf!>uFh`iRJy*;!kIn69&Ah&IlbLT|p$|>crjiWrk@kX8DQ8XIK1lo9)~LlmKCJ-& zQ1Stl&iYxkbKf*HPxsds-6GWVs2FWU;hPAB4Okg0Vy=)UN2sd}@W>%ERQ31UR*ZyOBZL?9fkm)Q_8npXg zeS7l+{pARv*cL`^I2RQ(%XXC)A?uqj&3fg6qmqtZ>zH6l^V@}Q9)jBum1Sf@iEJ^> z$Z{~DL9RbhPaly3p;v-7qb$TRz5lCf+<3vQ_`3YuC$|=J04d(7zM}G{{C|EY+w(cx zjuj^mq&ilZ;=VD7_;>8J$uzOejl@U+XY=e6Vnt8Kq<^=US&cm*8g-CZwCS$Jj=M${ z3F6AL2luM`dbOqjviq!F+(o)vL7>JbKny*^QU(cCf&+bJV>rJqRHRhVgo^yxTKr`T5IkN~@Oo(?j5@ zboa8>1pHe_(Ybi@!d!p)&qFEl4x%zyE&X05ktX9EE7ms{spf(mrAzUD z$lo1!G&w$;M+BKA3sb)Xh3k8R{WHwIOil+4xfB?UE5&==Jh(He=oasHr3YF%QgyEC z=J{%@Md!Y4S)@xPD*5xB$G?qYOG@xpJiNj|Q0_gcRf!>1QGG15nGGfPEj{K8X`9Q% zX}T+C)*_1zW^k?KlGI81eLF+m5$&!2E$k`k}lU1zYD3<1&_?4E7QiZvzV zO9R}gcDcrC^%HlNSRS-)s{=U1PfE*X@@H8yh9ET1l=cOqUo5$ICEw>XL071cYW0fY zh%v=$pZ(@VrOz zMU~l8$+ao!wI@S~=k0Ibbn%#1XCV6Y=1&JQ!%mt4rq^W;ei{!V3<7C%Kg_n5{H$(7 z$0gtk3H?3SA-0wTq9HV|msB-Mm%0-)*!zBu1sCEs@Xet>?<4+jB3S4jtae7oQHKVx zyX3Y@0~mmzTIjauccPb+gZUN4p+X;O;he17mlSDO+}}-l4ZUFD3a5?rXypt30b}aj z2cd|9%*ZNPLrQyrr%LZ3Lgjn_rm6*j%hjyj*|GF+9@FPfuO9oRK3_F_H~rt;SST6` z_#e!$18jDs_s91*bg+rmx<(Eb3(kgL@dvmyNdY+?q11Y38aIR6E?XJXwu=U zT;nUGY4z85HGDFl^~>LP$DR7S&G-gY&UI5EJLBM28WuM_RA+btfefsQCkFBvL_>!h z<1CfD1DDxff_lg+^l19<4OhK<9clAT>uz+%fI$Hu59gI385L6E>GCtaXvV#_ViIqd z)j1a!UiU7SUy1!br(WH@(e|uIZ9PMVORHeMSKu|{N)$c%8pUAm%4m97#8Ps)oLi$Z zuXczsDDNn|=8)b z2rKu4uR#H7TWU8UhAfqHHEg3(Dk`zH^=y*(}w z_ie-pP2vH}>CdK!x#}IbX$93c=xH*H@*Ddv4ioS;BooAib(>kHHw9^P?)ASZbwnrZ zss{Ypg6p#Q>d#w`x2TwFlWG@*bK$@HZJbqD;owS!6IliR|5;H2%K1A{CvMc?U<&sk zW(up1n(mVp-sdXg1fW()B=ZZ>pL8J~X}B~#6JUg|IC@3a)(!+_dP}x8=^aq+qo|hp zwPK#_nL=XaYDC}gWjo(T{5!&jBT@MqrdYP@C}wf)4*)IjvrhgWiE;@dlWSZf{X$_3 zj9QLAKMQC(yZZW{B)X#-I*U20_ErC+zQXqr^5OvMr&o++v2@mi&iVD<2HbeG|9c^`j`>8-v0*{09~2<{?31FYC}_&pyf+X1ALBwlNB{r; literal 0 HcmV?d00001 diff --git a/lifx/lifx.pro b/lifx/lifx.pro index 02a48086..9dea64c2 100644 --- a/lifx/lifx.pro +++ b/lifx/lifx.pro @@ -1,7 +1,5 @@ include(../plugins.pri) -TARGET = $$qtLibraryTarget(nymea_integrationpluginlifx) - QT += network SOURCES += \ diff --git a/lifx/meta.json b/lifx/meta.json new file mode 100644 index 00000000..5a34dd9c --- /dev/null +++ b/lifx/meta.json @@ -0,0 +1,13 @@ +{ + "title": "LIFX", + "tagline": "Control LIFX light bulbs.", + "icon": "lifx.png", + "stability": "consumer", + "offline": true, + "technologies": [ + "network" + ], + "categories": [ + "light" + ] +} diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts new file mode 100644 index 00000000..27a145ba --- /dev/null +++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts @@ -0,0 +1,188 @@ + + + + + Lifx + + Brightness + The name of the ParamType (ThingClass: dimmableBulb, ActionType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611}) +---------- +The name of the ParamType (ThingClass: dimmableBulb, EventType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611}) +---------- +The name of the StateType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb +---------- +The name of the ParamType (ThingClass: colorBulb, ActionType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863}) +---------- +The name of the ParamType (ThingClass: colorBulb, EventType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863}) +---------- +The name of the StateType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb + Helligkeit + + + Brightness changed + The name of the EventType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb +---------- +The name of the EventType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb + Helligkeit geändert + + + Color + The name of the ParamType (ThingClass: colorBulb, ActionType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6}) +---------- +The name of the ParamType (ThingClass: colorBulb, EventType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6}) +---------- +The name of the StateType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb +---------- +The name of the ThingClass ({12907c9c-e7f0-47f2-bd58-39d52ffdf24e}) + Farbe + + + Color changed + The name of the EventType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb + Farbe geändert + + + Color temperature + The name of the ParamType (ThingClass: dimmableBulb, ActionType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42}) +---------- +The name of the ParamType (ThingClass: dimmableBulb, EventType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42}) +---------- +The name of the StateType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb +---------- +The name of the ParamType (ThingClass: colorBulb, ActionType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be}) +---------- +The name of the ParamType (ThingClass: colorBulb, EventType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be}) +---------- +The name of the StateType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb + Farbtemperatur + + + Color temperature changed + The name of the EventType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb +---------- +The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb + Farbtemperatur geändert + + + Day and Dusk + The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e}) + Tag und Sonnenaufgang + + + Host address + The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {cc0a765b-a753-4e07-a6e5-47e9272c4346}) +---------- +The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-5111-433a-b5b9-fd9f49d4975c}) + Adresse + + + Id + The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0}) + ID + + + LIFX + The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87}) +---------- +The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992}) + LIFX + + + Port + The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {d233d9bf-6662-414d-92f6-dd3e267051b5}) +---------- +The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {44c13745-300c-491f-b617-3a8d53472998}) + Port + + + Power + The name of the ParamType (ThingClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) +---------- +The name of the ParamType (ThingClass: dimmableBulb, EventType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) +---------- +The name of the StateType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb +---------- +The name of the ParamType (ThingClass: colorBulb, ActionType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd}) +---------- +The name of the ParamType (ThingClass: colorBulb, EventType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd}) +---------- +The name of the StateType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb + Eingeschalten + + + Power changed + The name of the EventType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb +---------- +The name of the EventType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb + Eingeschalten changed + + + Reachable + The name of the ParamType (ThingClass: dimmableBulb, EventType: connected, ID: {d33f98ef-5e0f-464c-afed-88b95cc701cd}) +---------- +The name of the StateType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of ThingClass dimmableBulb +---------- +The name of the ParamType (ThingClass: colorBulb, EventType: connected, ID: {dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) +---------- +The name of the StateType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of ThingClass colorBulb + Erreichbar + + + Reachable changed + The name of the EventType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of ThingClass dimmableBulb +---------- +The name of the EventType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of ThingClass colorBulb + Erreichbar changed + + + Set color + The name of the ActionType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb + Setze Farbe + + + Set color temperature + The name of the ActionType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb +---------- +The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb + Setze Farbtemperatur + + + Set effect + The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + Setze Effekt + + + Set power + The name of the ActionType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb +---------- +The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb + Setze Eingeschalten + + + Effect + The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) +---------- +The name of the ParamType (ThingClass: colorBulb, EventType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) +---------- +The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + Effekt + + + Effect changed + The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + Effekt geändert + + + ID + The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed}) + ID + + + Set brightness + The name of the ActionType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb +---------- +The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb + Setze Helligkeit + + + diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts index 6d128630..722805c7 100644 --- a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts +++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts @@ -5,81 +5,79 @@ Lifx Brightness - The name of the ParamType (DeviceClass: dimmableBulb, ActionType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611}) + The name of the ParamType (ThingClass: dimmableBulb, ActionType: brightness, ID: {a0a1bdcc-2761-4d90-85d1-5ce887546611}) ---------- -The name of the ParamType (DeviceClass: dimmableBulb, EventType: 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 DeviceClass dimmableBulb +The name of the StateType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb ---------- -The name of the ParamType (DeviceClass: colorBulb, ActionType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863}) +The name of the ParamType (ThingClass: colorBulb, ActionType: brightness, ID: {8bd20350-0e79-45dc-b68a-84da99356863}) ---------- -The name of the ParamType (DeviceClass: colorBulb, EventType: 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 DeviceClass colorBulb +The name of the StateType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb Brightness changed - The name of the EventType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of DeviceClass dimmableBulb + The name of the EventType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb ---------- -The name of the EventType ({8bd20350-0e79-45dc-b68a-84da99356863}) of DeviceClass colorBulb +The name of the EventType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb Color - The name of the ParamType (DeviceClass: colorBulb, ActionType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6}) + The name of the ParamType (ThingClass: colorBulb, ActionType: color, ID: {a47d8164-5023-4ffb-8298-73293e93e7f6}) ---------- -The name of the ParamType (DeviceClass: colorBulb, EventType: 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 DeviceClass colorBulb +The name of the StateType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb ---------- -The name of the DeviceClass ({12907c9c-e7f0-47f2-bd58-39d52ffdf24e}) +The name of the ThingClass ({12907c9c-e7f0-47f2-bd58-39d52ffdf24e}) Color changed - The name of the EventType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of DeviceClass colorBulb + The name of the EventType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb Color temperature - The name of the ParamType (DeviceClass: dimmableBulb, ActionType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42}) + The name of the ParamType (ThingClass: dimmableBulb, ActionType: colorTemperature, ID: {95797dee-b836-4047-98d5-afbbce4f8c42}) ---------- -The name of the ParamType (DeviceClass: dimmableBulb, EventType: 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 DeviceClass dimmableBulb +The name of the StateType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb ---------- -The name of the ParamType (DeviceClass: colorBulb, ActionType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be}) +The name of the ParamType (ThingClass: colorBulb, ActionType: colorTemperature, ID: {dd7d7e70-5552-4531-8789-2d0f750488be}) ---------- -The name of the ParamType (DeviceClass: colorBulb, EventType: 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 DeviceClass colorBulb +The name of the StateType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb Color temperature changed - The name of the EventType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of DeviceClass dimmableBulb + The name of the EventType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb ---------- -The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of DeviceClass colorBulb +The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb Day and Dusk - The name of the DeviceClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e}) + The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e}) Host address - The name of the ParamType (DeviceClass: dimmableBulb, Type: device, ID: {cc0a765b-a753-4e07-a6e5-47e9272c4346}) + The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {cc0a765b-a753-4e07-a6e5-47e9272c4346}) ---------- -The name of the ParamType (DeviceClass: colorBulb, Type: device, ID: {fd1c4817-5111-433a-b5b9-fd9f49d4975c}) +The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-5111-433a-b5b9-fd9f49d4975c}) Id - The name of the ParamType (DeviceClass: dimmableBulb, Type: device, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed}) ----------- -The name of the ParamType (DeviceClass: colorBulb, Type: device, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0}) + The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0}) @@ -91,94 +89,99 @@ The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992}) Port - The name of the ParamType (DeviceClass: dimmableBulb, Type: device, ID: {d233d9bf-6662-414d-92f6-dd3e267051b5}) + The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {d233d9bf-6662-414d-92f6-dd3e267051b5}) ---------- -The name of the ParamType (DeviceClass: colorBulb, Type: device, ID: {44c13745-300c-491f-b617-3a8d53472998}) +The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {44c13745-300c-491f-b617-3a8d53472998}) Power - The name of the ParamType (DeviceClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) + The name of the ParamType (ThingClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) ---------- -The name of the ParamType (DeviceClass: dimmableBulb, EventType: 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 DeviceClass dimmableBulb +The name of the StateType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb ---------- -The name of the ParamType (DeviceClass: colorBulb, ActionType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd}) +The name of the ParamType (ThingClass: colorBulb, ActionType: power, ID: {12de3f8f-2454-4057-aa12-9290296fdbdd}) ---------- -The name of the ParamType (DeviceClass: colorBulb, EventType: 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 DeviceClass colorBulb +The name of the StateType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb Power changed - The name of the EventType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of DeviceClass dimmableBulb + The name of the EventType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb ---------- -The name of the EventType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of DeviceClass colorBulb +The name of the EventType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb Reachable - The name of the ParamType (DeviceClass: dimmableBulb, EventType: connected, ID: {d33f98ef-5e0f-464c-afed-88b95cc701cd}) + 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 DeviceClass dimmableBulb +The name of the StateType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of ThingClass dimmableBulb ---------- -The name of the ParamType (DeviceClass: colorBulb, EventType: connected, ID: {dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) +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 DeviceClass colorBulb +The name of the StateType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of ThingClass colorBulb Reachable changed - The name of the EventType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of DeviceClass dimmableBulb + The name of the EventType ({d33f98ef-5e0f-464c-afed-88b95cc701cd}) of ThingClass dimmableBulb ---------- -The name of the EventType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of DeviceClass colorBulb - - - - Set brigtness - The name of the ActionType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of DeviceClass dimmableBulb ----------- -The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of DeviceClass colorBulb +The name of the EventType ({dc4c1640-90f3-4fe0-af9b-db7fa105f18a}) of ThingClass colorBulb Set color - The name of the ActionType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of DeviceClass colorBulb + The name of the ActionType ({a47d8164-5023-4ffb-8298-73293e93e7f6}) of ThingClass colorBulb Set color temperature - The name of the ActionType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of DeviceClass dimmableBulb + The name of the ActionType ({95797dee-b836-4047-98d5-afbbce4f8c42}) of ThingClass dimmableBulb ---------- -The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of DeviceClass colorBulb +The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass colorBulb Set effect - The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of DeviceClass colorBulb + The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb Set power - The name of the ActionType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of DeviceClass dimmableBulb + The name of the ActionType ({9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) of ThingClass dimmableBulb ---------- -The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of DeviceClass colorBulb +The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClass colorBulb - effect - The name of the ParamType (DeviceClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) + Effect + The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- -The name of the ParamType (DeviceClass: colorBulb, EventType: 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 DeviceClass colorBulb +The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb - effect changed - The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of DeviceClass colorBulb + Effect changed + The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + + + + ID + The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed}) + + + + Set brightness + The name of the ActionType ({a0a1bdcc-2761-4d90-85d1-5ce887546611}) of ThingClass dimmableBulb +---------- +The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb From e54c35bdcc7829c5044883a4293cf6b3f3ada931 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Mon, 29 Jun 2020 15:03:14 +0200 Subject: [PATCH 05/15] some more work on the api --- lifx/integrationpluginlifx.cpp | 4 ++ lifx/lifx.cpp | 72 ++++++++++++++++++++++++++++------ lifx/lifx.h | 16 +++++++- 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp index 2c73c9d0..d4f49add 100644 --- a/lifx/integrationpluginlifx.cpp +++ b/lifx/integrationpluginlifx.cpp @@ -63,6 +63,10 @@ void IntegrationPluginLifx::discoverThings(ThingDiscoveryInfo *info) } if (info->thingClassId() == colorBulbThingClassId) { + } else if () { + + } else { + } } diff --git a/lifx/lifx.cpp b/lifx/lifx.cpp index 859a1bbf..b3193507 100644 --- a/lifx/lifx.cpp +++ b/lifx/lifx.cpp @@ -33,6 +33,10 @@ #include "extern-plugininfo.h" #include +#include +#include +#include +#include Lifx::Lifx(QObject *parent) : QObject(parent) @@ -40,6 +44,36 @@ Lifx::Lifx(QObject *parent) : m_reconnectTimer = new QTimer(this); m_reconnectTimer->setSingleShot(true); connect(m_reconnectTimer, &QTimer::timeout, this, &Lifx::onReconnectTimer); + m_clientId = qrand(); + + QFile file; + file.setFileName("/tmp/products.json"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCWarning(dcLifx()) << "Could not open products file" << file.errorString(); + } + QJsonDocument productsJson = QJsonDocument::fromJson(file.readAll()); + file.close(); + + if (!productsJson.isArray()) { + qCWarning(dcLifx()) << "Products JSON is not a valid array"; + } + QJsonArray productsArray = productsJson.array().first().toObject().value("products").toArray(); + foreach (QJsonValue value, productsArray) { + QJsonObject object = value.toObject(); + 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); + } } Lifx::~Lifx() @@ -143,42 +177,56 @@ int Lifx::flash15s() void Lifx::sendMessage(const Lifx::Message &message) { - QByteArray data; + 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) - data.append(protocol >> 8); - data.append(protocol & 0xff); + header.append(protocol >> 8); + header.append(protocol & 0xff); //Source identifier: unique value set by the client, used by responses - data.append("nyma"); + header.append(m_clientId); // -- FRAME ADDRESS -- //Target - frame address starts with 64 bits //ADD RESERVED SECTION a reserved section of 48 bits (6 bytes) - data.append(6, '0'); + header.append(6, 0x00); //that must be all zeros. //ADD ACK and RES + header.append(2, 0x01); //ADD SEQUENCE NUMBER 1Byte - data.append(m_sequenceNumber + 1); + header.append(m_sequenceNumber++); + + //Protocol header. which begins with 64 reserved bits (8 bytes). Set these all to zero. + header.append(8, 0x00); //that must be all zeros. //ADD MESSAGE TYPE + header.append(static_cast(LightMessages::SetColor)); + + // Finally another reserved field of 16 bits (2 bytes). + header.append(2, 0x00); //ADD SIZE - data.append(((static_cast(data.length()+1) & 0xff00) >> 8)); - data.append((static_cast(data.length()+1) & 0x00ff)); + header.append(((static_cast(header.length()+1) & 0xff00) >> 8)); + header.append((static_cast(header.length()+1) & 0x00ff)); //Finally another reserved field of 16 bits (2 bytes). - data.append(2, '0'); - data = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); - m_socket->writeDatagram(data, m_host, m_port); - Q_UNUSED(message) + header.append(2, '0'); + + QByteArray payload; + + QByteArray message; + message.append(header); + message.append(payload); + message.append(message.length()); + //header = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); + m_socket->writeDatagram(message, m_host, m_port); } void Lifx::onStateChanged(QAbstractSocket::SocketState state) diff --git a/lifx/lifx.h b/lifx/lifx.h index ceec40a8..708a9c21 100644 --- a/lifx/lifx.h +++ b/lifx/lifx.h @@ -111,6 +111,18 @@ public: SetInfrared = 122 }; + struct LifxProduct { + int pid; + QString name; + bool color; + bool infrared; + bool matrix; + bool multizone; + uint minColorTemperature; + uint maxColorTemperature; + bool chain; + }; + explicit Lifx(QObject *parent = nullptr); ~Lifx(); bool enable(); @@ -124,11 +136,13 @@ public: int flash15s(); private: + quint32 m_clientId = 0; QTimer *m_reconnectTimer = nullptr; QUdpSocket *m_socket = nullptr; QHostAddress m_host; quint16 m_port; quint8 m_sequenceNumber = 0; + QHash m_lifxProducts; void sendMessage(const Message &message); @@ -139,7 +153,7 @@ private slots: signals: void connectionChanged(bool connected); - void devicesDiscovered(QHostAddress address, int port); + void deviceDiscovered(const QHostAddress &address, int port, const LifxProduct &product); //void requestExecuted(int requestId, bool success); //void errorReceived(int code, const QString &message); From 3b2f76ed80e19371fcabbae40c910605d8ebb339 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Mon, 6 Jul 2020 15:23:35 +0200 Subject: [PATCH 06/15] added lifx cloud connection --- lifx/integrationpluginlifx.cpp | 442 +++++++++++++++++++++++++++----- lifx/integrationpluginlifx.h | 35 ++- lifx/integrationpluginlifx.json | 47 +++- lifx/lifx.cpp | 100 +++----- lifx/lifx.h | 16 +- lifx/lifx.pro | 2 + lifx/lifxcloud.cpp | 284 ++++++++++++++++++++ lifx/lifxcloud.h | 122 +++++++++ 8 files changed, 902 insertions(+), 146 deletions(-) create mode 100644 lifx/lifxcloud.cpp create mode 100644 lifx/lifxcloud.h diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp index d4f49add..1baacc85 100644 --- a/lifx/integrationpluginlifx.cpp +++ b/lifx/integrationpluginlifx.cpp @@ -33,10 +33,18 @@ #include "integrations/integrationplugin.h" #include "types/param.h" #include "plugininfo.h" +#include "platform/platformzeroconfcontroller.h" +#include "network/zeroconf/zeroconfservicebrowser.h" #include #include #include +#include +#include +#include +#include +#include +#include IntegrationPluginLifx::IntegrationPluginLifx() { @@ -47,26 +55,146 @@ 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_hostAddressParamTypeIds.insert(colorBulbThingClassId, colorBulbThingHostParamTypeId); + m_hostAddressParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingHostParamTypeId); + + m_portParamTypeIds.insert(colorBulbThingClassId, colorBulbThingPortParamTypeId); + m_portParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingPortParamTypeId); + + m_idParamTypeIds.insert(colorBulbThingClassId, colorBulbThingIdParamTypeId); + m_idParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingIdParamTypeId); + + m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_hap._tcp"); // discovers all homekit devices + + QFile file; + file.setFileName("/tmp/products.json"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCWarning(dcLifx()) << "Could not open products file" << file.errorString() << "file name:" << file.fileName(); + } else { + QJsonDocument productsJson = QJsonDocument::fromJson(file.readAll()); + file.close(); + + if (!productsJson.isArray()) { + qCWarning(dcLifx()) << "Products JSON is not a valid array"; + } else { + QJsonArray productsArray = productsJson.array().first().toObject().value("products").toArray(); + foreach (QJsonValue value, productsArray) { + QJsonObject object = value.toObject(); + Lifx::LifxProduct product; + product.pid = object["pid"].toInt(); + product.name = object["name"].toString(); + qCDebug(dcLifx()) << "Lifx product JSON, found product. PID:" << product.pid << "Name" << product.name; + QJsonObject features = object["features"].toObject(); + product.color = features["color"].toBool(); + product.infrared = features["infrared"].toBool(); + product.matrix = features["matrix"].toBool(); + product.multizone = features["multizone"].toBool(); + product.minColorTemperature = features["temperature_range"].toArray().first().toInt(); + product.maxColorTemperature = features["temperature_range"].toArray().last().toInt(); + product.chain = features["chain"].toBool(); + m_lifxProducts.insert(product.pid, product); + } + } + } + + 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, [this, 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, "Please enter your Token and password. 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("This 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) { - if (!m_lifxConnection) { - m_lifxConnection = new Lifx(this); - m_lifxConnection->enable(); - } + if ((info->thingClassId() == colorBulbThingClassId) || (info->thingClassId() == dimmableBulbThingClassId)) { + QHash descriptors; + foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) { + if (!avahiEntry.name().contains("lifx", Qt::CaseSensitivity::CaseInsensitive)) { + continue; + } - if (info->thingClassId() == colorBulbThingClassId) { - } else if () { + 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()); } } @@ -74,14 +202,44 @@ void IntegrationPluginLifx::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); - if (!m_lifxConnection) { - m_lifxConnection = new Lifx(this); - m_lifxConnection->enable(); - } + if (thing->thingClassId() == colorBulbThingClassId || thing->thingClassId() == dimmableBulbThingClassId) { + Lifx *lifx = new Lifx(QHostAddress(thing->paramValue(m_hostAddressParamTypeIds.value(thing->thingClassId())).toString()), + thing->paramValue(m_portParamTypeIds.value(thing->thingClassId())).toInt(), this); + if(lifx->enable()) { + m_lifxConnections.insert(thing, lifx); + //TODO async setup + info->finish(Thing::ThingErrorNoError); + } else { + lifx->deleteLater(); + info->finish(Thing::ThingErrorSetupFailed); + } + } else if (thing->thingClassId() == lifxAccountThingClassId) { - if (thing->thingClassId() == colorBulbThingClassId) { + pluginStorage()->beginGroup(thing->id().toString()); + QByteArray token = pluginStorage()->value("token").toByteArray(); + QByteArray username = pluginStorage()->value("username").toByteArray(); + pluginStorage()->endGroup(); - info->finish(Thing::ThingErrorNoError); + 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); + lifxCloud->setAuthorizationToken(token); + lifxCloud->listLights(); + + //TODO try setup again if it failes + } else { + Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } } @@ -92,92 +250,168 @@ void IntegrationPluginLifx::postSetupThing(Thing *thing) if (!m_pluginTimer) { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() { + foreach (Lifx *lifx, m_lifxConnections) { + Q_UNUSED(lifx) + } + foreach (LifxCloud *lifx, m_lifxCloudConnections) { + lifx->listLights(); + } }); } - m_pluginTimer->timeout(); + + 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; + Lifx *lifx; + LifxCloud *lifxCloud; - if (!m_lifxConnection) + if (m_lifxConnections.contains(thing)) { + lifx = m_lifxConnections.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 = m_lifxConnection->setPower(power); + 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()) - m_lifxConnection->setPower(true); + lifx->setPower(true); - if (m_pendingBrightnessAction.contains(thing->id())) { - //Scrap old info and insert new one - m_pendingBrightnessAction.insert(thing->id(), info); + int brightness = info->action().param(colorBulbBrightnessActionBrightnessParamTypeId).value().toInt(); + int requestId; + if (cloudDevice) { + requestId = lifxCloud->setBrightnesss(lightId, brightness); } else { - m_pendingBrightnessAction.insert(thing->id(), info); - QTimer::singleShot(500, this, [info, this]{ - int brightness = info->action().param(colorBulbBrightnessActionBrightnessParamTypeId).value().toInt(); - m_pendingBrightnessAction.remove(info->thing()->id()); - int requestId = m_lifxConnection->setBrightness(brightness); - connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); - m_asyncActions.insert(requestId, info); - }); + 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()) - m_lifxConnection->setPower(true); - int requestId = m_lifxConnection->setColor(color); + 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() * -11.12); if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()) - m_lifxConnection->setPower(true); - int requestId = m_lifxConnection->setColorTemperature(colorTemperature); + 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 { - qCWarning(dcLifx()) << "Unhandled action"; + Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); } - } else if (thing->thingClassId() == dimmableBulbThingClassId) { + } else if (thing->thingClassId() == dimmableBulbThingClassId) { + QByteArray lightId = thing->paramValue(dimmableBulbThingIdParamTypeId).toByteArray(); if (action.actionTypeId() == dimmableBulbPowerActionTypeId) { bool power = action.param(dimmableBulbPowerActionPowerParamTypeId).value().toBool(); - int requestId = m_lifxConnection->setPower(power); + 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(dimmableBulbPowerStateTypeId).toBool()) - m_lifxConnection->setPower(true); - int requestId = m_lifxConnection->setBrightness(brightness); + 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 { - qCWarning(dcLifx()) << "Unhandled action"; + Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); } } else { - qCWarning(dcLifx()) << "Unhandled device class"; + Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } } void IntegrationPluginLifx::thingRemoved(Thing *thing) { - Q_UNUSED(thing) + if (thing->thingClassId() == colorBulbThingClassId || thing->thingClassId() == dimmableBulbThingClassId) { + if (m_lifxConnections.contains(thing)) + m_lifxConnections.take(thing)->deleteLater(); + } else if (thing->thingClassId() == lifxAccountThingClassId) { + if (m_lifxCloudConnections.contains(thing)) + m_lifxCloudConnections.take(thing)->deleteLater(); + } if (myThings().isEmpty()) { - m_lifxConnection->deleteLater(); - m_lifxConnection = nullptr; + 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::onDeviceNameChanged() { Thing *thing = static_cast(sender()); @@ -186,8 +420,12 @@ void IntegrationPluginLifx::onDeviceNameChanged() void IntegrationPluginLifx::onConnectionChanged(bool connected) { - Q_UNUSED(connected) -// thing->setStateValue(m_connectedStateTypeIds.value(thing->ThingClassId()), connected); + Q_UNUSED(connected) + Lifx *lifx = static_cast(sender()); + Thing *thing = m_lifxConnections.key(lifx); + if (!thing) + return; + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), connected); } void IntegrationPluginLifx::onRequestExecuted(int requestId, bool success) @@ -200,27 +438,111 @@ void IntegrationPluginLifx::onRequestExecuted(int requestId, bool success) info->finish(Thing::ThingErrorHardwareFailure); } } + + if (m_asyncBrowserItem.contains(requestId)) { + BrowserActionInfo *info = m_asyncBrowserItem.take(requestId); + if (success) { + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + } } -/*void IntegrationPluginLifx::onPowerNotificationReceived(bool status) +void IntegrationPluginLifx::onLifxCloudConnectionChanged(bool connected) { - Q_UNUSED(status) - Lifx *Lifx = static_cast(sender()); - Device * device = myThings().findById(m_LifxConnections.key(Lifx)); - if(!device) + LifxCloud *lifxCloud = static_cast(sender()); + Thing *accountThing = m_lifxCloudConnections.key(lifxCloud); + if (!accountThing) return; + accountThing->setStateValue(m_connectedStateTypeIds.value(accountThing->thingClassId()), connected); - thing->setStateValue(m_powerStateTypeIds.value(thing->ThingClassId()), status); - + foreach (Thing *thing, myThings().filterByParentId(accountThing->id())) { + if (!connected) + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), connected); + } } -void IntegrationPluginLifx::onBrightnessNotificationReceived(int percentage) +void IntegrationPluginLifx::onLifxCloudLightsListReceived(const QList &lights) { - Q_UNUSED(percentage) - Lifx *lifx = static_cast(sender()); - Device * device = myThings().findById(m_lifxConnections.key(lifx)); - if(!device) - return; + LifxCloud *lifxCloud = static_cast(sender()); + if (m_asyncCloudSetups.contains(lifxCloud)) { + ThingSetupInfo *info = m_asyncCloudSetups.take(lifxCloud); + m_lifxCloudConnections.insert(info->thing(), lifxCloud); + info->finish(Thing::ThingErrorNoError); + } - thing->setStateValue(m_brightnessStateTypeIds.value(thing->ThingClassId()), percentage); -}*/ + 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); + thing->setStateValue(m_colorTemperatureStateTypeIds.value(thingClassId), light.colorTemperature); + 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); + thingDescriptor.setParams(params); + thingDescriptors.append(thingDescriptor); + } + if (!thingDescriptors.isEmpty()) + autoThingsAppeared(thingDescriptors); +} + +void IntegrationPluginLifx::onLifxCloudRequestExecuted(int requestId, bool success) +{ + ThingActionInfo *info = m_asyncActions.take(requestId); + if (!info) { + return; + } + + if (success) { + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareNotAvailable); + } +} + + +void IntegrationPluginLifx::onLifxCloudScenesListReceived(const QList &scenes) +{ + LifxCloud *lifxCloud = static_cast(sender()); + Thing *thing = m_lifxCloudConnections.key(lifxCloud); + if (!thing) + return; + qCDebug(dcLifx()) << "Scene list received, count: " << scenes.length(); + + if (m_asyncBrowseResults.contains(lifxCloud)) { + BrowseResult *result = m_asyncBrowseResults.take(lifxCloud); + foreach (LifxCloud::Scene scene, scenes) { + BrowserItem item; + item.setId(scene.id); + item.setBrowsable(false); + item.setExecutable(true); + item.setDisplayName(scene.name); + item.setDisabled(false); + result->addItem(item); + } + result->finish(Thing::ThingErrorNoError); + } +} diff --git a/lifx/integrationpluginlifx.h b/lifx/integrationpluginlifx.h index 551ea103..f073486a 100644 --- a/lifx/integrationpluginlifx.h +++ b/lifx/integrationpluginlifx.h @@ -34,6 +34,11 @@ #include "integrations/integrationplugin.h" #include "plugintimer.h" #include "lifx.h" +#include "lifxcloud.h" + +#include "network/networkaccessmanager.h" +#include "network/zeroconf/zeroconfservicebrowser.h" +#include "network/zeroconf/zeroconfserviceentry.h" #include @@ -48,33 +53,51 @@ public: explicit IntegrationPluginLifx(); void init() override; + void startPairing(ThingPairingInfo *info) override; + void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override; + void discoverThings(ThingDiscoveryInfo *info) override; void setupThing(ThingSetupInfo *info) override; void postSetupThing(Thing *thing) override; void executeAction(ThingActionInfo *info) override; void thingRemoved(Thing *thing) override; + void browseThing(BrowseResult *result) override; + void browserItem(BrowserItemResult *result) override; + void executeBrowserItem(BrowserActionInfo *info) override; + private: + NetworkAccessManager *m_networkManager = nullptr; PluginTimer *m_pluginTimer = nullptr; + QHash m_asyncCloudSetups; QHash m_asyncActions; - Lifx *m_lifxConnection; + QHash m_lifxConnections; + QHash m_lifxCloudConnections; + QHash m_asyncBrowseResults; + QHash m_asyncBrowserItem; + + ZeroConfServiceBrowser *m_serviceBrowser = nullptr; QHash m_connectedStateTypeIds; QHash m_powerStateTypeIds; QHash m_brightnessStateTypeIds; + QHash m_colorTemperatureStateTypeIds; + QHash m_hostAddressParamTypeIds; + QHash m_portParamTypeIds; + QHash m_idParamTypeIds; QHash m_pendingBrightnessAction; + QHash m_lifxProducts; private slots: void onDeviceNameChanged(); void onConnectionChanged(bool connected); void onRequestExecuted(int requestId, bool success); - /*void onPowerNotificationReceived(bool status); - void onBrightnessNotificationReceived(int percentage); - void onColorTemperatureNotificationReceived(int kelvin); - void onRgbNotificationReceived(QRgb rgbColor); - void onNameNotificationReceived(const QString &name);*/ + void onLifxCloudConnectionChanged(bool connected); + void onLifxCloudRequestExecuted(int requestId, bool success); + void onLifxCloudLightsListReceived(const QList &lights); + void onLifxCloudScenesListReceived(const QList &scenes); }; #endif // INTEGRATIONPLUGIN_LIFX_H diff --git a/lifx/integrationpluginlifx.json b/lifx/integrationpluginlifx.json index 74007705..eb69ebbd 100644 --- a/lifx/integrationpluginlifx.json +++ b/lifx/integrationpluginlifx.json @@ -8,11 +8,50 @@ "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": ["user", "discovery"], + "createMethods": ["auto", "discovery"], "interfaces": ["colorlight", "connectable"], "paramTypes": [ { @@ -20,7 +59,6 @@ "name": "host", "displayName": "Host address", "type" : "QString", - "inputType": "IPv4Address", "readOnly": true }, { @@ -33,7 +71,7 @@ { "id": "976ecea0-ac25-47d4-9dc5-362962ddb6c0", "name": "id", - "displayName": "Id", + "displayName": "ID", "type" : "QString", "readOnly": true } @@ -114,7 +152,7 @@ "id": "a5b02af8-7c97-4a78-9c78-bafee7407b5e", "name": "dimmableBulb", "displayName": "Day and Dusk", - "createMethods": ["user", "discovery"], + "createMethods": ["auto", "discovery"], "interfaces": ["colortemperaturelight", "connectable"], "paramTypes": [ { @@ -122,7 +160,6 @@ "name": "host", "displayName": "Host address", "type" : "QString", - "inputType": "IPv4Address", "readOnly": true }, { diff --git a/lifx/lifx.cpp b/lifx/lifx.cpp index b3193507..2fdf6503 100644 --- a/lifx/lifx.cpp +++ b/lifx/lifx.cpp @@ -33,47 +33,22 @@ #include "extern-plugininfo.h" #include -#include -#include -#include -#include -Lifx::Lifx(QObject *parent) : - QObject(parent) + +Lifx::Lifx(const QHostAddress &address, quint16 port, QObject *parent) : + QObject(parent), + m_host(address), + m_port(port) { m_reconnectTimer = new QTimer(this); m_reconnectTimer->setSingleShot(true); connect(m_reconnectTimer, &QTimer::timeout, this, &Lifx::onReconnectTimer); m_clientId = qrand(); - QFile file; - file.setFileName("/tmp/products.json"); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qCWarning(dcLifx()) << "Could not open products file" << file.errorString(); - } - QJsonDocument productsJson = QJsonDocument::fromJson(file.readAll()); - file.close(); + m_socket = new QUdpSocket(this); - if (!productsJson.isArray()) { - qCWarning(dcLifx()) << "Products JSON is not a valid array"; - } - QJsonArray productsArray = productsJson.array().first().toObject().value("products").toArray(); - foreach (QJsonValue value, productsArray) { - QJsonObject object = value.toObject(); - 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_socket->setSocketOption(QAbstractSocket::MulticastTtlOption, QVariant(1)); + m_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1)); } Lifx::~Lifx() @@ -86,20 +61,7 @@ Lifx::~Lifx() bool Lifx::enable() { - // Clean up - if (m_socket) { - delete m_socket; - m_socket = nullptr; - } - // Bind udp socket and join multicast group - m_socket = new QUdpSocket(this); - m_port = 56700; - m_host = QHostAddress("239.255.255.250"); - - m_socket->setSocketOption(QAbstractSocket::MulticastTtlOption,QVariant(1)); - m_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption,QVariant(1)); - if(!m_socket->bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress)){ qCWarning(dcLifx()) << "could not bind to port" << m_port; delete m_socket; @@ -107,8 +69,8 @@ bool Lifx::enable() return false; } - if(!m_socket->joinMulticastGroup(m_host)){ - qCWarning(dcLifx()) << "could not join multicast group" << m_host; + if(!m_socket->joinMulticastGroup(QHostAddress("239.255.255.250"))){ + qCWarning(dcLifx()) << "could not join multicast group"; delete m_socket; m_socket = nullptr; return false; @@ -118,31 +80,37 @@ bool Lifx::enable() return true; } -void Lifx::discoverDevices() +void Lifx::setHostAddress(const QHostAddress &address) { - Message message; - sendMessage(message); + m_host = address; } -int Lifx::setColorTemperature(int mirad, int msFadeTime) +void Lifx::setPort(quint16 port) +{ + m_port = port; +} + +int Lifx::setColorTemperature(uint mirad, uint msFadeTime) { Q_UNUSED(mirad) Q_UNUSED(msFadeTime) int requestId = qrand(); - + Message message; + sendMessage(message); return requestId; } -int Lifx::setColor(QColor color, int msFadeTime) +int Lifx::setColor(QColor color, uint msFadeTime) { Q_UNUSED(color) Q_UNUSED(msFadeTime) int requestId = qrand(); - + Message message; + sendMessage(message); return requestId; } -int Lifx::setBrightness(int percentage, int msFadeTime) +int Lifx::setBrightness(uint percentage, uint msFadeTime) { Q_UNUSED(percentage) Q_UNUSED(msFadeTime) @@ -152,12 +120,13 @@ int Lifx::setBrightness(int percentage, int msFadeTime) return requestId; } -int Lifx::setPower(bool power, int msFadeTime) +int Lifx::setPower(bool power, uint msFadeTime) { Q_UNUSED(power) Q_UNUSED(msFadeTime) int requestId = qrand(); - + Message message; + sendMessage(message); return requestId; } @@ -219,14 +188,13 @@ void Lifx::sendMessage(const Lifx::Message &message) //Finally another reserved field of 16 bits (2 bytes). header.append(2, '0'); - QByteArray payload; - - QByteArray message; - message.append(header); - message.append(payload); - message.append(message.length()); - //header = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); - m_socket->writeDatagram(message, m_host, m_port); + QByteArray fullMessage; + //message.append(header); + //message.append(payload); + //message.append(message.length()); + fullMessage = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); + std::reverse(fullMessage.begin(), fullMessage.end()); + m_socket->writeDatagram(fullMessage, m_host, m_port); } void Lifx::onStateChanged(QAbstractSocket::SocketState state) diff --git a/lifx/lifx.h b/lifx/lifx.h index 708a9c21..5af8b849 100644 --- a/lifx/lifx.h +++ b/lifx/lifx.h @@ -28,7 +28,6 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - #ifndef LIFX_H #define LIFX_H @@ -123,15 +122,16 @@ public: bool chain; }; - explicit Lifx(QObject *parent = nullptr); + explicit Lifx(const QHostAddress &address, quint16 port = 56700, QObject *parent = nullptr); ~Lifx(); bool enable(); + void setHostAddress(const QHostAddress &address); + void setPort(quint16 port); - void discoverDevices(); - int setColorTemperature(int kelvin, int msFadeTime=500); - int setColor(QColor color, int msFadeTime = 500); - int setBrightness(int percentage, int msFadeTime = 500); - int setPower(bool power, int msFadeTime = 500); + 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); int flash(); int flash15s(); @@ -142,7 +142,6 @@ private: QHostAddress m_host; quint16 m_port; quint8 m_sequenceNumber = 0; - QHash m_lifxProducts; void sendMessage(const Message &message); @@ -153,7 +152,6 @@ private slots: signals: void connectionChanged(bool connected); - void deviceDiscovered(const QHostAddress &address, int port, const LifxProduct &product); //void requestExecuted(int requestId, bool success); //void errorReceived(int code, const QString &message); diff --git a/lifx/lifx.pro b/lifx/lifx.pro index 9dea64c2..802b5391 100644 --- a/lifx/lifx.pro +++ b/lifx/lifx.pro @@ -5,8 +5,10 @@ QT += network SOURCES += \ integrationpluginlifx.cpp \ lifx.cpp \ + lifxcloud.cpp \ HEADERS += \ integrationpluginlifx.h \ lifx.h \ + lifxcloud.h \ diff --git a/lifx/lifxcloud.cpp b/lifx/lifxcloud.cpp new file mode 100644 index 00000000..2fbba9da --- /dev/null +++ b/lifx/lifxcloud.cpp @@ -0,0 +1,284 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "lifxcloud.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include +#include +#include + +LifxCloud::LifxCloud(NetworkAccessManager *networkManager, QObject *parent) : + QObject(parent), + m_networkManager(networkManager) +{ + +} + +void LifxCloud::setAuthorizationToken(const QByteArray &token) +{ + m_authorizationToken = token; +} + +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] { + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // check HTTP status code + if (status != 200) { + qCWarning(dcLifx()) << "Error get lights list" << status << reply->errorString(); + return; + } + QByteArray rawData = reply->readAll(); + + QJsonDocument data; QJsonParseError error; + data = QJsonDocument::fromJson(rawData, &error); + if (error.error != QJsonParseError::NoError) { + qDebug(dcLifx()) << "List lights: Received invalide JSON object" << error.errorString(); + return; + } + + if (!data.isArray()) + qCWarning(dcLifx()) << "Data is not an array"; + + QJsonArray array = data.array(); + QList descriptors; + foreach (QJsonValue jsonValue, array) { + + QJsonObject object = jsonValue.toObject(); + qCDebug(dcLifx()) << "Light object:" << object; + Light light; + light.id = object["id"].toString().toUtf8(); + light.uuid = object["uuid"].toString().toUtf8(); + 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] { + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // check HTTP status code + if (status != 200) { + qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString(); + return; + } + QByteArray rawData = reply->readAll(); + qCDebug(dcLifx()) << "Got list scenes reply" << rawData; + QJsonDocument data; QJsonParseError error; + data = QJsonDocument::fromJson(rawData, &error); + if (error.error != QJsonParseError::NoError) { + qDebug(dcLifx()) << "List scenes: Received invalide JSON object" << error.errorString(); + return; + } + if (!data.isArray()) + qCWarning(dcLifx()) << "Data is not an array"; + + QJsonArray array = data.array(); + QList scenes; + foreach (QJsonValue value, array) { + Scene scene; + scene.id = value.toObject().value("id").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(lightId, StatePower, power, duration); +} + +int LifxCloud::setBrightnesss(const QString &lightId, int brightness, int duration) +{ + return setState(lightId, StateBrightness, brightness/100.00, duration); +} + +int LifxCloud::setColor(const QString &lightId, QColor color, int duration) +{ + return setState(lightId, StateColor, color.name(), duration); +} + +int LifxCloud::setColorTemperature(const QString &selector, int kelvin, int duration) +{ + return setState(selector, StateColorTemperature, kelvin, duration); +} + +int LifxCloud::setInfrared(const QString &lightId, int infrared, int duration) +{ + return setState(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); + QByteArray payload; + payload.append("duration:5"); + QNetworkReply *reply = m_networkManager->put(request, payload); + connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [reply, this] { + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // check HTTP status code + if (status != 200) { + qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString(); + return; + } + QByteArray rawData = reply->readAll(); + qCDebug(dcLifx()) << "Got list lights reply" << rawData; + + }); + 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"; + break; + case StateBrightness: + payload["brightness"] = stateValue.toDouble(); + break; + case StateColor: + payload["color"] = stateValue.toString(); + break; + case StateColorTemperature: + payload["color"] = "kelvin:"+stateValue.toString(); + break; + case StateInfrared: + payload["infrared"] = stateValue.toDouble(); + } + + doc.setObject(payload); + QNetworkReply *reply = m_networkManager->post(request, doc.toJson()); + connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // check HTTP status code + if (status != 200) { + qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString(); + emit requestExecuted(requestId, false); + return; + } + QByteArray rawData = reply->readAll(); + qCDebug(dcLifx()) << "Got set state reply" << rawData; + emit requestExecuted(requestId, true); + }); + return requestId; +} diff --git a/lifx/lifxcloud.h b/lifx/lifxcloud.h new file mode 100644 index 00000000..545768a0 --- /dev/null +++ b/lifx/lifxcloud.h @@ -0,0 +1,122 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef LIFXCLOUD_H +#define LIFXCLOUD_H + +#include +#include +#include +#include "network/networkaccessmanager.h" + +class LifxCloud : public QObject +{ + Q_OBJECT +public: + enum State { + StatePower, + StateBrightness, + StateColor, + StateColorTemperature, + StateInfrared + }; + 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); + + void listLights(); + void listScenes(); + int setPower(const QString &lightId, bool power, int duration = 3); + int setBrightnesss(const QString &lightId, int brightness, int duration = 3); + int setColor(const QString &selector, QColor color, int duration = 3); + int setColorTemperature(const QString &selector, int kelvin, int duration = 3); + int setInfrared(const QString &lightId, int infrared, int duration = 3); + + int activateScene(const QString &sceneId); + +private: + NetworkAccessManager *m_networkManager = nullptr; + QByteArray m_authorizationToken; + + int setState(const QString &lightId, State state, QVariant stateValue, int duration); + +signals: + void lightsListReceived(const QList &lights); + void scenesListReceived(const QList &scenes); + void requestExecuted(int requestId, bool susccess); +}; + +#endif // LIFXCLOUD_H From 8907fb7b47c7ef4dd605f31818d5c8d76ca84542 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Mon, 6 Jul 2020 16:21:13 +0200 Subject: [PATCH 07/15] added auth and connected state change --- lifx/integrationpluginlifx.cpp | 56 +++++++++++++++++---------- lifx/integrationpluginlifx.h | 2 +- lifx/lifx.cpp | 1 - lifx/lifx.h | 2 +- lifx/lifxcloud.cpp | 71 ++++++++++++++++++++++++++++------ lifx/lifxcloud.h | 7 +++- 6 files changed, 102 insertions(+), 37 deletions(-) diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp index 1baacc85..81fb9731 100644 --- a/lifx/integrationpluginlifx.cpp +++ b/lifx/integrationpluginlifx.cpp @@ -234,6 +234,8 @@ void IntegrationPluginLifx::setupThing(ThingSetupInfo *info) 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(); @@ -245,8 +247,6 @@ void IntegrationPluginLifx::setupThing(ThingSetupInfo *info) void IntegrationPluginLifx::postSetupThing(Thing *thing) { - connect(thing, &Thing::nameChanged, this, &IntegrationPluginLifx::onDeviceNameChanged); - if (!m_pluginTimer) { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() { @@ -336,7 +336,7 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) 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()); + 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(); @@ -412,12 +412,6 @@ void IntegrationPluginLifx::executeBrowserItem(BrowserActionInfo *info) connect(info, &BrowserActionInfo::aborted, this, [requestId, this] {m_asyncBrowserItem.remove(requestId);}); } -void IntegrationPluginLifx::onDeviceNameChanged() -{ - Thing *thing = static_cast(sender()); - Q_UNUSED(thing) -} - void IntegrationPluginLifx::onConnectionChanged(bool connected) { Q_UNUSED(connected) @@ -437,9 +431,7 @@ void IntegrationPluginLifx::onRequestExecuted(int requestId, bool success) } else { info->finish(Thing::ThingErrorHardwareFailure); } - } - - if (m_asyncBrowserItem.contains(requestId)) { + } else if (m_asyncBrowserItem.contains(requestId)) { BrowserActionInfo *info = m_asyncBrowserItem.take(requestId); if (success) { info->finish(Thing::ThingErrorNoError); @@ -463,6 +455,15 @@ void IntegrationPluginLifx::onLifxCloudConnectionChanged(bool connected) } } +void IntegrationPluginLifx::onLifxCloudAuthenticationChanged(bool authenticated) +{ + LifxCloud *lifxCloud = static_cast(sender()); + Thing *accountThing = m_lifxCloudConnections.key(lifxCloud); + if (!accountThing) + return; + accountThing->setStateValue(lifxAccountLoggedInStateTypeId, authenticated); +} + void IntegrationPluginLifx::onLifxCloudLightsListReceived(const QList &lights) { LifxCloud *lifxCloud = static_cast(sender()); @@ -502,6 +503,8 @@ void IntegrationPluginLifx::onLifxCloudLightsListReceived(const QListfinish(Thing::ThingErrorNoError); - } else { - info->finish(Thing::ThingErrorHardwareNotAvailable); + 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); + } } } diff --git a/lifx/integrationpluginlifx.h b/lifx/integrationpluginlifx.h index f073486a..788e4b85 100644 --- a/lifx/integrationpluginlifx.h +++ b/lifx/integrationpluginlifx.h @@ -90,11 +90,11 @@ private: QHash m_lifxProducts; private slots: - void onDeviceNameChanged(); void onConnectionChanged(bool connected); void onRequestExecuted(int requestId, bool success); void onLifxCloudConnectionChanged(bool connected); + void onLifxCloudAuthenticationChanged(bool authenticated); void onLifxCloudRequestExecuted(int requestId, bool success); void onLifxCloudLightsListReceived(const QList &lights); void onLifxCloudScenesListReceived(const QList &scenes); diff --git a/lifx/lifx.cpp b/lifx/lifx.cpp index 2fdf6503..1bb4eb73 100644 --- a/lifx/lifx.cpp +++ b/lifx/lifx.cpp @@ -75,7 +75,6 @@ bool Lifx::enable() m_socket = nullptr; return false; } - connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError))); connect(m_socket, &QUdpSocket::readyRead, this, &Lifx::onReadyRead); return true; } diff --git a/lifx/lifx.h b/lifx/lifx.h index 5af8b849..6ac42447 100644 --- a/lifx/lifx.h +++ b/lifx/lifx.h @@ -152,7 +152,7 @@ private slots: signals: void connectionChanged(bool connected); - //void requestExecuted(int requestId, bool success); + void requestExecuted(int requestId, bool success); //void errorReceived(int code, const QString &message); //void powerNotificationReceived(bool status); diff --git a/lifx/lifxcloud.cpp b/lifx/lifxcloud.cpp index 2fbba9da..4b3d13f9 100644 --- a/lifx/lifxcloud.cpp +++ b/lifx/lifxcloud.cpp @@ -50,6 +50,16 @@ 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()) { @@ -67,10 +77,18 @@ void LifxCloud::listLights() int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // check HTTP status code - if (status != 200) { + if (status > 207) { qCWarning(dcLifx()) << "Error get lights list" << status << reply->errorString(); return; } + if (!m_authenticated) { + m_authenticated = true; + emit authenticationChanged(true); + } + if (!m_connected) { + m_connected = true; + emit authenticationChanged(true); + } QByteArray rawData = reply->readAll(); QJsonDocument data; QJsonParseError error; @@ -151,6 +169,14 @@ void LifxCloud::listScenes() qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString(); return; } + if (!m_authenticated) { + m_authenticated = true; + emit authenticationChanged(true); + } + if (!m_connected) { + m_connected = true; + emit authenticationChanged(true); + } QByteArray rawData = reply->readAll(); qCDebug(dcLifx()) << "Got list scenes reply" << rawData; QJsonDocument data; QJsonParseError error; @@ -166,7 +192,7 @@ void LifxCloud::listScenes() QList scenes; foreach (QJsonValue value, array) { Scene scene; - scene.id = value.toObject().value("id").toString().toUtf8(); + scene.id = value.toObject().value("uuid").toString().toUtf8(); scene.name = value.toObject().value("name").toString(); scenes.append(scene); } @@ -208,23 +234,36 @@ int LifxCloud::activateScene(const QString &sceneId) int requestId = qrand(); QNetworkRequest request; - request.setUrl(QUrl(QString("https://api.lifx.com/v1/scenes/scene_id::%1/activate").arg(sceneId))); + 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); - QByteArray payload; - payload.append("duration:5"); - QNetworkReply *reply = m_networkManager->put(request, payload); + QNetworkReply *reply = m_networkManager->put(request, ""); connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); - connect(reply, &QNetworkReply::finished, this, [reply, this] { + connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // check HTTP status code - if (status != 200) { - qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString(); + if (status == 401 || status == 403) { + if (m_authenticated) { + m_authenticated = false; + emit authenticationChanged(false); + } + } + if (status > 207) { + qCWarning(dcLifx()) << "Error activate Scene" << status << reply->errorString(); + emit requestExecuted(requestId, false); return; } + if (!m_authenticated) { + m_authenticated = true; + emit authenticationChanged(true); + } + if (!m_connected) { + m_connected = true; + emit authenticationChanged(true); + } + emit requestExecuted(requestId, true); QByteArray rawData = reply->readAll(); - qCDebug(dcLifx()) << "Got list lights reply" << rawData; - + qCDebug(dcLifx()) << "Got activate scene reply" << rawData; }); return requestId; } @@ -271,11 +310,19 @@ int LifxCloud::setState(const QString &selector, State state, QVariant stateValu connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // check HTTP status code - if (status != 200) { + if (status > 207) { qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString(); emit requestExecuted(requestId, false); return; } + if (!m_authenticated) { + m_authenticated = true; + emit authenticationChanged(true); + } + if (!m_connected) { + m_connected = true; + emit authenticationChanged(true); + } QByteArray rawData = reply->readAll(); qCDebug(dcLifx()) << "Got set state reply" << rawData; emit requestExecuted(requestId, true); diff --git a/lifx/lifxcloud.h b/lifx/lifxcloud.h index 545768a0..1e0025e1 100644 --- a/lifx/lifxcloud.h +++ b/lifx/lifxcloud.h @@ -96,6 +96,8 @@ public: explicit LifxCloud(NetworkAccessManager *networkManager, QObject *parent = nullptr); void setAuthorizationToken(const QByteArray &token); + bool cloudAuthenticated(); + bool cloudConnected(); void listLights(); void listScenes(); @@ -112,8 +114,11 @@ private: QByteArray m_authorizationToken; int setState(const QString &lightId, State state, QVariant stateValue, int duration); - + bool m_authenticated = false; + bool m_connected = false; signals: + void connectionChanged(bool m_connected); + void authenticationChanged(bool m_authenticated); void lightsListReceived(const QList &lights); void scenesListReceived(const QList &scenes); void requestExecuted(int requestId, bool susccess); From 5e40675829894e2ff16ee7abcfe77999ba36f5f4 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Mon, 6 Jul 2020 19:36:38 +0200 Subject: [PATCH 08/15] cloud actions do now work --- lifx/README.md | 11 +- lifx/integrationpluginlifx.cpp | 219 ++++++++++++------ lifx/integrationpluginlifx.h | 10 +- lifx/integrationpluginlifx.json | 60 +++-- lifx/lifx.pro | 14 -- lifx/lifxcloud.cpp | 187 +++++++++------ lifx/lifxcloud.h | 23 +- lifx/{lifx.cpp => lifxlan.cpp} | 65 ++---- lifx/{lifx.h => lifxlan.h} | 24 +- lifx/meta.json | 2 +- ...4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts | 91 ++++++-- ...0ee30-79e2-447b-8dcc-c34470f41992-en_US.ts | 91 ++++++-- 12 files changed, 488 insertions(+), 309 deletions(-) delete mode 100644 lifx/lifx.pro rename lifx/{lifx.cpp => lifxlan.cpp} (77%) rename lifx/{lifx.h => lifxlan.h} (87%) diff --git a/lifx/README.md b/lifx/README.md index 3ffe992e..efe39ff9 100644 --- a/lifx/README.md +++ b/lifx/README.md @@ -1,9 +1,18 @@ # Lifx -This plug-in implements the LAN API for Lifx devices +This plug-in integrates LIFX lights to nymea. ## Supported Things +* All LIFX lights + ## Requirements +* LIFX cloud access token. + ** Get the token from https://cloud.lifx.com/settings +* Internet connection +* The package 'nymea-plugin-lifx' must be installed. + ## More + +https://www.lifx.com/ diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp index 81fb9731..db94783b 100644 --- a/lifx/integrationpluginlifx.cpp +++ b/lifx/integrationpluginlifx.cpp @@ -63,52 +63,45 @@ void IntegrationPluginLifx::init() m_brightnessStateTypeIds.insert(colorBulbThingClassId, colorBulbBrightnessStateTypeId); m_brightnessStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbBrightnessStateTypeId); - m_colorTemperatureStateTypeIds.insert(colorBulbThingClassId, colorBulbColorTemperatureStateTypeId); m_colorTemperatureStateTypeIds.insert(dimmableBulbThingClassId, dimmableBulbColorTemperatureStateTypeId); - m_hostAddressParamTypeIds.insert(colorBulbThingClassId, colorBulbThingHostParamTypeId); - m_hostAddressParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingHostParamTypeId); - - m_portParamTypeIds.insert(colorBulbThingClassId, colorBulbThingPortParamTypeId); - m_portParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingPortParamTypeId); - m_idParamTypeIds.insert(colorBulbThingClassId, colorBulbThingIdParamTypeId); m_idParamTypeIds.insert(dimmableBulbThingClassId, dimmableBulbThingIdParamTypeId); m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_hap._tcp"); // discovers all homekit devices - QFile file; - file.setFileName("/tmp/products.json"); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qCWarning(dcLifx()) << "Could not open products file" << file.errorString() << "file name:" << file.fileName(); - } else { - QJsonDocument productsJson = QJsonDocument::fromJson(file.readAll()); - file.close(); - - if (!productsJson.isArray()) { - qCWarning(dcLifx()) << "Products JSON is not a valid array"; - } else { - QJsonArray productsArray = productsJson.array().first().toObject().value("products").toArray(); - foreach (QJsonValue value, productsArray) { - QJsonObject object = value.toObject(); - Lifx::LifxProduct product; - product.pid = object["pid"].toInt(); - product.name = object["name"].toString(); - qCDebug(dcLifx()) << "Lifx product JSON, found product. PID:" << product.pid << "Name" << product.name; - QJsonObject features = object["features"].toObject(); - product.color = features["color"].toBool(); - product.infrared = features["infrared"].toBool(); - product.matrix = features["matrix"].toBool(); - product.multizone = features["multizone"].toBool(); - product.minColorTemperature = features["temperature_range"].toArray().first().toInt(); - product.maxColorTemperature = features["temperature_range"].toArray().last().toInt(); - product.chain = features["chain"].toBool(); - m_lifxProducts.insert(product.pid, product); - } - } - } + // TODO for LAN connection, get id and device features + // QFile file; + // file.setFileName("/tmp/products.json"); + // if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + // qCWarning(dcLifx()) << "Could not open products file" << file.errorString() << "file name:" << file.fileName(); + // } else { + // QJsonDocument productsJson = QJsonDocument::fromJson(file.readAll()); + // file.close(); + // if (!productsJson.isArray()) { + // qCWarning(dcLifx()) << "Products JSON is not a valid array"; + // } else { + // QJsonArray productsArray = productsJson.array().first().toObject().value("products").toArray(); + // foreach (QJsonValue value, productsArray) { + // QJsonObject object = value.toObject(); + // LifxLan::LifxProduct product; + // product.pid = object["pid"].toInt(); + // product.name = object["name"].toString(); + // qCDebug(dcLifx()) << "Lifx product JSON, found product. PID:" << product.pid << "Name" << product.name; + // QJsonObject features = object["features"].toObject(); + // product.color = features["color"].toBool(); + // product.infrared = features["infrared"].toBool(); + // product.matrix = features["matrix"].toBool(); + // product.multizone = features["multizone"].toBool(); + // product.minColorTemperature = features["temperature_range"].toArray().first().toInt(); + // product.maxColorTemperature = features["temperature_range"].toArray().last().toInt(); + // product.chain = features["chain"].toBool(); + // m_lifxProducts.insert(product.pid, product); + // } + // } + // } m_networkManager = hardwareManager()->networkManager(); } @@ -156,6 +149,7 @@ void IntegrationPluginLifx::confirmPairing(ThingPairingInfo *info, const QString void IntegrationPluginLifx::discoverThings(ThingDiscoveryInfo *info) { + // NOTE: the LAN API is not yet finished, to enable LAN discovery add "discovery" to the createMethods if ((info->thingClassId() == colorBulbThingClassId) || (info->thingClassId() == dimmableBulbThingClassId)) { QHash descriptors; foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) { @@ -203,15 +197,20 @@ void IntegrationPluginLifx::setupThing(ThingSetupInfo *info) Thing *thing = info->thing(); if (thing->thingClassId() == colorBulbThingClassId || thing->thingClassId() == dimmableBulbThingClassId) { - Lifx *lifx = new Lifx(QHostAddress(thing->paramValue(m_hostAddressParamTypeIds.value(thing->thingClassId())).toString()), - thing->paramValue(m_portParamTypeIds.value(thing->thingClassId())).toInt(), this); - if(lifx->enable()) { - m_lifxConnections.insert(thing, lifx); - //TODO async setup - info->finish(Thing::ThingErrorNoError); - } else { - lifx->deleteLater(); + if (thing->parentId().isNull()) { + // Lifx LAN + //LifxLan *lifx = new LifxLan(, this); + //if(lifx->enable()) { + // m_lifxLanConnections.insert(thing, lifx); + //TODO async setup for LAN devices + // info->finish(Thing::ThingErrorNoError); + //} else { + // lifx->deleteLater(); info->finish(Thing::ThingErrorSetupFailed); + //} + } else { + // Lifx Cloud + info->finish(Thing::ThingErrorNoError); } } else if (thing->thingClassId() == lifxAccountThingClassId) { @@ -250,8 +249,9 @@ void IntegrationPluginLifx::postSetupThing(Thing *thing) if (!m_pluginTimer) { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() { - foreach (Lifx *lifx, m_lifxConnections) { + foreach (LifxLan *lifx, m_lifxLanConnections) { Q_UNUSED(lifx) + //TODO update LAN device states } foreach (LifxCloud *lifx, m_lifxCloudConnections) { @@ -271,11 +271,11 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) Thing *thing = info->thing(); Action action = info->action(); bool cloudDevice = false; - Lifx *lifx; + LifxLan *lifx; LifxCloud *lifxCloud; - if (m_lifxConnections.contains(thing)) { - lifx = m_lifxConnections.value(thing); + if (m_lifxLanConnections.contains(thing)) { + lifx = m_lifxLanConnections.value(thing); } else if (m_lifxCloudConnections.contains(myThings().findById(thing->parentId()))) { lifxCloud = m_lifxCloudConnections.value(myThings().findById(thing->parentId())); cloudDevice = true; @@ -299,9 +299,13 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) } else if (action.actionTypeId() == colorBulbBrightnessActionTypeId) { - if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()) - lifx->setPower(true); - + if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){ + if (cloudDevice) { + lifxCloud->setPower(lightId, true); + } else { + lifx->setPower(true); + } + } int brightness = info->action().param(colorBulbBrightnessActionBrightnessParamTypeId).value().toInt(); int requestId; if (cloudDevice) { @@ -313,8 +317,13 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) m_asyncActions.insert(requestId, info); } else if (action.actionTypeId() == colorBulbColorActionColorParamTypeId) { QRgb color = QColor(action.param(colorBulbColorActionColorParamTypeId).value().toString()).rgba(); - if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()) - lifx->setPower(true); + if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){ + if (cloudDevice) { + lifxCloud->setPower(lightId, true); + } else { + lifx->setPower(true); + } + } int requestId; if (cloudDevice) { requestId = lifxCloud->setColor(lightId, color); @@ -325,8 +334,13 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) m_asyncActions.insert(requestId, info); } else if (action.actionTypeId() == colorBulbColorTemperatureActionTypeId) { int colorTemperature = 6500 - (action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toUInt() * -11.12); - if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()) - lifx->setPower(true); + if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){ + if (cloudDevice) { + lifxCloud->setPower(lightId, true); + } else { + lifx->setPower(true); + } + } int requestId; if (cloudDevice) { requestId = lifxCloud->setColorTemperature(lightId, colorTemperature); @@ -335,6 +349,40 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) } connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); m_asyncActions.insert(requestId, info); + } else if (action.actionTypeId() == colorBulbEffectStateTypeId) { + if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){ + if (cloudDevice) { + lifxCloud->setPower(lightId, true); + } else { + lifx->setPower(true); + } + } + QString effectString = action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toString(); + int requestId; + LifxCloud::Effect effect; + if (effectString == "None") { + effect = LifxCloud::EffectNone; + } else if (effectString == "Breathe") { + effect = LifxCloud::EffectBreathe; + } else if (effectString == "Move") { + effect = LifxCloud::EffectMove; + } else if (effectString == "Morph") { + effect = LifxCloud::EffectMove; + } else if (effectString == "Flame") { + effect = LifxCloud::EffectMove; + } else if (effectString == "Pulse") { + effect = LifxCloud::EffectMove; + } + if (cloudDevice) { + QColor color = QColor(thing->stateValue(colorBulbColorStateTypeId).toString()); + requestId = lifxCloud->setEffect(lightId, effect, color); + } else { + qCWarning(dcLifx()) << "LAN devices are not yet supported"; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + m_asyncActions.insert(requestId, info); } else { Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); } @@ -351,8 +399,13 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); } else if (action.actionTypeId() == dimmableBulbBrightnessActionTypeId) { int brightness = action.param(dimmableBulbBrightnessActionBrightnessParamTypeId).value().toInt(); - if (!thing->stateValue(dimmableBulbPowerStateTypeId).toBool()) - lifx->setPower(true); + if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){ + if (cloudDevice) { + lifxCloud->setPower(lightId, true); + } else { + lifx->setPower(true); + } + } int requestId; if (cloudDevice) { requestId = lifxCloud->setBrightnesss(lightId, brightness); @@ -361,6 +414,39 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) } connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); m_asyncActions.insert(requestId, info); + } else if (action.actionTypeId() == dimmableBulbEffectStateTypeId) { + if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){ + if (cloudDevice) { + lifxCloud->setPower(lightId, true); + } else { + lifx->setPower(true); + } + } + QString effectString = action.param(dimmableBulbColorTemperatureActionColorTemperatureParamTypeId).value().toString(); + int requestId; + LifxCloud::Effect effect; + if (effectString == "None") { + effect = LifxCloud::EffectNone; + } else if (effectString == "Breathe") { + effect = LifxCloud::EffectBreathe; + } else if (effectString == "Move") { + effect = LifxCloud::EffectMove; + } else if (effectString == "Morph") { + effect = LifxCloud::EffectMove; + } else if (effectString == "Flame") { + effect = LifxCloud::EffectMove; + } else if (effectString == "Pulse") { + effect = LifxCloud::EffectMove; + } + if (cloudDevice) { + requestId = lifxCloud->setEffect(lightId, effect, QColor("0xffffff")); + } else { + qCWarning(dcLifx()) << "LAN devices are not yet supported"; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + m_asyncActions.insert(requestId, info); } else { Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); } @@ -372,8 +458,8 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) void IntegrationPluginLifx::thingRemoved(Thing *thing) { if (thing->thingClassId() == colorBulbThingClassId || thing->thingClassId() == dimmableBulbThingClassId) { - if (m_lifxConnections.contains(thing)) - m_lifxConnections.take(thing)->deleteLater(); + if (m_lifxLanConnections.contains(thing)) + m_lifxLanConnections.take(thing)->deleteLater(); } else if (thing->thingClassId() == lifxAccountThingClassId) { if (m_lifxCloudConnections.contains(thing)) m_lifxCloudConnections.take(thing)->deleteLater(); @@ -412,17 +498,17 @@ void IntegrationPluginLifx::executeBrowserItem(BrowserActionInfo *info) connect(info, &BrowserActionInfo::aborted, this, [requestId, this] {m_asyncBrowserItem.remove(requestId);}); } -void IntegrationPluginLifx::onConnectionChanged(bool connected) +void IntegrationPluginLifx::onLifxLanConnectionChanged(bool connected) { Q_UNUSED(connected) - Lifx *lifx = static_cast(sender()); - Thing *thing = m_lifxConnections.key(lifx); + LifxLan *lifx = static_cast(sender()); + Thing *thing = m_lifxLanConnections.key(lifx); if (!thing) return; thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), connected); } -void IntegrationPluginLifx::onRequestExecuted(int requestId, bool success) +void IntegrationPluginLifx::onLifxLanRequestExecuted(int requestId, bool success) { if (m_asyncActions.contains(requestId)) { ThingActionInfo *info = m_asyncActions.take(requestId); @@ -492,8 +578,8 @@ void IntegrationPluginLifx::onLifxCloudLightsListReceived(const QListid()); foreach (Thing * thing, myThings().filterByParam(m_idParamTypeIds.value(thingClassId), light.id)) { thing->setStateValue(m_connectedStateTypeIds.value(thingClassId), light.connected); - thing->setStateValue(m_brightnessStateTypeIds.value(thingClassId), light.brightness); - thing->setStateValue(m_colorTemperatureStateTypeIds.value(thingClassId), light.colorTemperature); + thing->setStateValue(m_brightnessStateTypeIds.value(thingClassId), light.brightness*100.00); + thing->setStateValue(m_colorTemperatureStateTypeIds.value(thingClassId), light.colorTemperature); //TODO Kelvin to mired thing->setStateValue(m_powerStateTypeIds.value(thingClassId), light.power); if (thingClassId == colorBulbThingClassId) { thing->setStateValue(colorBulbColorStateTypeId, light.color); @@ -537,7 +623,6 @@ void IntegrationPluginLifx::onLifxCloudRequestExecuted(int requestId, bool succe } } - void IntegrationPluginLifx::onLifxCloudScenesListReceived(const QList &scenes) { LifxCloud *lifxCloud = static_cast(sender()); diff --git a/lifx/integrationpluginlifx.h b/lifx/integrationpluginlifx.h index 788e4b85..2b440d5d 100644 --- a/lifx/integrationpluginlifx.h +++ b/lifx/integrationpluginlifx.h @@ -33,7 +33,7 @@ #include "integrations/integrationplugin.h" #include "plugintimer.h" -#include "lifx.h" +#include "lifxlan.h" #include "lifxcloud.h" #include "network/networkaccessmanager.h" @@ -71,7 +71,7 @@ private: PluginTimer *m_pluginTimer = nullptr; QHash m_asyncCloudSetups; QHash m_asyncActions; - QHash m_lifxConnections; + QHash m_lifxLanConnections; QHash m_lifxCloudConnections; QHash m_asyncBrowseResults; QHash m_asyncBrowserItem; @@ -87,11 +87,11 @@ private: QHash m_idParamTypeIds; QHash m_pendingBrightnessAction; - QHash m_lifxProducts; + QHash m_lifxProducts; private slots: - void onConnectionChanged(bool connected); - void onRequestExecuted(int requestId, bool success); + void onLifxLanConnectionChanged(bool connected); + void onLifxLanRequestExecuted(int requestId, bool success); void onLifxCloudConnectionChanged(bool connected); void onLifxCloudAuthenticationChanged(bool authenticated); diff --git a/lifx/integrationpluginlifx.json b/lifx/integrationpluginlifx.json index eb69ebbd..59f4e486 100644 --- a/lifx/integrationpluginlifx.json +++ b/lifx/integrationpluginlifx.json @@ -51,23 +51,9 @@ "id": "12907c9c-e7f0-47f2-bd58-39d52ffdf24e", "name": "colorBulb", "displayName": "Color", - "createMethods": ["auto", "discovery"], + "createMethods": ["auto"], "interfaces": ["colorlight", "connectable"], "paramTypes": [ - { - "id": "fd1c4817-5111-433a-b5b9-fd9f49d4975c", - "name": "host", - "displayName": "Host address", - "type" : "QString", - "readOnly": true - }, - { - "id": "44c13745-300c-491f-b617-3a8d53472998", - "name": "port", - "displayName": "Port", - "type" : "int", - "readOnly": true - }, { "id": "976ecea0-ac25-47d4-9dc5-362962ddb6c0", "name": "id", @@ -139,10 +125,14 @@ "displayNameEvent": "Effect changed", "displayNameAction": "Set effect", "type": "QString", - "defaultValue": "none", + "defaultValue": "None", "possibleValues": [ - "none", - "color loop" + "None", + "Breathe", + "Move", + "Morph", + "Flame", + "Pulse" ], "writable": true } @@ -152,23 +142,9 @@ "id": "a5b02af8-7c97-4a78-9c78-bafee7407b5e", "name": "dimmableBulb", "displayName": "Day and Dusk", - "createMethods": ["auto", "discovery"], + "createMethods": ["auto"], "interfaces": ["colortemperaturelight", "connectable"], "paramTypes": [ - { - "id": "cc0a765b-a753-4e07-a6e5-47e9272c4346", - "name": "host", - "displayName": "Host address", - "type" : "QString", - "readOnly": true - }, - { - "id": "d233d9bf-6662-414d-92f6-dd3e267051b5", - "name": "port", - "displayName": "Port", - "type" : "int", - "readOnly": true - }, { "id": "f157a97b-3fe5-4d9e-b5e3-5636f80d46ed", "name": "id", @@ -221,6 +197,24 @@ "minValue": 153, "maxValue": 500, "writable": true + }, + { + "id": "be47c474-eca1-479e-9393-68281a43d72a", + "name": "effect", + "displayName": "Effect", + "displayNameEvent": "Effect changed", + "displayNameAction": "Set effect", + "type": "QString", + "defaultValue": "None", + "possibleValues": [ + "None", + "Breathe", + "Move", + "Morph", + "Flame", + "Pulse" + ], + "writable": true } ] } diff --git a/lifx/lifx.pro b/lifx/lifx.pro deleted file mode 100644 index 802b5391..00000000 --- a/lifx/lifx.pro +++ /dev/null @@ -1,14 +0,0 @@ -include(../plugins.pri) - -QT += network - -SOURCES += \ - integrationpluginlifx.cpp \ - lifx.cpp \ - lifxcloud.cpp \ - -HEADERS += \ - integrationpluginlifx.h \ - lifx.h \ - lifxcloud.h \ - diff --git a/lifx/lifxcloud.cpp b/lifx/lifxcloud.cpp index 4b3d13f9..e9c2c3a5 100644 --- a/lifx/lifxcloud.cpp +++ b/lifx/lifxcloud.cpp @@ -74,21 +74,9 @@ void LifxCloud::listLights() QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [reply, this] { - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - // check HTTP status code - if (status > 207) { - qCWarning(dcLifx()) << "Error get lights list" << status << reply->errorString(); + if(!checkHttpStatusCode(reply)) { return; } - if (!m_authenticated) { - m_authenticated = true; - emit authenticationChanged(true); - } - if (!m_connected) { - m_connected = true; - emit authenticationChanged(true); - } QByteArray rawData = reply->readAll(); QJsonDocument data; QJsonParseError error; @@ -110,6 +98,11 @@ void LifxCloud::listLights() Light light; light.id = object["id"].toString().toUtf8(); light.uuid = object["uuid"].toString().toUtf8(); + if (object["power"].toString() == "on") { + light.power = true; + } else { + light.power = false; + } light.label = object["label"].toString(); light.connected = object["connected"].toBool(); light.brightness = object["brightness"].toDouble(); @@ -162,21 +155,9 @@ void LifxCloud::listScenes() QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [reply, this] { - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - // check HTTP status code - if (status != 200) { - qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString(); + if(!checkHttpStatusCode(reply)) { return; } - if (!m_authenticated) { - m_authenticated = true; - emit authenticationChanged(true); - } - if (!m_connected) { - m_connected = true; - emit authenticationChanged(true); - } QByteArray rawData = reply->readAll(); qCDebug(dcLifx()) << "Got list scenes reply" << rawData; QJsonDocument data; QJsonParseError error; @@ -202,27 +183,27 @@ void LifxCloud::listScenes() int LifxCloud::setPower(const QString &lightId, bool power, int duration) { - return setState(lightId, StatePower, power, duration); + return setState("id:"+lightId, StatePower, power, duration); } int LifxCloud::setBrightnesss(const QString &lightId, int brightness, int duration) { - return setState(lightId, StateBrightness, brightness/100.00, duration); + return setState("id:"+lightId, StateBrightness, brightness/100.00, duration); } int LifxCloud::setColor(const QString &lightId, QColor color, int duration) { - return setState(lightId, StateColor, color.name(), duration); + return setState("id:"+lightId, StateColor, color.name(), duration); } -int LifxCloud::setColorTemperature(const QString &selector, int kelvin, int duration) +int LifxCloud::setColorTemperature(const QString &lightId, int kelvin, int duration) { - return setState(selector, StateColorTemperature, kelvin, duration); + return setState("id:"+lightId, StateColorTemperature, kelvin, duration); } int LifxCloud::setInfrared(const QString &lightId, int infrared, int duration) { - return setState(lightId, StateColor, infrared/100.00, duration); + return setState("id:"+lightId, StateColor, infrared/100.00, duration); } int LifxCloud::activateScene(const QString &sceneId) @@ -240,34 +221,59 @@ int LifxCloud::activateScene(const QString &sceneId) QNetworkReply *reply = m_networkManager->put(request, ""); connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - // check HTTP status code - if (status == 401 || status == 403) { - if (m_authenticated) { - m_authenticated = false; - emit authenticationChanged(false); - } - } - if (status > 207) { - qCWarning(dcLifx()) << "Error activate Scene" << status << reply->errorString(); - emit requestExecuted(requestId, false); - return; - } - if (!m_authenticated) { - m_authenticated = true; - emit authenticationChanged(true); - } - if (!m_connected) { - m_connected = true; - emit authenticationChanged(true); - } - emit requestExecuted(requestId, true); + emit requestExecuted(requestId, checkHttpStatusCode(reply)); QByteArray rawData = reply->readAll(); qCDebug(dcLifx()) << "Got activate scene reply" << rawData; }); return requestId; } +int LifxCloud::setEffect(const QString &lightId, LifxCloud::Effect effect, QColor color) +{ + if (m_authorizationToken.isEmpty()) { + qCWarning(dcLifx()) << "Authorization token is not set"; + return -1; + } + int requestId = qrand(); + QNetworkRequest request; + QJsonObject payload; + switch (effect) { + case LifxCloud::EffectNone: + request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/off").arg(lightId))); + break; + case LifxCloud::EffectBreathe: + request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/breathe").arg(lightId))); + payload["color"] = color.name(); + break; + case LifxCloud::EffectMove: + request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/move").arg(lightId))); + break; + case LifxCloud::EffectMorph: + request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/morph").arg(lightId))); + break; + case LifxCloud::EffectFlame: + request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/flame").arg(lightId))); + break; + case LifxCloud::EffectPulse: + request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/pulse").arg(lightId))); + payload["color"] = color.name(); + break; + } + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization","Bearer "+m_authorizationToken); + QJsonDocument doc; + doc.setObject(payload); + QNetworkReply *reply = m_networkManager->put(request, doc.toJson()); + connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { + + QByteArray rawData = reply->readAll(); + qCDebug(dcLifx()) << "Got set state reply" << rawData; + emit requestExecuted(requestId, checkHttpStatusCode(reply)); + }); + return requestId; +} + int LifxCloud::setState(const QString &selector, State state, QVariant stateValue, int duration) { if (m_authorizationToken.isEmpty()) { @@ -275,7 +281,6 @@ int LifxCloud::setState(const QString &selector, State state, QVariant stateValu return -1; } int requestId = qrand(); - QNetworkRequest request; request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/%1/state").arg(selector))); request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); @@ -287,45 +292,77 @@ int LifxCloud::setState(const QString &selector, State state, QVariant stateValu switch (state) { case StatePower: if (stateValue.toBool()) - payload["power"] = "ON"; + payload["power"] = "on"; else - payload["power"] = "OFF"; + payload["power"] = "off"; + + qCDebug(dcLifx()) << "Set state power" << stateValue.toBool(); break; case StateBrightness: payload["brightness"] = stateValue.toDouble(); + qCDebug(dcLifx()) << "Set state brightness" << stateValue; break; case StateColor: payload["color"] = stateValue.toString(); + qCDebug(dcLifx()) << "Set state color" << stateValue; break; case StateColorTemperature: payload["color"] = "kelvin:"+stateValue.toString(); + qCDebug(dcLifx()) << "Set state color" << stateValue; break; case StateInfrared: payload["infrared"] = stateValue.toDouble(); + qCDebug(dcLifx()) << "Set state infrared" << stateValue; } doc.setObject(payload); - QNetworkReply *reply = m_networkManager->post(request, doc.toJson()); + qCDebug(dcLifx()) << "Set state request" << request.url() << doc.toJson(); + QNetworkReply *reply = m_networkManager->put(request, doc.toJson()); connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); - connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] { - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - // check HTTP status code - if (status > 207) { - qCWarning(dcLifx()) << "Error get scene list" << status << reply->errorString(); - emit requestExecuted(requestId, false); - return; - } - if (!m_authenticated) { - m_authenticated = true; - emit authenticationChanged(true); - } - if (!m_connected) { - m_connected = true; - emit authenticationChanged(true); - } + connect(reply, &QNetworkReply::finished, this, [requestId, duration,reply, this] { + QByteArray rawData = reply->readAll(); qCDebug(dcLifx()) << "Got set state reply" << rawData; - emit requestExecuted(requestId, true); + if (checkHttpStatusCode(reply)) { + emit requestExecuted(requestId, true); + QTimer::singleShot(duration*1000+500, this, [=] {listLights();}); + } else { + emit requestExecuted(requestId, false); + } }); return requestId; } + +bool LifxCloud::checkHttpStatusCode(QNetworkReply *reply) +{ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcLifx()) << "Request error:" << status << reply->errorString(); + if (m_connected) { + m_connected = false; + emit connectionChanged(false); + } + return false; + } + // check HTTP status code + if (status == 401 || status == 403) { + if (m_authenticated) { + m_authenticated = false; + emit authenticationChanged(false); + } + } + if (status > 207) { + qCWarning(dcLifx()) << "Error get scene list" << status; + return false; + } + if (!m_authenticated) { + m_authenticated = true; + emit authenticationChanged(true); + } + if (!m_connected) { + m_connected = true; + emit connectionChanged(true); + } + return true; +} diff --git a/lifx/lifxcloud.h b/lifx/lifxcloud.h index 1e0025e1..ef4e513c 100644 --- a/lifx/lifxcloud.h +++ b/lifx/lifxcloud.h @@ -47,6 +47,16 @@ public: StateColorTemperature, StateInfrared }; + + enum Effect { + EffectNone, + EffectBreathe, + EffectMove, + EffectMorph, + EffectFlame, + EffectPulse + }; + struct Group { QByteArray id; QString name; @@ -101,19 +111,22 @@ public: void listLights(); void listScenes(); - int setPower(const QString &lightId, bool power, int duration = 3); - int setBrightnesss(const QString &lightId, int brightness, int duration = 3); - int setColor(const QString &selector, QColor color, int duration = 3); - int setColorTemperature(const QString &selector, int kelvin, int duration = 3); - int setInfrared(const QString &lightId, int infrared, int duration = 3); + int setPower(const QString &lightId, bool power, int duration = 5); + int setBrightnesss(const QString &lightId, int brightness, int duration = 5); + int setColor(const QString &lightId, QColor color, int duration = 5); + int setColorTemperature(const QString &lightId, int kelvin, int duration = 5); + int setInfrared(const QString &lightId, int infrared, int duration = 5); int activateScene(const QString &sceneId); + int setEffect(const QString &lightId, Effect effect, QColor color); + private: NetworkAccessManager *m_networkManager = nullptr; QByteArray m_authorizationToken; int setState(const QString &lightId, State state, QVariant stateValue, int duration); + bool checkHttpStatusCode(QNetworkReply *reply); bool m_authenticated = false; bool m_connected = false; signals: diff --git a/lifx/lifx.cpp b/lifx/lifxlan.cpp similarity index 77% rename from lifx/lifx.cpp rename to lifx/lifxlan.cpp index 1bb4eb73..2270b1ac 100644 --- a/lifx/lifx.cpp +++ b/lifx/lifxlan.cpp @@ -29,20 +29,16 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "lifx.h" +#include "lifxlan.h" #include "extern-plugininfo.h" #include - -Lifx::Lifx(const QHostAddress &address, quint16 port, QObject *parent) : +LifxLan::LifxLan(const QHostAddress &address, quint16 port, QObject *parent) : QObject(parent), m_host(address), m_port(port) { - m_reconnectTimer = new QTimer(this); - m_reconnectTimer->setSingleShot(true); - connect(m_reconnectTimer, &QTimer::timeout, this, &Lifx::onReconnectTimer); m_clientId = qrand(); m_socket = new QUdpSocket(this); @@ -51,7 +47,7 @@ Lifx::Lifx(const QHostAddress &address, quint16 port, QObject *parent) : m_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1)); } -Lifx::~Lifx() +LifxLan::~LifxLan() { if (m_socket) { m_socket->waitForBytesWritten(1000); @@ -59,7 +55,7 @@ Lifx::~Lifx() } } -bool Lifx::enable() +bool LifxLan::enable() { // Bind udp socket and join multicast group if(!m_socket->bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress)){ @@ -75,21 +71,21 @@ bool Lifx::enable() m_socket = nullptr; return false; } - connect(m_socket, &QUdpSocket::readyRead, this, &Lifx::onReadyRead); + connect(m_socket, &QUdpSocket::readyRead, this, &LifxLan::onReadyRead); return true; } -void Lifx::setHostAddress(const QHostAddress &address) +void LifxLan::setHostAddress(const QHostAddress &address) { m_host = address; } -void Lifx::setPort(quint16 port) +void LifxLan::setPort(quint16 port) { m_port = port; } -int Lifx::setColorTemperature(uint mirad, uint msFadeTime) +int LifxLan::setColorTemperature(uint mirad, uint msFadeTime) { Q_UNUSED(mirad) Q_UNUSED(msFadeTime) @@ -99,59 +95,48 @@ int Lifx::setColorTemperature(uint mirad, uint msFadeTime) return requestId; } -int Lifx::setColor(QColor color, uint msFadeTime) +int LifxLan::setColor(QColor color, uint msFadeTime) { Q_UNUSED(color) Q_UNUSED(msFadeTime) int requestId = qrand(); Message message; + //TODO create LAN message sendMessage(message); return requestId; } -int Lifx::setBrightness(uint percentage, uint msFadeTime) +int LifxLan::setBrightness(uint percentage, uint msFadeTime) { Q_UNUSED(percentage) Q_UNUSED(msFadeTime) int requestId = qrand(); Message message; sendMessage(message); + //TODO create LAN message return requestId; } -int Lifx::setPower(bool power, uint msFadeTime) +int LifxLan::setPower(bool power, uint msFadeTime) { Q_UNUSED(power) Q_UNUSED(msFadeTime) int requestId = qrand(); Message message; sendMessage(message); + //TODO create LAN message return requestId; } -int Lifx::flash() -{ - int requestId = qrand(); - - return requestId; -} - -int Lifx::flash15s() -{ - int requestId = qrand(); - - return requestId; -} - -void Lifx::sendMessage(const Lifx::Message &message) +void LifxLan::sendMessage(const LifxLan::Message &message) { QByteArray header; // -- FRAME -- // Protocol number: must be 1024 (decimal) quint16 protocol = 1024; - protocol |= (0x0001 << 4); //Message includes a target address: must be one (1) + protocol |= (0x0001 << 4); //Message includes a target address: must be one (1) protocol |= (message.frame.Tagged << 5); // Determines usage of the Frame Address target field - protocol &= ~(0x0003); // Message origin indicator: must be zero (0) + protocol &= ~(0x0003); // Message origin indicator: must be zero (0) header.append(protocol >> 8); header.append(protocol & 0xff); @@ -188,15 +173,12 @@ void Lifx::sendMessage(const Lifx::Message &message) header.append(2, '0'); QByteArray fullMessage; - //message.append(header); - //message.append(payload); - //message.append(message.length()); - fullMessage = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); - std::reverse(fullMessage.begin(), fullMessage.end()); + //fullMessage = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); // test message - set all lights green + //std::reverse(fullMessage.begin(), fullMessage.end()); m_socket->writeDatagram(fullMessage, m_host, m_port); } -void Lifx::onStateChanged(QAbstractSocket::SocketState state) +void LifxLan::onStateChanged(QAbstractSocket::SocketState state) { switch (state) { case QAbstractSocket::SocketState::ConnectedState: @@ -212,13 +194,8 @@ void Lifx::onStateChanged(QAbstractSocket::SocketState state) } } -void Lifx::onReadyRead() +void LifxLan::onReadyRead() { QByteArray data = m_socket->readAll(); qCDebug(dcLifx()) << "Message received" << data; } - -void Lifx::onReconnectTimer() -{ - -} diff --git a/lifx/lifx.h b/lifx/lifxlan.h similarity index 87% rename from lifx/lifx.h rename to lifx/lifxlan.h index 6ac42447..e15dea84 100644 --- a/lifx/lifx.h +++ b/lifx/lifxlan.h @@ -28,8 +28,8 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#ifndef LIFX_H -#define LIFX_H +#ifndef LIFXLAN_H +#define LIFXLAN_H #include #include @@ -40,7 +40,7 @@ #include -class Lifx : public QObject +class LifxLan : public QObject { Q_OBJECT public: @@ -122,8 +122,8 @@ public: bool chain; }; - explicit Lifx(const QHostAddress &address, quint16 port = 56700, QObject *parent = nullptr); - ~Lifx(); + explicit LifxLan(const QHostAddress &address, quint16 port = 56700, QObject *parent = nullptr); + ~LifxLan(); bool enable(); void setHostAddress(const QHostAddress &address); void setPort(quint16 port); @@ -132,8 +132,6 @@ public: int setColor(QColor color, uint msFadeTime = 500); int setBrightness(uint percentage, uint msFadeTime = 500); int setPower(bool power, uint msFadeTime = 500); - int flash(); - int flash15s(); private: quint32 m_clientId = 0; @@ -148,19 +146,9 @@ private: private slots: void onStateChanged(QAbstractSocket::SocketState state); void onReadyRead(); - void onReconnectTimer(); signals: void connectionChanged(bool connected); void requestExecuted(int requestId, bool success); - //void errorReceived(int code, const QString &message); - - //void powerNotificationReceived(bool status); - //void brightnessNotificationReceived(int percentage); - //void colorTemperatureNotificationReceived(int kelvin); - //void rgbNotificationReceived(QRgb rgbColor); - //void hueNotificationReceived(int hueColor); - //void nameNotificationReceived(const QString &name); - //void saturationNotificationReceived(int percentage); }; -#endif // LIFX_H +#endif // LIFXLAN_H diff --git a/lifx/meta.json b/lifx/meta.json index 5a34dd9c..37064e23 100644 --- a/lifx/meta.json +++ b/lifx/meta.json @@ -3,7 +3,7 @@ "tagline": "Control LIFX light bulbs.", "icon": "lifx.png", "stability": "consumer", - "offline": true, + "offline": false, "technologies": [ "network" ], diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts index 27a145ba..418b72fd 100644 --- a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts +++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts @@ -1,6 +1,17 @@ + + IntegrationPluginLifx + + LIFX server is not reachable. + + + + This token is invalid. + + + Lifx @@ -68,18 +79,6 @@ The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e}) Tag und Sonnenaufgang - - Host address - The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {cc0a765b-a753-4e07-a6e5-47e9272c4346}) ----------- -The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-5111-433a-b5b9-fd9f49d4975c}) - Adresse - - - Id - The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0}) - ID - LIFX The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87}) @@ -87,13 +86,6 @@ The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-511 The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992}) LIFX - - Port - The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {d233d9bf-6662-414d-92f6-dd3e267051b5}) ----------- -The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {44c13745-300c-491f-b617-3a8d53472998}) - Port - Power The name of the ParamType (ThingClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) @@ -148,7 +140,9 @@ The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClas Set effect - The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + The name of the ActionType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb +---------- +The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb Setze Effekt @@ -160,7 +154,13 @@ The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClas Effect - The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) + The name of the ParamType (ThingClass: dimmableBulb, ActionType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a}) +---------- +The name of the ParamType (ThingClass: dimmableBulb, EventType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a}) +---------- +The name of the StateType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb +---------- +The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- The name of the ParamType (ThingClass: colorBulb, EventType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- @@ -169,12 +169,16 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass Effect changed - The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + The name of the EventType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb +---------- +The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb Effekt geändert ID - The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed}) + The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed}) +---------- +The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0}) ID @@ -184,5 +188,46 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb Setze Helligkeit + + Connected + The name of the ParamType (ThingClass: lifxAccount, EventType: connected, ID: {3e7b358b-d7de-4db4-8a3a-b9860eae186f}) +---------- +The name of the StateType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount + + + + Connected changed + The name of the EventType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount + + + + LIFX cloud account + The name of the ThingClass ({387c87f6-3e5b-4d6a-ba4d-372d0efad79f}) + + + + Logged in + The name of the ParamType (ThingClass: lifxAccount, EventType: loggedIn, ID: {0db34069-5de0-4233-baec-27f039228524}) +---------- +The name of the StateType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount + + + + Logged in changed + The name of the EventType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount + + + + User name + The name of the ParamType (ThingClass: lifxAccount, EventType: userDisplayName, ID: {554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) +---------- +The name of the StateType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount + + + + User name changed + The name of the EventType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount + + diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts index 722805c7..cd993a05 100644 --- a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts +++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts @@ -1,6 +1,17 @@ + + IntegrationPluginLifx + + LIFX server is not reachable. + + + + This token is invalid. + + + Lifx @@ -68,18 +79,6 @@ The name of the EventType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClass The name of the ThingClass ({a5b02af8-7c97-4a78-9c78-bafee7407b5e}) - - Host address - The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {cc0a765b-a753-4e07-a6e5-47e9272c4346}) ----------- -The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-5111-433a-b5b9-fd9f49d4975c}) - - - - Id - The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {976ecea0-ac25-47d4-9dc5-362962ddb6c0}) - - LIFX The name of the vendor ({e5e48c0d-cff7-4c0f-983e-d23bd3e4ba87}) @@ -87,13 +86,6 @@ The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {fd1c4817-511 The name of the plugin Lifx ({4e00ee30-79e2-447b-8dcc-c34470f41992}) - - Port - The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {d233d9bf-6662-414d-92f6-dd3e267051b5}) ----------- -The name of the ParamType (ThingClass: colorBulb, Type: thing, ID: {44c13745-300c-491f-b617-3a8d53472998}) - - Power The name of the ParamType (ThingClass: dimmableBulb, ActionType: power, ID: {9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0}) @@ -148,7 +140,9 @@ The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClas Set effect - The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + The name of the ActionType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb +---------- +The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb @@ -160,7 +154,13 @@ The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClas Effect - The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) + The name of the ParamType (ThingClass: dimmableBulb, ActionType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a}) +---------- +The name of the ParamType (ThingClass: dimmableBulb, EventType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a}) +---------- +The name of the StateType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb +---------- +The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- The name of the ParamType (ThingClass: colorBulb, EventType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- @@ -169,12 +169,16 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass Effect changed - The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + The name of the EventType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb +---------- +The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb ID - The name of the ParamType (ThingClass: dimmableBulb, Type: thing, ID: {f157a97b-3fe5-4d9e-b5e3-5636f80d46ed}) + 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}) @@ -184,5 +188,46 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClass colorBulb + + Connected + The name of the ParamType (ThingClass: lifxAccount, EventType: connected, ID: {3e7b358b-d7de-4db4-8a3a-b9860eae186f}) +---------- +The name of the StateType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount + + + + Connected changed + The name of the EventType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount + + + + LIFX cloud account + The name of the ThingClass ({387c87f6-3e5b-4d6a-ba4d-372d0efad79f}) + + + + Logged in + The name of the ParamType (ThingClass: lifxAccount, EventType: loggedIn, ID: {0db34069-5de0-4233-baec-27f039228524}) +---------- +The name of the StateType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount + + + + Logged in changed + The name of the EventType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount + + + + User name + The name of the ParamType (ThingClass: lifxAccount, EventType: userDisplayName, ID: {554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) +---------- +The name of the StateType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount + + + + User name changed + The name of the EventType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount + + From 1d4563c6afbec1702f36cbaa79dea7b68e8a91a4 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 7 Jul 2020 09:30:42 +0200 Subject: [PATCH 09/15] updated german translation --- lifx/integrationpluginlifx.cpp | 13 +++++++------ lifx/lifx.pro | 14 ++++++++++++++ .../4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts | 18 +++++++++--------- 3 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 lifx/lifx.pro diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp index db94783b..84b26e17 100644 --- a/lifx/integrationpluginlifx.cpp +++ b/lifx/integrationpluginlifx.cpp @@ -115,7 +115,7 @@ void IntegrationPluginLifx::startPairing(ThingPairingInfo *info) if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError) { info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("LIFX server is not reachable.")); } else { - info->finish(Thing::ThingErrorNoError, "Please enter your Token and password. Get the token from https://cloud.lifx.com/settings"); + info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter your user name and token. Get the token from https://cloud.lifx.com/settings")); } }); } @@ -134,7 +134,7 @@ void IntegrationPluginLifx::confirmPairing(ThingPairingInfo *info, const QString // check HTTP status code if (status != 200) { // Error setting up device with invalid token - info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("This token is invalid.")); + info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("The token is invalid.")); return; } qCDebug(dcLifx()) << "Confirm pairing successfull"; @@ -237,8 +237,9 @@ void IntegrationPluginLifx::setupThing(ThingSetupInfo *info) connect(lifxCloud, &LifxCloud::authenticationChanged, this, &IntegrationPluginLifx::onLifxCloudAuthenticationChanged); lifxCloud->setAuthorizationToken(token); lifxCloud->listLights(); - - //TODO try setup again if it failes + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } @@ -247,13 +248,12 @@ void IntegrationPluginLifx::setupThing(ThingSetupInfo *info) void IntegrationPluginLifx::postSetupThing(Thing *thing) { if (!m_pluginTimer) { - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); + 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(); } @@ -275,6 +275,7 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) LifxCloud *lifxCloud; 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())); diff --git a/lifx/lifx.pro b/lifx/lifx.pro new file mode 100644 index 00000000..20671af6 --- /dev/null +++ b/lifx/lifx.pro @@ -0,0 +1,14 @@ +include(../plugins.pri) + +QT += network + +SOURCES += \ + integrationpluginlifx.cpp \ + lifxcloud.cpp \ + lifxlan.cpp \ + +HEADERS += \ + integrationpluginlifx.h \ + lifxcloud.h \ + lifxlan.h \ + diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts index 418b72fd..16041dec 100644 --- a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts +++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts @@ -5,11 +5,11 @@ IntegrationPluginLifx LIFX server is not reachable. - + LIFX Server ist nicht erreichbar This token is invalid. - + Der Token ist ungültig @@ -193,41 +193,41 @@ The name of the ActionType ({8bd20350-0e79-45dc-b68a-84da99356863}) of ThingClas The name of the ParamType (ThingClass: lifxAccount, EventType: connected, ID: {3e7b358b-d7de-4db4-8a3a-b9860eae186f}) ---------- The name of the StateType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount - + Verbunden Connected changed The name of the EventType ({3e7b358b-d7de-4db4-8a3a-b9860eae186f}) of ThingClass lifxAccount - + Verbunden geändert LIFX cloud account The name of the ThingClass ({387c87f6-3e5b-4d6a-ba4d-372d0efad79f}) - + LIFX Cloud-Account Logged in The name of the ParamType (ThingClass: lifxAccount, EventType: loggedIn, ID: {0db34069-5de0-4233-baec-27f039228524}) ---------- The name of the StateType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount - + Eingelogged Logged in changed The name of the EventType ({0db34069-5de0-4233-baec-27f039228524}) of ThingClass lifxAccount - + Eingelogged geändert User name The name of the ParamType (ThingClass: lifxAccount, EventType: userDisplayName, ID: {554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) ---------- The name of the StateType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount - + Benutzername User name changed The name of the EventType ({554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2}) of ThingClass lifxAccount - + Benutzername geändert From 83c22c0cfc9264e6561fa8975b7618e62f9a09f3 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Fri, 24 Jul 2020 18:27:04 +0200 Subject: [PATCH 10/15] fixed color temperature, reduced fade time --- lifx/integrationpluginlifx.cpp | 49 ++++----------------------------- lifx/integrationpluginlifx.json | 21 -------------- lifx/lifxcloud.cpp | 21 ++++++++------ lifx/lifxcloud.h | 12 ++++---- 4 files changed, 24 insertions(+), 79 deletions(-) diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp index 84b26e17..c07f8ca0 100644 --- a/lifx/integrationpluginlifx.cpp +++ b/lifx/integrationpluginlifx.cpp @@ -334,7 +334,7 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); m_asyncActions.insert(requestId, info); } else if (action.actionTypeId() == colorBulbColorTemperatureActionTypeId) { - int colorTemperature = 6500 - (action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toUInt() * -11.12); + 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); @@ -358,25 +358,19 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) lifx->setPower(true); } } - QString effectString = action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toString(); + QString effectString = action.param(colorBulbEffectActionEffectParamTypeId).value().toString(); int requestId; LifxCloud::Effect effect; if (effectString == "None") { effect = LifxCloud::EffectNone; } else if (effectString == "Breathe") { effect = LifxCloud::EffectBreathe; - } else if (effectString == "Move") { - effect = LifxCloud::EffectMove; - } else if (effectString == "Morph") { - effect = LifxCloud::EffectMove; - } else if (effectString == "Flame") { - effect = LifxCloud::EffectMove; } else if (effectString == "Pulse") { - effect = LifxCloud::EffectMove; + effect = LifxCloud::EffectPulse; } if (cloudDevice) { - QColor color = QColor(thing->stateValue(colorBulbColorStateTypeId).toString()); - requestId = lifxCloud->setEffect(lightId, effect, color); + //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); @@ -415,39 +409,6 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) } connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); m_asyncActions.insert(requestId, info); - } else if (action.actionTypeId() == dimmableBulbEffectStateTypeId) { - if (!thing->stateValue(colorBulbPowerStateTypeId).toBool()){ - if (cloudDevice) { - lifxCloud->setPower(lightId, true); - } else { - lifx->setPower(true); - } - } - QString effectString = action.param(dimmableBulbColorTemperatureActionColorTemperatureParamTypeId).value().toString(); - int requestId; - LifxCloud::Effect effect; - if (effectString == "None") { - effect = LifxCloud::EffectNone; - } else if (effectString == "Breathe") { - effect = LifxCloud::EffectBreathe; - } else if (effectString == "Move") { - effect = LifxCloud::EffectMove; - } else if (effectString == "Morph") { - effect = LifxCloud::EffectMove; - } else if (effectString == "Flame") { - effect = LifxCloud::EffectMove; - } else if (effectString == "Pulse") { - effect = LifxCloud::EffectMove; - } - if (cloudDevice) { - requestId = lifxCloud->setEffect(lightId, effect, QColor("0xffffff")); - } else { - qCWarning(dcLifx()) << "LAN devices are not yet supported"; - info->finish(Thing::ThingErrorHardwareNotAvailable); - return; - } - connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); - m_asyncActions.insert(requestId, info); } else { Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); } diff --git a/lifx/integrationpluginlifx.json b/lifx/integrationpluginlifx.json index 59f4e486..b5e6c52c 100644 --- a/lifx/integrationpluginlifx.json +++ b/lifx/integrationpluginlifx.json @@ -129,9 +129,6 @@ "possibleValues": [ "None", "Breathe", - "Move", - "Morph", - "Flame", "Pulse" ], "writable": true @@ -197,24 +194,6 @@ "minValue": 153, "maxValue": 500, "writable": true - }, - { - "id": "be47c474-eca1-479e-9393-68281a43d72a", - "name": "effect", - "displayName": "Effect", - "displayNameEvent": "Effect changed", - "displayNameAction": "Set effect", - "type": "QString", - "defaultValue": "None", - "possibleValues": [ - "None", - "Breathe", - "Move", - "Morph", - "Flame", - "Pulse" - ], - "writable": true } ] } diff --git a/lifx/lifxcloud.cpp b/lifx/lifxcloud.cpp index e9c2c3a5..1aabda98 100644 --- a/lifx/lifxcloud.cpp +++ b/lifx/lifxcloud.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -236,14 +237,16 @@ int LifxCloud::setEffect(const QString &lightId, LifxCloud::Effect effect, QColo } int requestId = qrand(); QNetworkRequest request; - QJsonObject payload; + 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))); - payload["color"] = color.name(); + 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))); @@ -256,19 +259,21 @@ int LifxCloud::setEffect(const QString &lightId, LifxCloud::Effect effect, QColo break; case LifxCloud::EffectPulse: request.setUrl(QUrl(QString("https://api.lifx.com/v1/lights/id:%1/effects/pulse").arg(lightId))); - payload["color"] = color.name(); + params.addQueryItem("color", color.name().trimmed()); + params.addQueryItem("period", "2"); + params.addQueryItem("cycles", "3"); break; } - request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded."); request.setRawHeader("Authorization","Bearer "+m_authorizationToken); - QJsonDocument doc; - doc.setObject(payload); - QNetworkReply *reply = m_networkManager->put(request, doc.toJson()); + 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 state reply" << rawData; + qCDebug(dcLifx()) << "Got set effect reply" << rawData; emit requestExecuted(requestId, checkHttpStatusCode(reply)); }); return requestId; diff --git a/lifx/lifxcloud.h b/lifx/lifxcloud.h index ef4e513c..df95369b 100644 --- a/lifx/lifxcloud.h +++ b/lifx/lifxcloud.h @@ -111,15 +111,15 @@ public: void listLights(); void listScenes(); - int setPower(const QString &lightId, bool power, int duration = 5); - int setBrightnesss(const QString &lightId, int brightness, int duration = 5); - int setColor(const QString &lightId, QColor color, int duration = 5); - int setColorTemperature(const QString &lightId, int kelvin, int duration = 5); - int setInfrared(const QString &lightId, int infrared, int duration = 5); + int 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); + int setEffect(const QString &lightId, Effect effect, QColor color = "#FFFFFF"); private: NetworkAccessManager *m_networkManager = nullptr; From bb692b90feff5c8d534a2f0bbc494f93a5e082b9 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Fri, 24 Jul 2020 18:29:39 +0200 Subject: [PATCH 11/15] updated translation --- ...4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts | 24 +++++++------------ ...0ee30-79e2-447b-8dcc-c34470f41992-en_US.ts | 22 +++++++---------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts index 16041dec..70809735 100644 --- a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts +++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-de.ts @@ -8,8 +8,12 @@ LIFX Server ist nicht erreichbar - This token is invalid. - Der Token ist ungültig + Please enter your user name and token. Get the token from https://cloud.lifx.com/settings + Bitte geben Sie Ihren Lifx Benutzernamen und Token ein. Holen Sie sich Ihren Token von https://cloud.lifx.com/settings + + + The token is invalid. + Der Token ist ungültig. @@ -140,9 +144,7 @@ The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClas Set effect - The name of the ActionType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb ----------- -The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb Setze Effekt @@ -154,13 +156,7 @@ The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClas Effect - The name of the ParamType (ThingClass: dimmableBulb, ActionType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a}) ----------- -The name of the ParamType (ThingClass: dimmableBulb, EventType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a}) ----------- -The name of the StateType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb ----------- -The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) + The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- The name of the ParamType (ThingClass: colorBulb, EventType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- @@ -169,9 +165,7 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass Effect changed - The name of the EventType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb ----------- -The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb Effekt geändert diff --git a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts index cd993a05..f869d2e1 100644 --- a/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts +++ b/lifx/translations/4e00ee30-79e2-447b-8dcc-c34470f41992-en_US.ts @@ -8,7 +8,11 @@ - This token is invalid. + Please enter your user name and token. Get the token from https://cloud.lifx.com/settings + + + + The token is invalid. @@ -140,9 +144,7 @@ The name of the ActionType ({dd7d7e70-5552-4531-8789-2d0f750488be}) of ThingClas Set effect - The name of the ActionType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb ----------- -The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + The name of the ActionType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb @@ -154,13 +156,7 @@ The name of the ActionType ({12de3f8f-2454-4057-aa12-9290296fdbdd}) of ThingClas Effect - The name of the ParamType (ThingClass: dimmableBulb, ActionType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a}) ----------- -The name of the ParamType (ThingClass: dimmableBulb, EventType: effect, ID: {be47c474-eca1-479e-9393-68281a43d72a}) ----------- -The name of the StateType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb ----------- -The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) + The name of the ParamType (ThingClass: colorBulb, ActionType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- The name of the ParamType (ThingClass: colorBulb, EventType: effect, ID: {65f88396-2958-480e-b0be-c4695400a343}) ---------- @@ -169,9 +165,7 @@ The name of the StateType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass Effect changed - The name of the EventType ({be47c474-eca1-479e-9393-68281a43d72a}) of ThingClass dimmableBulb ----------- -The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb + The name of the EventType ({65f88396-2958-480e-b0be-c4695400a343}) of ThingClass colorBulb From ea8f37ef838c94d50b41f6d473da264a3774b6d0 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 11 Aug 2020 10:13:53 +0200 Subject: [PATCH 12/15] fixed build error uninitialized lifxCloud --- lifx/integrationpluginlifx.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp index c07f8ca0..e0e2ac10 100644 --- a/lifx/integrationpluginlifx.cpp +++ b/lifx/integrationpluginlifx.cpp @@ -110,7 +110,7 @@ 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, [this, reply, info] { + 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.")); @@ -271,8 +271,8 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) Thing *thing = info->thing(); Action action = info->action(); bool cloudDevice = false; - LifxLan *lifx; - LifxCloud *lifxCloud; + LifxLan *lifx = nullptr; + LifxCloud *lifxCloud = nullptr; if (m_lifxLanConnections.contains(thing)) { // Local connection first From 9dbb943fabee045557a3bd470363105fc1fdc49c Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 11 Aug 2020 12:29:11 +0200 Subject: [PATCH 13/15] fixed uninitialiazed effect --- lifx/integrationpluginlifx.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lifx/integrationpluginlifx.cpp b/lifx/integrationpluginlifx.cpp index e0e2ac10..6f39f9e5 100644 --- a/lifx/integrationpluginlifx.cpp +++ b/lifx/integrationpluginlifx.cpp @@ -360,7 +360,7 @@ void IntegrationPluginLifx::executeAction(ThingActionInfo *info) } QString effectString = action.param(colorBulbEffectActionEffectParamTypeId).value().toString(); int requestId; - LifxCloud::Effect effect; + LifxCloud::Effect effect = LifxCloud::EffectNone; if (effectString == "None") { effect = LifxCloud::EffectNone; } else if (effectString == "Breathe") { From 435e1ccf5ad961486fe98a2434933af84b83fd6a Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Thu, 13 Aug 2020 13:10:34 +0200 Subject: [PATCH 14/15] invalid conversion --- lifx/lifxlan.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lifx/lifxlan.cpp b/lifx/lifxlan.cpp index 2270b1ac..2c707627 100644 --- a/lifx/lifxlan.cpp +++ b/lifx/lifxlan.cpp @@ -148,29 +148,29 @@ void LifxLan::sendMessage(const LifxLan::Message &message) //ADD RESERVED SECTION a reserved section of 48 bits (6 bytes) - header.append(6, 0x00); //that must be all zeros. + //header.append(6, '\x00'); //that must be all zeros. //ADD ACK and RES - header.append(2, 0x01); + //header.append(2, '\x01'); //ADD SEQUENCE NUMBER 1Byte - header.append(m_sequenceNumber++); + //header.append(m_sequenceNumber++); //Protocol header. which begins with 64 reserved bits (8 bytes). Set these all to zero. - header.append(8, 0x00); //that must be all zeros. + //header.append(8, '\x00'); //that must be all zeros. //ADD MESSAGE TYPE - header.append(static_cast(LightMessages::SetColor)); + //header.append(static_cast(LightMessages::SetColor)); // Finally another reserved field of 16 bits (2 bytes). - header.append(2, 0x00); + //header.append(2, '\x00'); //ADD SIZE - header.append(((static_cast(header.length()+1) & 0xff00) >> 8)); - header.append((static_cast(header.length()+1) & 0x00ff)); + //header.append(((static_cast(header.length()+1) & 0xff00) >> 8)); + //header.append((static_cast(header.length()+1) & 0x00ff)); //Finally another reserved field of 16 bits (2 bytes). - header.append(2, '0'); + //header.append(2, '\x00'); QByteArray fullMessage; //fullMessage = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); // test message - set all lights green From 825abadca268863b0fd1c481e08d4e95b9af0284 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Thu, 13 Aug 2020 14:14:13 +0200 Subject: [PATCH 15/15] fixed debian install file --- debian/nymea-plugin-lifx.install.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/nymea-plugin-lifx.install.in b/debian/nymea-plugin-lifx.install.in index ea0623b7..d8f7c34a 100644 --- a/debian/nymea-plugin-lifx.install.in +++ b/debian/nymea-plugin-lifx.install.in @@ -1 +1 @@ -usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginlifx.so +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginlifx.so