API unstability of Yeelight is bugging me

This commit is contained in:
Boernsman 2019-12-29 20:52:17 +01:00
parent 9014a17618
commit c78d3663d9
7 changed files with 69 additions and 35 deletions

View File

@ -1 +1,5 @@
# Yeelight # Yeelight
This plug-in supports dimmable- and color bulbs from yeelight.
Discovery works with min. firmware version 2.0.6_0065

View File

@ -46,19 +46,19 @@ void DevicePluginYeelight::init()
m_brightnessStateTypeIds.insert(colorBulbDeviceClassId, colorBulbBrightnessStateTypeId); m_brightnessStateTypeIds.insert(colorBulbDeviceClassId, colorBulbBrightnessStateTypeId);
m_brightnessStateTypeIds.insert(dimmableBulbDeviceClassId, dimmableBulbBrightnessStateTypeId); m_brightnessStateTypeIds.insert(dimmableBulbDeviceClassId, dimmableBulbBrightnessStateTypeId);
m_ssdp = new Ssdp(this);
m_ssdp->enable();
m_ssdp->discover();
} }
void DevicePluginYeelight::discoverDevices(DeviceDiscoveryInfo *info) void DevicePluginYeelight::discoverDevices(DeviceDiscoveryInfo *info)
{ {
if (info->deviceClassId() == colorBulbDeviceClassId) { if (info->deviceClassId() == colorBulbDeviceClassId) {
DiscoveryJob *discovery = new DiscoveryJob();
m_discoveries.insert(info, discovery);
qCDebug(dcYeelight()) << "Starting UPnP discovery..."; qCDebug(dcYeelight()) << "Starting UPnP discovery...";
Ssdp *ssdp = new Ssdp(this); /*
ssdp->enable();
ssdp->discover();
connect(ssdp, &Ssdp::discovered, this,[info, this](const QString &address, int port, int id, const QString &model) { connect(ssdp, &Ssdp::discovered, this,[info, this](const QString &address, int port, int id, const QString &model) {
DeviceDescriptor descriptor(colorBulbDeviceClassId, "Yeelight"+ model + "Bulb", address); DeviceDescriptor descriptor(colorBulbDeviceClassId, "Yeelight"+ model + "Bulb", address);
@ -75,7 +75,7 @@ void DevicePluginYeelight::discoverDevices(DeviceDiscoveryInfo *info)
descriptor.setParams(params); descriptor.setParams(params);
qCDebug(dcYeelight()) << "UPnP: Found Yeelight device:" << id; qCDebug(dcYeelight()) << "UPnP: Found Yeelight device:" << id;
info->finish(Device::DeviceErrorNoError); info->finish(Device::DeviceErrorNoError);
}); });*/
} }
} }
@ -113,13 +113,15 @@ void DevicePluginYeelight::postSetupDevice(Device *device)
if (yeelight->isConnected()) { if (yeelight->isConnected()) {
QList<Yeelight::YeelightProperty> properties; QList<Yeelight::YeelightProperty> properties;
if (device->deviceClassId() == colorBulbDeviceClassId) { if (device->deviceClassId() == colorBulbDeviceClassId) {
properties << Yeelight::YeelightProperty::Name;
properties << Yeelight::YeelightProperty::Ct; properties << Yeelight::YeelightProperty::Ct;
properties << Yeelight::YeelightProperty::Rgb; properties << Yeelight::YeelightProperty::Rgb;
properties << Yeelight::YeelightProperty::Power; properties << Yeelight::YeelightProperty::Power;
properties << Yeelight::YeelightProperty::Bright; properties << Yeelight::YeelightProperty::Bright;
properties << Yeelight::YeelightProperty::ColorMode; properties << Yeelight::YeelightProperty::ColorMode;
} //TODO add white color bulb with other property requests } else if (device->deviceClassId() == colorBulbDeviceClassId) {
properties << Yeelight::YeelightProperty::Power;
properties << Yeelight::YeelightProperty::Bright;
}
yeelight->getParam(properties); yeelight->getParam(properties);
} }
} }
@ -162,14 +164,14 @@ void DevicePluginYeelight::executeAction(DeviceActionInfo *info)
}); });
} }
} else if (action.actionTypeId() == colorBulbColorActionColorParamTypeId) { } else if (action.actionTypeId() == colorBulbColorActionColorParamTypeId) {
QRgb color = action.param(colorBulbColorActionColorParamTypeId).value().toUInt(); QRgb color = QColor(action.param(colorBulbColorActionColorParamTypeId).value().toString()).rgba();
if (!device->stateValue(colorBulbPowerStateTypeId).toBool()) if (!device->stateValue(colorBulbPowerStateTypeId).toBool())
yeelight->setPower(true); yeelight->setPower(true);
int requestId = yeelight->setRgb(color); int requestId = yeelight->setRgb(color);
connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info); m_asyncActions.insert(requestId, info);
} else if (action.actionTypeId() == colorBulbColorTemperatureActionTypeId) { } else if (action.actionTypeId() == colorBulbColorTemperatureActionTypeId) {
int colorTemperature = action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toUInt() * 11.12; int colorTemperature = 6500 - (action.param(colorBulbColorTemperatureActionColorTemperatureParamTypeId).value().toUInt() * -11.12);
if (!device->stateValue(colorBulbPowerStateTypeId).toBool()) if (!device->stateValue(colorBulbPowerStateTypeId).toBool())
yeelight->setPower(true); yeelight->setPower(true);
int requestId = yeelight->setColorTemperature(colorTemperature); int requestId = yeelight->setColorTemperature(colorTemperature);
@ -201,6 +203,10 @@ void DevicePluginYeelight::executeAction(DeviceActionInfo *info)
connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info); m_asyncActions.insert(requestId, info);
} }
} else if (action.actionTypeId() == colorBulbDefaultActionTypeId) {
int requestId = yeelight->setDefault();
connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
} else { } else {
qCWarning(dcYeelight()) << "Unhandled action"; qCWarning(dcYeelight()) << "Unhandled action";
} }
@ -229,6 +235,10 @@ void DevicePluginYeelight::executeAction(DeviceActionInfo *info)
connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info); m_asyncActions.insert(requestId, info);
} }
} else if (action.actionTypeId() == dimmableBulbDefaultActionTypeId) {
int requestId = yeelight->setDefault();
connect(info, &DeviceActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
} else { } else {
qCWarning(dcYeelight()) << "Unhandled action"; qCWarning(dcYeelight()) << "Unhandled action";
} }
@ -239,7 +249,8 @@ void DevicePluginYeelight::executeAction(DeviceActionInfo *info)
void DevicePluginYeelight::deviceRemoved(Device *device) void DevicePluginYeelight::deviceRemoved(Device *device)
{ {
if ((device->deviceClassId() == colorBulbDeviceClassId) || (device->deviceClassId() == dimmableBulbDeviceClassId)){ if ((device->deviceClassId() == colorBulbDeviceClassId) ||
(device->deviceClassId() == dimmableBulbDeviceClassId)){
Yeelight *yeelight = m_yeelightConnections.take(device->id()); Yeelight *yeelight = m_yeelightConnections.take(device->id());
yeelight->deleteLater(); yeelight->deleteLater();
} }

View File

@ -28,6 +28,7 @@
#include "network/networkaccessmanager.h" #include "network/networkaccessmanager.h"
#include "network/upnp/upnpdiscovery.h" #include "network/upnp/upnpdiscovery.h"
#include "yeelight.h" #include "yeelight.h"
#include "ssdp.h"
#include <QTimer> #include <QTimer>
@ -49,15 +50,7 @@ public:
void deviceRemoved(Device *device) override; void deviceRemoved(Device *device) override;
private: private:
class DiscoveryJob { Ssdp *m_ssdp = nullptr;
public:
UpnpDiscoveryReply* upnpReply;
QNetworkReply* nUpnpReply;
DeviceDescriptors results;
};
QHash<DeviceDiscoveryInfo*, DiscoveryJob*> m_discoveries;
QList<QString> m_usedInterfaces;
PluginTimer *m_pluginTimer = nullptr; PluginTimer *m_pluginTimer = nullptr;
QHash<int, DeviceActionInfo *> m_asyncActions; QHash<int, DeviceActionInfo *> m_asyncActions;
QHash<DeviceId, Yeelight *> m_yeelightConnections; QHash<DeviceId, Yeelight *> m_yeelightConnections;

View File

@ -53,6 +53,7 @@ bool Ssdp::enable()
// Bind udp socket and join multicast group // Bind udp socket and join multicast group
m_socket = new QUdpSocket(this); m_socket = new QUdpSocket(this);
m_socket2 = new QUdpSocket(this);
m_port = 1982; m_port = 1982;
m_host = QHostAddress("239.255.255.250"); m_host = QHostAddress("239.255.255.250");
@ -67,6 +68,13 @@ bool Ssdp::enable()
return false; return false;
} }
if(!m_socket2->bind(QHostAddress::AnyIPv4, 34343, QUdpSocket::ShareAddress)){
qCWarning(dcYeelight()) << "could not bind to port" << 34343;
delete m_socket2;
m_socket2 = nullptr;
return false;
}
if(!m_socket->joinMulticastGroup(m_host)){ if(!m_socket->joinMulticastGroup(m_host)){
qCWarning(dcYeelight()) << "could not join multicast group" << m_host; qCWarning(dcYeelight()) << "could not join multicast group" << m_host;
m_available = false; m_available = false;
@ -76,6 +84,7 @@ bool Ssdp::enable()
} }
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError))); connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
connect(m_socket, &QUdpSocket::readyRead, this, &Ssdp::readData); connect(m_socket, &QUdpSocket::readyRead, this, &Ssdp::readData);
connect(m_socket2, &QUdpSocket::readyRead, this, &Ssdp::readData2);
return true; return true;
} }
@ -84,8 +93,8 @@ void Ssdp::discover()
QByteArray searchMessage = QByteArray("M-SEARCH * HTTP/1.1\r\n" QByteArray searchMessage = QByteArray("M-SEARCH * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1982\r\n" "HOST: 239.255.255.250:1982\r\n"
"MAN: \"ssdp:discover\"\r\n" "MAN: \"ssdp:discover\"\r\n"
"ST: wifi_bulb\r\n\r\n"); "ST: wifi_bulb\r\n");
m_socket->writeDatagram(searchMessage, m_host, m_port); m_socket2->writeDatagram(searchMessage, m_host, m_port);
} }
@ -134,6 +143,20 @@ void Ssdp::readData()
} }
} }
void Ssdp::readData2()
{
QByteArray data;
quint16 port;
QHostAddress hostAddress;
// read the answere from the multicast
while (m_socket->hasPendingDatagrams()) {
data.resize(m_socket->pendingDatagramSize());
m_socket->readDatagram(data.data(), data.size(), &hostAddress, &port);
}
qCDebug(dcYeelight())<< "SSDP message received " << data;
}
bool Ssdp::disable() bool Ssdp::disable()
{ {

View File

@ -45,6 +45,7 @@ public:
bool disable(); bool disable();
private: private:
QUdpSocket *m_socket = nullptr; QUdpSocket *m_socket = nullptr;
QUdpSocket *m_socket2 = nullptr;
QHostAddress m_host; QHostAddress m_host;
quint16 m_port; quint16 m_port;
@ -64,6 +65,7 @@ signals:
private slots: private slots:
void error(QAbstractSocket::SocketError error); void error(QAbstractSocket::SocketError error);
void readData(); void readData();
void readData2();
}; };
#endif // SSDP_H #endif // SSDP_H

