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 + + + +