PhilipsHue: Improve discovery and error handling

This commit is contained in:
Michael Zanetti 2022-01-31 14:11:28 +01:00
parent eff66ea2f4
commit 6cb3e66716
12 changed files with 3188 additions and 2615 deletions

View File

@ -113,12 +113,7 @@ void IntegrationPluginPhilipsHue::discoverThings(ThingDiscoveryInfo *info)
// Tracking object // Tracking object
DiscoveryJob *discovery = new DiscoveryJob(); DiscoveryJob *discovery = new DiscoveryJob();
m_discoveries.insert(info, discovery); connect(info, &ThingDiscoveryInfo::destroyed, this, [=](){ delete discovery; });
// clean up the discovery job when the ThingDiscoveryInfo is destroyed (either finished or cancelled)
connect(info, &ThingDiscoveryInfo::destroyed, this, [this, info](){
delete m_discoveries.take(info);
});
foreach (const ZeroConfServiceEntry &entry, m_zeroConfBrowser->serviceEntries()) { foreach (const ZeroConfServiceEntry &entry, m_zeroConfBrowser->serviceEntries()) {
@ -152,6 +147,11 @@ void IntegrationPluginPhilipsHue::discoverThings(ThingDiscoveryInfo *info)
discovery->results.append(descriptor); discovery->results.append(descriptor);
} }
finishDiscovery(info, discovery);
}
void IntegrationPluginPhilipsHue::startUpnPDiscovery(ThingDiscoveryInfo *info, DiscoveryJob *discovery)
{
qCDebug(dcPhilipsHue()) << "Starting UPnP discovery..."; qCDebug(dcPhilipsHue()) << "Starting UPnP discovery...";
UpnpDiscoveryReply *upnpReply = hardwareManager()->upnpDiscovery()->discoverDevices("libhue:idl"); UpnpDiscoveryReply *upnpReply = hardwareManager()->upnpDiscovery()->discoverDevices("libhue:idl");
discovery->upnpReply = upnpReply; discovery->upnpReply = upnpReply;
@ -159,9 +159,10 @@ void IntegrationPluginPhilipsHue::discoverThings(ThingDiscoveryInfo *info)
connect(upnpReply, &UpnpDiscoveryReply::finished, upnpReply, &UpnpDiscoveryReply::deleteLater); connect(upnpReply, &UpnpDiscoveryReply::finished, upnpReply, &UpnpDiscoveryReply::deleteLater);
// Process results if info is still around // Process results if info is still around
connect(upnpReply, &UpnpDiscoveryReply::finished, info, [this, upnpReply, discovery](){ connect(upnpReply, &UpnpDiscoveryReply::finished, info, [=](){
// Indicate we're done... // Indicate we're done...
discovery->upnpDone = true;
discovery->upnpReply = nullptr; discovery->upnpReply = nullptr;
if (upnpReply->error() != UpnpDiscoveryReply::UpnpDiscoveryReplyErrorNoError) { if (upnpReply->error() != UpnpDiscoveryReply::UpnpDiscoveryReplyErrorNoError) {
@ -189,10 +190,12 @@ void IntegrationPluginPhilipsHue::discoverThings(ThingDiscoveryInfo *info)
} }
} }
finishDiscovery(discovery); finishDiscovery(info, discovery);
}); });
}
void IntegrationPluginPhilipsHue::startNUpnpDiscovery(ThingDiscoveryInfo *info, DiscoveryJob *discovery)
{
qCDebug(dcPhilipsHue) << "Starting N-UPNP discovery..."; qCDebug(dcPhilipsHue) << "Starting N-UPNP discovery...";
QNetworkRequest request(QUrl("https://discovery.meethue.com")); QNetworkRequest request(QUrl("https://discovery.meethue.com"));
QNetworkReply *nUpnpReply = hardwareManager()->networkManager()->get(request); QNetworkReply *nUpnpReply = hardwareManager()->networkManager()->get(request);
@ -202,20 +205,21 @@ void IntegrationPluginPhilipsHue::discoverThings(ThingDiscoveryInfo *info)
connect(nUpnpReply, &QNetworkReply::finished, nUpnpReply, &QNetworkReply::deleteLater); connect(nUpnpReply, &QNetworkReply::finished, nUpnpReply, &QNetworkReply::deleteLater);
// Process results if info is still around // Process results if info is still around
connect(nUpnpReply, &QNetworkReply::finished, info, [this, nUpnpReply, discovery](){ connect(nUpnpReply, &QNetworkReply::finished, info, [=](){
nUpnpReply->deleteLater();
discovery->nUpnpReply = nullptr; discovery->nUpnpReply = nullptr;
discovery->nUpnpDone = true;
if (nUpnpReply->error() != QNetworkReply::NoError) { if (nUpnpReply->error() != QNetworkReply::NoError) {
qCWarning(dcPhilipsHue()) << "N-UPnP discovery failed:" << nUpnpReply->error() << nUpnpReply->errorString(); qCWarning(dcPhilipsHue()) << "N-UPnP discovery failed:" << nUpnpReply->error() << nUpnpReply->errorString();
finishDiscovery(discovery); finishDiscovery(info, discovery);
return; return;
} }
QJsonParseError error; QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(nUpnpReply->readAll(), &error); QJsonDocument jsonDoc = QJsonDocument::fromJson(nUpnpReply->readAll(), &error);
if (error.error != QJsonParseError::NoError) { if (error.error != QJsonParseError::NoError) {
qCWarning(dcPhilipsHue) << "N-UPNP discovery JSON error in response" << error.errorString(); qCWarning(dcPhilipsHue) << "N-UPNP discovery JSON error in response" << error.errorString();
finishDiscovery(discovery); finishDiscovery(info, discovery);
return; return;
} }
@ -232,16 +236,28 @@ void IntegrationPluginPhilipsHue::discoverThings(ThingDiscoveryInfo *info)
discovery->results.append(descriptor); discovery->results.append(descriptor);
} }
finishDiscovery(discovery); finishDiscovery(info, discovery);
}); });
} }
void IntegrationPluginPhilipsHue::finishDiscovery(IntegrationPluginPhilipsHue::DiscoveryJob *job) void IntegrationPluginPhilipsHue::finishDiscovery(ThingDiscoveryInfo *info, IntegrationPluginPhilipsHue::DiscoveryJob *job)
{ {
if (job->upnpReply || job->nUpnpReply) { if (job->upnpReply || job->nUpnpReply) {
// still pending... // still pending...
return; return;
} }
if (job->results.isEmpty() && !job->upnpDone) {
qCDebug(dcPhilipsHue()) << "No results on mDNS. Trying UPnP...";
startUpnPDiscovery(info, job);
return;
}
if (job->results.isEmpty() && !job->nUpnpDone) {
qCDebug(dcPhilipsHue()) << "No results on UPnP. Trying NUPnP...";
startNUpnpDiscovery(info, job);
return;
}
QHash<QString, ThingDescriptor> results; QHash<QString, ThingDescriptor> results;
foreach (ThingDescriptor result, job->results) { foreach (ThingDescriptor result, job->results) {
// dedupe // dedupe
@ -259,8 +275,6 @@ void IntegrationPluginPhilipsHue::finishDiscovery(IntegrationPluginPhilipsHue::D
} }
ThingDiscoveryInfo *info = m_discoveries.key(job);
info->addThingDescriptors(results.values()); info->addThingDescriptors(results.values());
info->finish(Thing::ThingErrorNoError); info->finish(Thing::ThingErrorNoError);
} }
@ -269,7 +283,7 @@ void IntegrationPluginPhilipsHue::startPairing(ThingPairingInfo *info)
{ {
Q_ASSERT_X(info->thingClassId() == bridgeThingClassId, "DevicePluginPhilipsHue::startPairing", "Unexpected thing class."); Q_ASSERT_X(info->thingClassId() == bridgeThingClassId, "DevicePluginPhilipsHue::startPairing", "Unexpected thing class.");
info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please press the button on the Hue Bridge within 30 seconds before you continue")); info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please press the button on the Hue Bridge within 30 seconds before you continue."));
} }
void IntegrationPluginPhilipsHue::setupThing(ThingSetupInfo *info) void IntegrationPluginPhilipsHue::setupThing(ThingSetupInfo *info)
@ -705,7 +719,7 @@ void IntegrationPluginPhilipsHue::confirmPairing(ThingPairingInfo *info, const Q
connect(reply, &QNetworkReply::finished, info, [this, info, reply](){ connect(reply, &QNetworkReply::finished, info, [this, info, reply](){
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error connecting to hue bridge.")); info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Connecting to the Hue Bridge failed. Please make sure that your Hue Bridge is working and connected to the same network."));
return; return;
} }
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
@ -715,22 +729,29 @@ void IntegrationPluginPhilipsHue::confirmPairing(ThingPairingInfo *info, const Q
// check JSON error // check JSON error
if (error.error != QJsonParseError::NoError) { if (error.error != QJsonParseError::NoError) {
qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString();
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Received unexpected data from hue bridge.")); info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The pairing process failed. Please make sure that your Hue Bridge is working."));
return; return;
} }
if (jsonDoc.toVariant().toList().isEmpty()) {
qCWarning(dcPhilipsHue) << "Hue Bridge empty json!";
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The pairing process failed. Please make sure that your Hue Bridge is working."));
return;
}
QVariantMap pairingReply = jsonDoc.toVariant().toList().first().toMap();
// check response error // check response error
if (data.contains("error")) { if (pairingReply.contains("error")) {
if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to pair Hue Bridge:" << pairingReply;
qCWarning(dcPhilipsHue) << "Failed to pair Hue Bridge:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); if (pairingReply.value("error").toMap().value("type").toInt() == 101) {
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The pairing process failed. The link button has not been pressed. Please follow the on-screen instructions again."));
} else { } else {
qCWarning(dcPhilipsHue) << "Failed to pair Hue Bridge: Invalid error message format"; info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Please make sure that your Hue bridge is working and follow the on-screen instructions again."));
} }
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("An error happened pairing the hue bridge."));
return; return;
} }
QString apiKey = jsonDoc.toVariant().toList().first().toMap().value("success").toMap().value("username").toString(); QString apiKey = pairingReply.value("success").toMap().value("username").toString();
if (apiKey.isEmpty()) { if (apiKey.isEmpty()) {
qCWarning(dcPhilipsHue) << "Failed to pair Hue Bridge: did not get any key from the bridge"; qCWarning(dcPhilipsHue) << "Failed to pair Hue Bridge: did not get any key from the bridge";

View File

@ -92,12 +92,16 @@ private:
class DiscoveryJob { class DiscoveryJob {
public: public:
UpnpDiscoveryReply* upnpReply; UpnpDiscoveryReply* upnpReply;
bool upnpDone = false;
QNetworkReply* nUpnpReply; QNetworkReply* nUpnpReply;
bool nUpnpDone = false;
ThingDescriptors results; ThingDescriptors results;
}; };
ZeroConfServiceBrowser *m_zeroConfBrowser = nullptr; ZeroConfServiceBrowser *m_zeroConfBrowser = nullptr;
QHash<ThingDiscoveryInfo*, DiscoveryJob*> m_discoveries;
void finishDiscovery(DiscoveryJob* job); void startUpnPDiscovery(ThingDiscoveryInfo *info, DiscoveryJob *discovery);
void startNUpnpDiscovery(ThingDiscoveryInfo *info, DiscoveryJob *discovery);
void finishDiscovery(ThingDiscoveryInfo *info, DiscoveryJob* job);
PluginTimer *m_pluginTimer1Sec = nullptr; PluginTimer *m_pluginTimer1Sec = nullptr;
PluginTimer *m_pluginTimer5Sec = nullptr; PluginTimer *m_pluginTimer5Sec = nullptr;
@ -131,7 +135,6 @@ private:
void setLightName(Thing *thing); void setLightName(Thing *thing);
void setRemoteName(Thing *thing); void setRemoteName(Thing *thing);
void processNUpnpResponse(const QByteArray &data);
void processBridgeLightDiscoveryResponse(Thing *thing, const QByteArray &data); void processBridgeLightDiscoveryResponse(Thing *thing, const QByteArray &data);
void processBridgeSensorDiscoveryResponse(Thing *thing, const QByteArray &data); void processBridgeSensorDiscoveryResponse(Thing *thing, const QByteArray &data);
void processLightRefreshResponse(Thing *thing, const QByteArray &data); void processLightRefreshResponse(Thing *thing, const QByteArray &data);

View File

@ -11,7 +11,7 @@
{ {
"id": "642aa4c7-19aa-45ed-ba06-aa1ae6c9edf7", "id": "642aa4c7-19aa-45ed-ba06-aa1ae6c9edf7",
"name": "bridge", "name": "bridge",
"displayName": "Hue gateway", "displayName": "Philips Hue Bridge",
"interfaces": ["gateway", "update"], "interfaces": ["gateway", "update"],
"providedInterfaces": ["light", "button", "presencesensor", "powersocket"], "providedInterfaces": ["light", "button", "presencesensor", "powersocket"],
"createMethods": ["discovery"], "createMethods": ["discovery"],