diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 5605b2b5..6790bd12 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -65,6 +65,7 @@ PLUGIN_DIRS = \ wakeonlan \ wemo \ ws2812fx \ + yeelight \ message(============================================) diff --git a/yeelight/README.md b/yeelight/README.md new file mode 100644 index 00000000..1aeecc2d --- /dev/null +++ b/yeelight/README.md @@ -0,0 +1 @@ +# Yeelight diff --git a/yeelight/devicepluginyeelight.cpp b/yeelight/devicepluginyeelight.cpp new file mode 100644 index 00000000..ae84fc3e --- /dev/null +++ b/yeelight/devicepluginyeelight.cpp @@ -0,0 +1,113 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "devicepluginyeelight.h" + +#include "devices/device.h" +#include "types/param.h" +#include "plugininfo.h" +#include "network/upnp/upnpdiscovery.h" +#include "network/upnp/upnpdiscoveryreply.h" + +#include +#include + +DevicePluginYeelight::DevicePluginYeelight() +{ + +} + +void DevicePluginYeelight::discoverDevices(DeviceDiscoveryInfo *info) +{ + if (info->deviceClassId() == colorBulbDeviceClassId) { + + DiscoveryJob *discovery = new DiscoveryJob(); + m_discoveries.insert(info, discovery); + + qCDebug(dcYeelight()) << "Starting UPnP discovery..."; + UpnpDiscoveryReply *upnpReply = hardwareManager()->upnpDiscovery()->discoverDevices("wifi_bulb"); + discovery->upnpReply = upnpReply; + // Always clean up the upnp discovery + connect(upnpReply, &UpnpDiscoveryReply::finished, upnpReply, &UpnpDiscoveryReply::deleteLater); + + // Process results if info is still around + connect(upnpReply, &UpnpDiscoveryReply::finished, info, [this, upnpReply, discovery](){ + + // Indicate we're done... + discovery->upnpReply = nullptr; + + if (upnpReply->error() != UpnpDiscoveryReply::UpnpDiscoveryReplyErrorNoError) { + qCWarning(dcYeelight()) << "Upnp discovery error" << upnpReply->error(); + } + + foreach (const UpnpDeviceDescriptor &upnpDevice, upnpReply->deviceDescriptors()) { + if (upnpDevice.modelDescription().contains("color")) { + DeviceDescriptor descriptor(bridgeDeviceClassId, "Yeelight Color Bulb", upnpDevice.hostAddress().toString()); + ParamList params; + QString bridgeId = upnpDevice.serialNumber().toLower(); + foreach (Device *existingDevice, myDevices()) { + if (existingDevice->paramValue(bridgeDeviceIdParamTypeId).toString() == bridgeId) { + descriptor.setDeviceId(existingDevice->id()); + break; + } + } + params.append(Param(bridgeDeviceHostParamTypeId, upnpDevice.hostAddress().toString())); + params.append(Param(bridgeDeviceIdParamTypeId, bridgeId)); + descriptor.setParams(params); + qCDebug(dcYeelight()) << "UPnP: Found Hue bridge:" << bridgeId; + discovery->results.append(descriptor); + } + } + //finishDiscovery(discovery); + }); + + info->finish(Device::DeviceErrorNoError); + } +} + +void DevicePluginYeelight::setupDevice(DeviceSetupInfo *info) +{ + Device *device = info->device(); + if (device->deviceClassId() == colorBulbDeviceClassId) { + QHostAddress *address = device->paramValue(colorBulbDeviceAddressParamTypeId).toString(); + int port; + Yeelight *yeelight = new Yeelight(hardwareManager(), address, port, this); + info->finish(Device::DeviceErrorNoError); + } +} + +void DevicePluginYeelight::executeAction(DeviceActionInfo *info) +{ + Device *device = info->device(); + Action action = info->action(); + + if (device->deviceClassId() == colorBulbDeviceClassId) { + + // if (action.actionTypeId() == ) { + //} + } +} + +void DevicePluginYeelight::deviceRemoved(Device *device) +{ + +} diff --git a/yeelight/devicepluginyeelight.h b/yeelight/devicepluginyeelight.h new file mode 100644 index 00000000..ec34c1c1 --- /dev/null +++ b/yeelight/devicepluginyeelight.h @@ -0,0 +1,69 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINYEELIGHT_H +#define DEVICEPLUGINYEELIGHT_H + +#include "devices/deviceplugin.h" +#include "plugintimer.h" +#include "network/networkaccessmanager.h" +#include "network/upnp/upnpdiscovery.h" +#include "yeelight.h" + +#include + +class DevicePluginYeelight : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginyeelight.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginYeelight(); + + void discoverDevices(DeviceDiscoveryInfo *info) override; + void setupDevice(DeviceSetupInfo *info) override; + void executeAction(DeviceActionInfo *info) override; + void deviceRemoved(Device *device) override; + +private: + class DiscoveryJob { + public: + UpnpDiscoveryReply* upnpReply; + QNetworkReply* nUpnpReply; + DeviceDescriptors results; + }; + QHash m_discoveries; + QList m_usedInterfaces; + QTimer *m_reconnectTimer = nullptr; + QHash m_asyncActions; + +private slots: + void onReadyRead(); + void onReconnectTimer(); + +signals: + +}; + +#endif // DEVICEPLUGINYEELIGHT_H diff --git a/yeelight/devicepluginyeelight.json b/yeelight/devicepluginyeelight.json new file mode 100644 index 00000000..c0d23219 --- /dev/null +++ b/yeelight/devicepluginyeelight.json @@ -0,0 +1,102 @@ +{ + "displayName": "Yeelight", + "name": "yeelight", + "id": "40012951-be9f-4de6-b6e1-0a7455fdd416", + "vendors": [ + { + "name": "Yeelight", + "displayName": "Yeelight", + "id": "74011456-0a0f-4e43-bd94-bf4bb69593a4", + "deviceClasses": [ + { + "id": "7d801b71-0aee-4b5d-ae62-a950473b20f5", + "name": "colorBulb", + "displayName": "Color bulb", + "createMethods": ["user", "discovery"], + "interfaces": ["colorlight", "connectable"], + "paramTypes": [ + { + "id": "a603ff66-0da8-42e8-942b-1632a22adefb", + "name": "host", + "displayName": "Host address", + "type" : "QString", + "inputType": "IPv4Address", + "readOnly": true + }, + { + "id": "bfe184d2-6e62-4979-8ff2-3fd190903c82", + "name": "id", + "displayName": "Id", + "type" : "QString", + "readOnly": true + } + ], + "actionTypes": [ + { + "id": "e80aea31-7ac0-4f71-9650-efb70ea1e768", + "name": "default", + "displayName": "Set default", + "type": "bool" + } + ], + "stateTypes": [ + { + "id": "7cb7d7ae-f281-46c9-83ea-52b1d7e8e2c8", + "name": "connected", + "displayName": "Reachable", + "displayNameEvent": "Reachable changed", + "defaultValue": false, + "type": "bool" + }, + { + "id": "759b661e-8726-41c8-9c29-c5885eb19018", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true + }, + { + "id": "01f058cc-4bfb-439c-8344-f7ffdd2b2ed0", + "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": "d74cfe47-b6e6-47ae-a8e9-bb99b8ae63fe", + "name": "color", + "displayName": "Color", + "displayNameEvent": "Color changed", + "displayNameAction": "Set color", + "type": "QColor", + "defaultValue": "#000000", + "writable": true + }, + { + "id": "a7a16fb8-0e8b-4aa3-977d-3ff7cd65c708", + "name": "brightness", + "displayName": "Brightness", + "displayNameEvent": "Brightness changed", + "displayNameAction": "Set brigtness", + "type": "int", + "unit": "Percentage", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100, + "writable": true + } + ] + } + ] + } + ] +} diff --git a/yeelight/yeelight.cpp b/yeelight/yeelight.cpp new file mode 100644 index 00000000..b1f536fd --- /dev/null +++ b/yeelight/yeelight.cpp @@ -0,0 +1,239 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#include "yeelight.h" +#include "extern-plugininfo.h" + +#include +#include +#include + +#include + +Yeelight::Yeelight(NetworkAccessManager *networkManager, const QHostAddress &address, quint16 port, QObject *parent) : + QObject(parent), + m_address(address), + m_port(port), + m_networkManager(networkManager) +{ + m_socket = new QTcpSocket(this); + connect(m_socket, &QTcpSocket::stateChanged, this, &Yeelight::onStateChanged); + m_socket->connectToHost(address, port); +} + +bool Yeelight::connected() +{ + return m_socket->isOpen(); +} + + +int Yeelight::getParam(QList properties) +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + QJsonDocument doc; + QJsonObject obj; + obj["id"] = requestId; + obj["method"] = "get_prop"; + QJsonArray params; + + foreach (Property property, properties) { + switch (property) { + case Property::Ct: + params.append("ct"); + break; + case Property::Power: + params.append("powr"); + break; + case Property::Bright: + params.append("bright"); + break; + case Property::Hue: + params.append("hue"); + break; + case Property::Rgb: + params.append("rgb"); + break; + case Property::Sat: + params.append("sat"); + break; + case Property::Name: + params.append("name"); + break; + case Property::BgCt: + params.append("bg_ct"); + break; + case Property::NlBr: + params.append("nl_br"); + break; + case Property::BgHue: + params.append("bg_hue"); + break; + case Property::BgRgb: + params.append("bg_rgb"); + break; + case Property::BgSat: + params.append("bg_sat"); + break; + case Property::BgLmode: + params.append("bg_lmode"); + break; + case Property::BgPower: + params.append("bg_power"); + break; + case Property::Flowing: + params.append("flowing"); + break; + case Property::MusicOn: + params.append("music_on"); + break; + case Property::BgBright: + params.append("bg_bright"); + break; + case Property::DelayOff: + params.append("delay_off"); + break; + case Property::BgFlowing: + params.append("bg_flowing"); + break; + case Property::ColorMode: + params.append("color_mode"); + break; + case Property::ActiveMode: + params.append("active_mode"); + break; + case Property::FlowParams: + params.append("flow_params"); + break; + case Property::BgFlowParams: + params.append("bg_flow_params"); + break; + } + } + + obj["params"] = params; + doc.setObject(obj); + m_socket->write(doc.toJson()); + return requestId; +} + +int Yeelight::setName(const QString &name) +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + QJsonDocument doc; + QJsonObject obj; + obj["id"] = requestId; + obj["method"] = "set_name"; + QJsonArray params; + params.append(name); + obj["params"] = params; + return requestId; +} + +int Yeelight::setColorTemperature(int mirad, int msFadeTime) +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + QJsonDocument doc; + QJsonObject obj; + obj["id"] = requestId; + obj["method"] = "set_ct_abx"; + QJsonArray params; + params.append(mirad); + params.append("smooth"); + params.append(msFadeTime); + obj["params"] = params; + return requestId; +} + +int Yeelight::setRgb(QRgb color, int msFadeTime) +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + QJsonDocument doc; + QJsonObject obj; + obj["id"] = requestId; + obj["method"] = "set_rgb"; + QJsonArray params; + params.append(QVariant(color).toInt()); + params.append("smooth"); + params.append(msFadeTime); + obj["params"] = params; + return requestId; +} + +int Yeelight::setBrightness(int percentage, int msFadeTime) +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + QJsonDocument doc; + QJsonObject obj; + obj["id"] = requestId; + obj["method"] = "set_bright"; + QJsonArray params; + params.append(percentage); + params.append("smooth"); + params.append(msFadeTime); + obj["params"] = params; + return requestId; +} + +int Yeelight::setPower(bool power, int msFadeTime) +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + QJsonDocument doc; + QJsonObject obj; + obj["id"] = requestId; + obj["method"] = "set_power"; + QJsonArray params; + if(power) { + params.append("on"); + } else { + params.append("off"); + } + params.append("smooth"); + params.append(msFadeTime); + obj["params"] = params; + return requestId; +} + +int Yeelight::setDefault() +{ + int requestId = static_cast(QRandomGenerator::global()->generate()); + QJsonDocument doc; + QJsonObject obj; + obj["id"] = requestId; + obj["method"] = "set_default"; + QJsonArray params; + obj["params"] = params; + return requestId; +} + +void Yeelight::onStateChanged(QAbstractSocket::SocketState state) +{ + switch (state) { + case QAbstractSocket::SocketState::ConnectedState: + + break; + default: + break; + } +} + + diff --git a/yeelight/yeelight.h b/yeelight/yeelight.h new file mode 100644 index 00000000..092bb7ff --- /dev/null +++ b/yeelight/yeelight.h @@ -0,0 +1,95 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef YEELIGHT_H +#define YEELIGHT_H + +#include +#include +#include +#include + +#include "network/networkaccessmanager.h" +#include "devices/device.h" + +#include + +class Yeelight : public QObject +{ + Q_OBJECT +public: + enum Property { + Power, //on: smart LED is turned on / off: smart LED is turned off + Bright, //Brightness percentage. Range 1 ~ 100 + Ct, //Color temperature. Range 1700 ~ 6500(k) + Rgb, //Color. Range 1 ~ 16777215 + Hue, //Hue. Range 0 ~ 359 + Sat, //Saturation. Range 0 ~ 100 + ColorMode, //1: rgb mode / 2: color temperature mode / 3: hsv mode + Flowing, //0: no flow is running / 1:color flow is running + DelayOff, //The remaining time of a sleep timer. Range 1 ~ 60 (minutes) + FlowParams, //Current flow parameters (only meaningful when 'flowing' is 1) + MusicOn, //1: Music mode is on / 0: Music mode is off + Name, //The name of the device set by “set_name” command + BgPower, //Background light power status + BgFlowing, //Background light is flowing + BgFlowParams, //Current flow parameters of background light + BgCt, //Color temperature of background light + BgLmode, //1: rgb mode / 2: color temperature mode / 3: hsv mode + BgBright, //Brightness percentage of background light + BgRgb, //Color of background light + BgHue, //Hue of background light + BgSat, //Saturation of background light + NlBr, //Brightness of night mode light + ActiveMode //0: daylight mode / 1: moonlight mode (ceiling light only) + }; + + explicit Yeelight(NetworkAccessManager *networkManager, const QHostAddress &address, quint16 port = 55443, QObject *parent = nullptr); + bool connected(); + + int getParam(QList properties); + int setName(const QString &name); + int setColorTemperature(int mirad, int msFadeTime=500); + int setRgb(QRgb color, int msFadeTime = 500); + int setBrightness(int percentage, int msFadeTime = 500); + int setPower(bool power, int msFadeTime = 500); + int setDefault(); + + +private: + + QTcpSocket *m_socket = nullptr; + QHostAddress m_address; + qint16 m_port; + NetworkAccessManager *m_networkManager = nullptr; + +private slots: + void onRefreshTimeout(); + void onStateChanged(QAbstractSocket::SocketState state); + +signals: + void connectionChanged(bool connected); + void authenticationStatusChanged(bool authenticated); + void requestExecuted(int requestId, bool success); + void propertyReceived(Property property, QVariant value); +}; +#endif // YEELIGHT_H diff --git a/yeelight/yeelight.pro b/yeelight/yeelight.pro new file mode 100644 index 00000000..780b952c --- /dev/null +++ b/yeelight/yeelight.pro @@ -0,0 +1,13 @@ +include(../plugins.pri) + +QT += network + +TARGET = $$qtLibraryTarget(nymea_devicepluginyeelight) + +SOURCES += \ + devicepluginyeelight.cpp \ + yeelight.cpp \ + +HEADERS += \ + devicepluginyeelight.h \ + yeelight.h \