View File

@ -145,7 +145,7 @@ int Yeelight::getParam(QList<Yeelight::YeelightProperty> properties)
QTimer::singleShot(10000, this, [requestId, this]{m_propertyRequests.remove(requestId);}); QTimer::singleShot(10000, this, [requestId, this]{m_propertyRequests.remove(requestId);});
obj["params"] = params; obj["params"] = params;
doc.setObject(obj); doc.setObject(obj);
qCDebug(dcYeelight()) << "Sending request" << doc.toJson(); //qCDebug(dcYeelight()) << "Sending request" << doc.toJson();
m_socket->write(doc.toJson() + "\r\n"); m_socket->write(doc.toJson() + "\r\n");
return requestId; return requestId;
} }
@ -161,7 +161,7 @@ int Yeelight::setName(const QString &name)
params.append(name); params.append(name);
obj["params"] = params; obj["params"] = params;
doc.setObject(obj); doc.setObject(obj);
qCDebug(dcYeelight()) << "Sending request" << doc.toJson(); //qCDebug(dcYeelight()) << "Sending request" << doc.toJson();
m_socket->write(doc.toJson() + "\r\n"); m_socket->write(doc.toJson() + "\r\n");
return requestId; return requestId;
} }
@ -192,12 +192,12 @@ int Yeelight::setRgb(QRgb color, int msFadeTime)
obj["id"] = requestId; obj["id"] = requestId;
obj["method"] = "set_rgb"; obj["method"] = "set_rgb";
QJsonArray params; QJsonArray params;
params.append(QVariant(color).toInt()); params.append(static_cast<int>(color & 0x00ffffff));
params.append("smooth"); params.append("smooth");
params.append(msFadeTime); params.append(msFadeTime);
obj["params"] = params; obj["params"] = params;
doc.setObject(obj); doc.setObject(obj);
qCDebug(dcYeelight()) << "Sending request" << doc.toJson(); //qCDebug(dcYeelight()) << "Sending request" << doc.toJson();
m_socket->write(doc.toJson() + "\r\n"); m_socket->write(doc.toJson() + "\r\n");
return requestId; return requestId;
} }
@ -252,7 +252,7 @@ int Yeelight::setDefault()
QJsonArray params; QJsonArray params;
obj["params"] = params; obj["params"] = params;
doc.setObject(obj); doc.setObject(obj);
qCDebug(dcYeelight()) << "Sending request" << doc.toJson(); //qCDebug(dcYeelight()) << "Sending request" << doc.toJson();
m_socket->write(doc.toJson() + "\r\n"); m_socket->write(doc.toJson() + "\r\n");
return requestId; return requestId;
} }
@ -267,7 +267,7 @@ int Yeelight::startColorFlow()
QJsonArray params; QJsonArray params;
params.append(0); //0 means infinite loop on the state changing params.append(0); //0 means infinite loop on the state changing
params.append(0); //LED recover to the state before the color flow started params.append(0); //LED recover to the state before the color flow started
params.append("2000, 1, 255, -1, 2000, 1, 16711680, -1, 2000, 1, 65280, -1,"); //Colors params.append("2000, 1, 255, 50, 2000, 1, 5000, 50, 2000, 1, 6000, 50"); //Colors
obj["params"] = params; obj["params"] = params;
doc.setObject(obj); doc.setObject(obj);
qCDebug(dcYeelight()) << "Sending request" << doc.toJson(); qCDebug(dcYeelight()) << "Sending request" << doc.toJson();
@ -298,9 +298,9 @@ int Yeelight::flash()
obj["id"] = requestId; obj["id"] = requestId;
obj["method"] = "start_cf"; obj["method"] = "start_cf";
QJsonArray params; QJsonArray params;
params.append(6); params.append(4 * 3);
params.append(0); //LED recover to the state before the color flow started params.append(0); //LED recover to the state before the color flow started
params.append("500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0,"); //Colors params.append("50, 2, 6500, 100, 500, 7, 6500, 1, 50, 2, 6500, 1, 500, 7, 6500, 1");
obj["params"] = params; obj["params"] = params;
doc.setObject(obj); doc.setObject(obj);
qCDebug(dcYeelight()) << "Sending request" << doc.toJson(); qCDebug(dcYeelight()) << "Sending request" << doc.toJson();
@ -316,9 +316,9 @@ int Yeelight::flash15s()
obj["id"] = requestId; obj["id"] = requestId;
obj["method"] = "start_cf"; obj["method"] = "start_cf";
QJsonArray params; QJsonArray params;
params.append(30); params.append(4 * 15);
params.append(0); //LED recover to the state before the color flow started params.append(0); //LED recover to the state before the color flow started
params.append("500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0, 500, 2, 4000, 100, 500, 7, 0, 0"); //Colors params.append("50, 2, 6500, 100, 500, 7, 6500, 1, 50, 2, 6500, 1, 500, 7, 6500, 1");
obj["params"] = params; obj["params"] = params;
doc.setObject(obj); doc.setObject(obj);
qCDebug(dcYeelight()) << "Sending request" << doc.toJson(); qCDebug(dcYeelight()) << "Sending request" << doc.toJson();
@ -345,7 +345,7 @@ void Yeelight::onStateChanged(QAbstractSocket::SocketState state)
void Yeelight::onReadyRead() void Yeelight::onReadyRead()
{ {
QByteArray data = m_socket->readAll(); QByteArray data = m_socket->readAll();
qCDebug(dcYeelight()) << "Message received" << data; //qCDebug(dcYeelight()) << "Message received" << data;
QJsonParseError error; QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error); QJsonDocument doc = QJsonDocument::fromJson(data, &error);
@ -446,6 +446,7 @@ void Yeelight::onReconnectTimer()
{ {
if(!m_socket->isOpen()) { if(!m_socket->isOpen()) {
m_socket->connectToHost(m_address, m_port); m_socket->connectToHost(m_address, m_port);
m_reconnectTimer->start(10 * 1000);
} }
} }

View File

@ -7,9 +7,9 @@ TARGET = $$qtLibraryTarget(nymea_devicepluginyeelight)
SOURCES += \ SOURCES += \
devicepluginyeelight.cpp \ devicepluginyeelight.cpp \
yeelight.cpp \ yeelight.cpp \
ssdp.cpp ssdp.cpp \
HEADERS += \ HEADERS += \
devicepluginyeelight.h \ devicepluginyeelight.h \
yeelight.h \ yeelight.h \
ssdp.h ssdp.h \