/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2014 Michael Zanetti * * Copyright (C) 2015 - 2019 Simon Stürz * * Copyright (C) 2018 Michael Zanetti * * * * 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 "devicepluginphilipshue.h" #include "devicemanager.h" #include "plugin/device.h" #include "types/param.h" #include "plugininfo.h" #include "network/upnp/upnpdiscovery.h" #include "network/upnp/upnpdiscoveryreply.h" #include #include #include #include #include DevicePluginPhilipsHue::DevicePluginPhilipsHue() { } DevicePluginPhilipsHue::~DevicePluginPhilipsHue() { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer1Sec); hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer5Sec); hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer15Sec); } void DevicePluginPhilipsHue::init() { m_pluginTimer1Sec = hardwareManager()->pluginTimerManager()->registerTimer(1); connect(m_pluginTimer1Sec, &PluginTimer::timeout, this, [this]() { // refresh sensors every second if (m_remotes.isEmpty()) { return; } foreach (HueBridge *bridge, m_bridges.keys()) { refreshSensors(bridge); } }); m_pluginTimer5Sec = hardwareManager()->pluginTimerManager()->registerTimer(5); connect(m_pluginTimer5Sec, &PluginTimer::timeout, this, [this]() { // refresh lights every 5 seconds foreach (HueBridge *bridge, m_bridges.keys()) { refreshLights(bridge); } }); m_pluginTimer15Sec = hardwareManager()->pluginTimerManager()->registerTimer(15); connect(m_pluginTimer15Sec, &PluginTimer::timeout, this, [this]() { // refresh bridges every 15 seconds foreach (Device *device, m_bridges.values()) { refreshBridge(device); } }); } DeviceManager::DeviceError DevicePluginPhilipsHue::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) { Q_UNUSED(params) Q_UNUSED(deviceClassId) UpnpDiscoveryReply *reply = hardwareManager()->upnpDiscovery()->discoverDevices("libhue:idl"); connect(reply, &UpnpDiscoveryReply::finished, this, &DevicePluginPhilipsHue::onUpnpDiscoveryFinished); return DeviceManager::DeviceErrorAsync; } DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *device) { // Update the name on the bridge if the user changes the device name connect(device, &Device::nameChanged, this, &DevicePluginPhilipsHue::onDeviceNameChanged); // hue bridge if (device->deviceClassId() == bridgeDeviceClassId) { // unconfigured bridges (from pairing) foreach (HueBridge *b, m_unconfiguredBridges) { if (b->hostAddress().toString() == device->paramValue(bridgeDeviceHostParamTypeId).toString()) { m_unconfiguredBridges.removeAll(b); qCDebug(dcPhilipsHue) << "Setup unconfigured Hue Bridge" << b->name(); // Set data which was not known during discovery device->setParamValue(bridgeDeviceApiKeyParamTypeId, b->apiKey()); device->setParamValue(bridgeDeviceZigbeeChannelParamTypeId, b->zigbeeChannel()); device->setParamValue(bridgeDeviceMacParamTypeId, b->macAddress()); m_bridges.insert(b, device); device->setStateValue(bridgeConnectedStateTypeId, true); discoverBridgeDevices(b); return DeviceManager::DeviceSetupStatusSuccess; } } // Loaded bridge qCDebug(dcPhilipsHue) << "Setup Hue Bridge" << device->params(); HueBridge *bridge = new HueBridge(this); bridge->setId(device->paramValue(bridgeDeviceIdParamTypeId).toString()); bridge->setApiKey(device->paramValue(bridgeDeviceApiKeyParamTypeId).toString()); bridge->setHostAddress(QHostAddress(device->paramValue(bridgeDeviceHostParamTypeId).toString())); bridge->setMacAddress(device->paramValue(bridgeDeviceMacParamTypeId).toString()); bridge->setZigbeeChannel(device->paramValue(bridgeDeviceZigbeeChannelParamTypeId).toInt()); m_bridges.insert(bridge, device); discoverBridgeDevices(bridge); return DeviceManager::DeviceSetupStatusSuccess; } // Hue color light if (device->deviceClassId() == colorLightDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue color light" << device->params(); HueBridge *bridge = m_bridges.key(myDevices().findById(device->parentId())); HueLight *hueLight = new HueLight(this); hueLight->setHostAddress(bridge->hostAddress()); hueLight->setApiKey(bridge->apiKey()); hueLight->setId(device->paramValue(colorLightDeviceLightIdParamTypeId).toInt()); hueLight->setModelId(device->paramValue(colorLightDeviceModelIdParamTypeId).toString()); hueLight->setUuid(device->paramValue(colorLightDeviceUuidParamTypeId).toString()); hueLight->setType(device->paramValue(colorLightDeviceTypeParamTypeId).toString()); connect(hueLight, &HueLight::stateChanged, this, &DevicePluginPhilipsHue::lightStateChanged); m_lights.insert(hueLight, device); refreshLight(device); return DeviceManager::DeviceSetupStatusSuccess; } // Hue color temperature light if (device->deviceClassId() == colorTemperatureLightDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue color temperature light" << device->params(); HueBridge *bridge = m_bridges.key(myDevices().findById(device->parentId())); HueLight *hueLight = new HueLight(this); hueLight->setHostAddress(bridge->hostAddress()); hueLight->setApiKey(bridge->apiKey()); hueLight->setId(device->paramValue(colorTemperatureLightDeviceLightIdParamTypeId).toInt()); hueLight->setModelId(device->paramValue(colorTemperatureLightDeviceModelIdParamTypeId).toString()); hueLight->setUuid(device->paramValue(colorTemperatureLightDeviceUuidParamTypeId).toString()); hueLight->setType(device->paramValue(colorTemperatureLightDeviceTypeParamTypeId).toString()); connect(hueLight, &HueLight::stateChanged, this, &DevicePluginPhilipsHue::lightStateChanged); m_lights.insert(hueLight, device); refreshLight(device); return DeviceManager::DeviceSetupStatusSuccess; } // Hue white light if (device->deviceClassId() == dimmableLightDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue white light" << device->params(); HueBridge *bridge = m_bridges.key(myDevices().findById(device->parentId())); HueLight *hueLight = new HueLight(this); hueLight->setHostAddress(bridge->hostAddress()); hueLight->setApiKey(bridge->apiKey()); hueLight->setId(device->paramValue(dimmableLightDeviceLightIdParamTypeId).toInt()); hueLight->setModelId(device->paramValue(dimmableLightDeviceModelIdParamTypeId).toString()); hueLight->setUuid(device->paramValue(dimmableLightDeviceUuidParamTypeId).toString()); hueLight->setType(device->paramValue(dimmableLightDeviceTypeParamTypeId).toString()); connect(hueLight, &HueLight::stateChanged, this, &DevicePluginPhilipsHue::lightStateChanged); m_lights.insert(hueLight, device); refreshLight(device); return DeviceManager::DeviceSetupStatusSuccess; } // Hue remote if (device->deviceClassId() == remoteDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue remote" << device->params() << device->deviceClassId(); HueBridge *bridge = m_bridges.key(myDevices().findById(device->parentId())); HueRemote *hueRemote = new HueRemote(this); hueRemote->setHostAddress(bridge->hostAddress()); hueRemote->setApiKey(bridge->apiKey()); hueRemote->setId(device->paramValue(remoteDeviceSensorIdParamTypeId).toInt()); hueRemote->setModelId(device->paramValue(remoteDeviceModelIdParamTypeId).toString()); hueRemote->setType(device->paramValue(remoteDeviceTypeParamTypeId).toString()); hueRemote->setUuid(device->paramValue(remoteDeviceUuidParamTypeId).toString()); connect(hueRemote, &HueRemote::stateChanged, this, &DevicePluginPhilipsHue::remoteStateChanged); connect(hueRemote, &HueRemote::buttonPressed, this, &DevicePluginPhilipsHue::onRemoteButtonEvent); m_remotes.insert(hueRemote, device); return DeviceManager::DeviceSetupStatusSuccess; } // Hue tap if (device->deviceClassId() == tapDeviceClassId) { HueRemote *hueTap = new HueRemote(this); hueTap->setName(device->name()); hueTap->setId(device->paramValue(tapDeviceSensorIdParamTypeId).toInt()); hueTap->setModelId(device->paramValue(tapDeviceModelIdParamTypeId).toString()); connect(hueTap, &HueRemote::stateChanged, this, &DevicePluginPhilipsHue::remoteStateChanged); connect(hueTap, &HueRemote::buttonPressed, this, &DevicePluginPhilipsHue::onRemoteButtonEvent); m_remotes.insert(hueTap, device); return DeviceManager::DeviceSetupStatusSuccess; } // Hue Outdoor sensor if (device->deviceClassId() == outdoorSensorDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue Outdoor sensor" << device->params(); HueOutdoorSensor *outdoorSensor = new HueOutdoorSensor(this); outdoorSensor->setUuid(device->paramValue(outdoorSensorDeviceUuidParamTypeId).toString()); outdoorSensor->setModelId(device->paramValue(outdoorSensorDeviceModelIdParamTypeId).toString()); outdoorSensor->setTemperatureSensorId(device->paramValue(outdoorSensorDeviceSensorIdTemperatureParamTypeId).toInt()); outdoorSensor->setTemperatureSensorUuid(device->paramValue(outdoorSensorDeviceSensorUuidTemperatureParamTypeId).toString()); outdoorSensor->setPresenceSensorId(device->paramValue(outdoorSensorDeviceSensorIdPresenceParamTypeId).toInt()); outdoorSensor->setPresenceSensorUuid(device->paramValue(outdoorSensorDeviceSensorUuidPresenceParamTypeId).toString()); outdoorSensor->setLightSensorId(device->paramValue(outdoorSensorDeviceSensorIdLightParamTypeId).toInt()); outdoorSensor->setLightSensorUuid(device->paramValue(outdoorSensorDeviceSensorUuidLightParamTypeId).toString()); connect(outdoorSensor, &HueOutdoorSensor::reachableChanged, this, &DevicePluginPhilipsHue::onOutdoorSensorReachableChanged); connect(outdoorSensor, &HueOutdoorSensor::batteryLevelChanged, this, &DevicePluginPhilipsHue::onOutdoorSensorBatteryLevelChanged); connect(outdoorSensor, &HueOutdoorSensor::temperatureChanged, this, &DevicePluginPhilipsHue::onOutdoorSensorTemperatureChanged); connect(outdoorSensor, &HueOutdoorSensor::presenceChanged, this, &DevicePluginPhilipsHue::onOutdoorSensorPresenceChanged); connect(outdoorSensor, &HueOutdoorSensor::lightIntensityChanged, this, &DevicePluginPhilipsHue::onOutdoorSensorLightIntensityChanged); m_outdoorSensors.insert(outdoorSensor, device); return DeviceManager::DeviceSetupStatusSuccess; } qCWarning(dcPhilipsHue()) << "Unhandled setupDevice call" << device->deviceClassId(); return DeviceManager::DeviceSetupStatusFailure; } void DevicePluginPhilipsHue::deviceRemoved(Device *device) { abortRequests(m_lightRefreshRequests, device); abortRequests(m_setNameRequests, device); abortRequests(m_bridgeRefreshRequests, device); abortRequests(m_lightsRefreshRequests, device); abortRequests(m_sensorsRefreshRequests, device); abortRequests(m_bridgeLightsDiscoveryRequests, device); abortRequests(m_bridgeSensorsDiscoveryRequests, device); abortRequests(m_bridgeSearchDevicesRequests, device); if (device->deviceClassId() == bridgeDeviceClassId) { HueBridge *bridge = m_bridges.key(device); m_bridges.remove(bridge); bridge->deleteLater(); } if (device->deviceClassId() == colorLightDeviceClassId || device->deviceClassId() == colorTemperatureLightDeviceClassId || device->deviceClassId() == dimmableLightDeviceClassId) { HueLight *light = m_lights.key(device); m_lights.remove(light); light->deleteLater(); } if (device->deviceClassId() == remoteDeviceClassId || device->deviceClassId() == tapDeviceClassId) { HueRemote *remote = m_remotes.key(device); m_remotes.remove(remote); remote->deleteLater(); } if (device->deviceClassId() == outdoorSensorDeviceClassId) { HueOutdoorSensor *outdoorSensor = m_outdoorSensors.key(device); m_outdoorSensors.remove(outdoorSensor); outdoorSensor->deleteLater(); } } DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::confirmPairing(const PairingTransactionId &pairingTransactionId, const DeviceClassId &deviceClassId, const ParamList ¶ms, const QString &secret) { Q_UNUSED(secret) qCDebug(dcPhilipsHue()) << "Confirming pairing for transactionId" << pairingTransactionId; if (deviceClassId != bridgeDeviceClassId) return DeviceManager::DeviceSetupStatusFailure; PairingInfo *pairingInfo = new PairingInfo(this); pairingInfo->setPairingTransactionId(pairingTransactionId); pairingInfo->setHost(QHostAddress(params.paramValue(bridgeDeviceHostParamTypeId).toString())); QVariantMap deviceTypeParam; deviceTypeParam.insert("devicetype", "nymea"); QJsonDocument jsonDoc = QJsonDocument::fromVariant(deviceTypeParam); QNetworkRequest request(QUrl("http://" + pairingInfo->host().toString() + "/api")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->post(request, jsonDoc.toJson()); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_pairingRequests.insert(reply, pairingInfo); return DeviceManager::DeviceSetupStatusAsync; } void DevicePluginPhilipsHue::networkManagerReplyReady() { QNetworkReply *reply = static_cast(sender()); reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // qCDebug(dcPhilipsHue()) << "Hue reply:" << status << reply->error() << reply->errorString(); // create user finished if (m_pairingRequests.contains(reply)) { PairingInfo *pairingInfo = m_pairingRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "Request error:" << status << reply->errorString(); pairingInfo->deleteLater(); return; } processPairingResponse(pairingInfo, reply->readAll()); } else if (m_informationRequests.contains(reply)) { PairingInfo *pairingInfo = m_informationRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "Request error:" << status << reply->errorString(); pairingInfo->deleteLater(); return; } processInformationResponse(pairingInfo, reply->readAll()); } else if (m_discoveryRequests.contains(reply)) { m_discoveryRequests.removeAll(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "N-UPNP discovery error:" << status << reply->errorString(); return; } processNUpnpResponse(reply->readAll()); } else if (m_bridgeLightsDiscoveryRequests.contains(reply)) { Device *device = m_bridgeLightsDiscoveryRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "Bridge light discovery error:" << status << reply->errorString(); bridgeReachableChanged(device, false); return; } processBridgeLightDiscoveryResponse(device, reply->readAll()); } else if (m_bridgeSensorsDiscoveryRequests.contains(reply)) { Device *device = m_bridgeSensorsDiscoveryRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "Bridge sensor discovery error:" << status << reply->errorString(); bridgeReachableChanged(device, false); return; } processBridgeSensorDiscoveryResponse(device, reply->readAll()); } else if (m_bridgeSearchDevicesRequests.contains(reply)) { Device *device = m_bridgeSearchDevicesRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "Bridge search new devices error:" << status << reply->errorString(); bridgeReachableChanged(device, false); return; } discoverBridgeDevices(m_bridges.key(device)); } else if (m_bridgeRefreshRequests.contains(reply)) { Device *device = m_bridgeRefreshRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { if (device->stateValue(bridgeConnectedStateTypeId).toBool()) { qCWarning(dcPhilipsHue) << "Refresh Hue Bridge request error:" << status << reply->errorString(); bridgeReachableChanged(device, false); } return; } processBridgeRefreshResponse(device, reply->readAll()); } else if (m_lightRefreshRequests.contains(reply)) { Device *device = m_lightRefreshRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "Refresh Hue Light request error:" << status << reply->errorString(); bridgeReachableChanged(device, false); return; } processLightRefreshResponse(device, reply->readAll()); } else if (m_lightsRefreshRequests.contains(reply)) { Device *device = m_lightsRefreshRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { if (device->stateValue(bridgeConnectedStateTypeId).toBool()) { qCWarning(dcPhilipsHue) << "Refresh Hue lights request error:" << status << reply->errorString(); bridgeReachableChanged(device, false); } return; } processLightsRefreshResponse(device, reply->readAll()); } else if (m_sensorsRefreshRequests.contains(reply)) { Device *device = m_sensorsRefreshRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { if (device->stateValue(bridgeConnectedStateTypeId).toBool()) { qCWarning(dcPhilipsHue) << "Refresh Hue sensors request error:" << status << reply->errorString(); bridgeReachableChanged(device, false); } return; } processSensorsRefreshResponse(device, reply->readAll()); } else if (m_asyncActions.contains(reply)) { QPair actionInfo = m_asyncActions.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "Execute Hue Light action request error:" << status << reply->errorString(); bridgeReachableChanged(actionInfo.first, false); emit actionExecutionFinished(actionInfo.second, DeviceManager::DeviceErrorHardwareNotAvailable); return; } processActionResponse(actionInfo.first, actionInfo.second, reply->readAll()); } else if (m_setNameRequests.contains(reply)) { Device *device = m_setNameRequests.take(reply); // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcPhilipsHue) << "Set name of Hue Light request error:" << status << reply->errorString(); bridgeReachableChanged(device, false); return; } processSetNameResponse(device, reply->readAll()); } else { qCWarning(dcPhilipsHue()) << "Unhandled bridge reply" << reply->error() << reply->readAll(); } } void DevicePluginPhilipsHue::onDeviceNameChanged() { Device *device = static_cast(sender()); if (m_lights.values().contains(device)) { setLightName(device); } if (m_remotes.values().contains(device)) { setRemoteName(device); } } DeviceManager::DeviceError DevicePluginPhilipsHue::executeAction(Device *device, const Action &action) { qCDebug(dcPhilipsHue) << "Execute action" << action.actionTypeId() << action.params(); // Color light if (device->deviceClassId() == colorLightDeviceClassId) { HueLight *light = m_lights.key(device); if (!light->reachable()) { qCWarning(dcPhilipsHue) << "Light" << light->name() << "not reachable"; return DeviceManager::DeviceErrorHardwareNotAvailable; } if (action.actionTypeId() == colorLightPowerActionTypeId) { QPair request = light->createSetPowerRequest(action.param(colorLightPowerActionPowerParamTypeId).value().toBool()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == colorLightColorActionTypeId) { QPair request = light->createSetColorRequest(action.param(colorLightColorActionColorParamTypeId).value().value()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply,QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == colorLightBrightnessActionTypeId) { QPair request = light->createSetBrightnessRequest(percentageToBrightness(action.param(colorLightBrightnessActionBrightnessParamTypeId).value().toInt())); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == colorLightEffectActionTypeId) { QPair request = light->createSetEffectRequest(action.param(colorLightEffectActionEffectParamTypeId).value().toString()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == colorLightAlertActionTypeId) { QPair request = light->createFlashRequest(action.param(colorLightAlertActionAlertParamTypeId).value().toString()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == colorLightColorTemperatureActionTypeId) { QPair request = light->createSetTemperatureRequest(action.param(colorLightColorTemperatureActionColorTemperatureParamTypeId).value().toInt()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } return DeviceManager::DeviceErrorActionTypeNotFound; } // Color temperature light if (device->deviceClassId() == colorTemperatureLightDeviceClassId) { HueLight *light = m_lights.key(device); if (!light->reachable()) { qCWarning(dcPhilipsHue) << "Light" << light->name() << "not reachable"; return DeviceManager::DeviceErrorHardwareNotAvailable; } if (action.actionTypeId() == colorTemperatureLightPowerActionTypeId) { QPair request = light->createSetPowerRequest(action.param(colorTemperatureLightPowerActionPowerParamTypeId).value().toBool()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == colorTemperatureLightBrightnessActionTypeId) { QPair request = light->createSetBrightnessRequest(percentageToBrightness(action.param(colorTemperatureLightBrightnessActionBrightnessParamTypeId).value().toInt())); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == colorTemperatureLightAlertActionTypeId) { QPair request = light->createFlashRequest(action.param(colorTemperatureLightAlertActionAlertParamTypeId).value().toString()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == colorTemperatureLightColorTemperatureActionTypeId) { QPair request = light->createSetTemperatureRequest(action.param(colorTemperatureLightColorTemperatureActionColorTemperatureParamTypeId).value().toInt()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } return DeviceManager::DeviceErrorActionTypeNotFound; } // Dimmable light if (device->deviceClassId() == dimmableLightDeviceClassId) { HueLight *light = m_lights.key(device); if (!light->reachable()) { qCWarning(dcPhilipsHue) << "Light" << light->name() << "not reachable"; return DeviceManager::DeviceErrorHardwareNotAvailable; } if (action.actionTypeId() == dimmableLightPowerActionTypeId) { QPair request = light->createSetPowerRequest(action.param(dimmableLightPowerActionPowerParamTypeId).value().toBool()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == dimmableLightBrightnessActionTypeId) { QPair request = light->createSetBrightnessRequest(percentageToBrightness(action.param(dimmableLightBrightnessActionBrightnessParamTypeId).value().toInt())); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == dimmableLightAlertActionTypeId) { QPair request = light->createFlashRequest(action.param(dimmableLightAlertActionAlertParamTypeId).value().toString()); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } return DeviceManager::DeviceErrorActionTypeNotFound; } // Hue bridge if (device->deviceClassId() == bridgeDeviceClassId) { HueBridge *bridge = m_bridges.key(device); if (!device->stateValue(bridgeConnectedStateTypeId).toBool()) { qCWarning(dcPhilipsHue) << "Bridge" << bridge->hostAddress().toString() << "not reachable"; return DeviceManager::DeviceErrorHardwareNotAvailable; } if (action.actionTypeId() == bridgeSearchNewDevicesActionTypeId) { searchNewDevices(bridge, action.param(bridgeSearchNewDevicesActionSerialParamTypeId).value().toString()); return DeviceManager::DeviceErrorNoError; } else if (action.actionTypeId() == bridgeCheckForUpdatesActionTypeId) { QPair request = bridge->createCheckUpdatesRequest(); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } else if (action.actionTypeId() == bridgeUpgradeActionTypeId) { QPair request = bridge->createUpgradeRequest(); QNetworkReply *reply = hardwareManager()->networkManager()->put(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_asyncActions.insert(reply, QPair(device, action.id())); return DeviceManager::DeviceErrorAsync; } return DeviceManager::DeviceErrorActionTypeNotFound; } return DeviceManager::DeviceErrorDeviceClassNotFound; } void DevicePluginPhilipsHue::lightStateChanged() { HueLight *light = static_cast(sender()); Device *device = m_lights.value(light); if (!device) { qCWarning(dcPhilipsHue) << "Could not find device for light" << light->name(); return; } if (device->deviceClassId() == colorLightDeviceClassId) { device->setStateValue(colorLightConnectedStateTypeId, light->reachable()); device->setStateValue(colorLightColorStateTypeId, QVariant::fromValue(light->color())); device->setStateValue(colorLightPowerStateTypeId, light->power()); device->setStateValue(colorLightBrightnessStateTypeId, brightnessToPercentage(light->brightness())); device->setStateValue(colorLightColorTemperatureStateTypeId, light->ct()); device->setStateValue(colorLightEffectStateTypeId, light->effect()); } else if (device->deviceClassId() == colorTemperatureLightDeviceClassId) { device->setStateValue(colorTemperatureLightConnectedStateTypeId, light->reachable()); device->setStateValue(colorTemperatureLightPowerStateTypeId, light->power()); device->setStateValue(colorTemperatureLightBrightnessStateTypeId, brightnessToPercentage(light->brightness())); device->setStateValue(colorTemperatureLightColorTemperatureStateTypeId, light->ct()); } else if (device->deviceClassId() == dimmableLightDeviceClassId) { device->setStateValue(dimmableLightConnectedStateTypeId, light->reachable()); device->setStateValue(dimmableLightPowerStateTypeId, light->power()); device->setStateValue(dimmableLightBrightnessStateTypeId, brightnessToPercentage(light->brightness())); } } void DevicePluginPhilipsHue::remoteStateChanged() { HueRemote *remote = static_cast(sender()); Device *device = m_remotes.value(remote); if (!device) { qCWarning(dcPhilipsHue) << "Could not find device for remote" << remote->name(); return; } if (device->deviceClassId() == tapDeviceClassId) { device->setStateValue(tapConnectedStateTypeId, remote->reachable()); } else { device->setStateValue(remoteConnectedStateTypeId, remote->reachable()); device->setStateValue(remoteBatteryLevelStateTypeId, remote->battery()); device->setStateValue(remoteBatteryCriticalStateTypeId, remote->battery() < 5); } } void DevicePluginPhilipsHue::onRemoteButtonEvent(int buttonCode) { HueRemote *remote = static_cast(sender()); EventTypeId id; Param param; // TODO: Legacy events should be removed eventually switch (buttonCode) { case HueRemote::OnPressed: param = Param(remotePressedEventButtonNameParamTypeId, "ON"); id = remotePressedEventTypeId; break; case HueRemote::OnLongPressed: emitEvent(Event(remoteOnLongPressedEventTypeId, m_remotes.value(remote)->id())); param = Param(remoteLongPressedEventButtonNameParamTypeId, "ON"); id = remoteLongPressedEventTypeId; break; case HueRemote::DimUpPressed: emitEvent(Event(remoteDimUpPressedEventTypeId, m_remotes.value(remote)->id())); param = Param(remotePressedEventButtonNameParamTypeId, "DIM UP"); id = remotePressedEventTypeId; break; case HueRemote::DimUpLongPressed: emitEvent(Event(remoteDimUpLongPressedEventTypeId, m_remotes.value(remote)->id())); param = Param(remoteLongPressedEventButtonNameParamTypeId, "DIM UP"); id = remoteLongPressedEventTypeId; break; case HueRemote::DimDownPressed: emitEvent(Event(remoteDimDownPressedEventTypeId, m_remotes.value(remote)->id())); param = Param(remotePressedEventButtonNameParamTypeId, "DIM DOWN"); id = remotePressedEventTypeId; break; case HueRemote::DimDownLongPressed: emitEvent(Event(remoteDimDownLongPressedEventTypeId, m_remotes.value(remote)->id())); param = Param(remoteLongPressedEventButtonNameParamTypeId, "DIM DOWN"); id = remoteLongPressedEventTypeId; break; case HueRemote::OffPressed: emitEvent(Event(remoteOffPressedEventTypeId, m_remotes.value(remote)->id())); param = Param(remotePressedEventButtonNameParamTypeId, "OFF"); id = remotePressedEventTypeId; break; case HueRemote::OffLongPressed: emitEvent(Event(remoteOffLongPressedEventTypeId, m_remotes.value(remote)->id())); param = Param(remoteLongPressedEventButtonNameParamTypeId, "OFF"); id = remoteLongPressedEventTypeId; break; case HueRemote::TapButton1Pressed: param = Param(tapPressedEventButtonNameParamTypeId, "•"); id = tapPressedEventTypeId; break; case HueRemote::TapButton2Pressed: param = Param(tapPressedEventButtonNameParamTypeId, "••"); id = tapPressedEventTypeId; break; case HueRemote::TapButton3Pressed: param = Param(tapPressedEventButtonNameParamTypeId, "•••"); id = tapPressedEventTypeId; break; case HueRemote::TapButton4Pressed: param = Param(tapPressedEventButtonNameParamTypeId, "••••"); id = tapPressedEventTypeId; break; default: break; } emitEvent(Event(id, m_remotes.value(remote)->id(), ParamList() << param)); } void DevicePluginPhilipsHue::onOutdoorSensorReachableChanged(bool reachable) { HueOutdoorSensor *sensor = static_cast(sender()); Device *sensorDevice = m_outdoorSensors.value(sensor); sensorDevice->setStateValue(outdoorSensorConnectedStateTypeId, reachable); } void DevicePluginPhilipsHue::onOutdoorSensorBatteryLevelChanged(int batteryLevel) { HueOutdoorSensor *sensor = static_cast(sender()); Device *sensorDevice = m_outdoorSensors.value(sensor); sensorDevice->setStateValue(outdoorSensorBatteryLevelStateTypeId, batteryLevel); sensorDevice->setStateValue(outdoorSensorBatteryCriticalStateTypeId, (batteryLevel < 5)); } void DevicePluginPhilipsHue::onOutdoorSensorTemperatureChanged(double temperature) { HueOutdoorSensor *sensor = static_cast(sender()); Device *sensorDevice = m_outdoorSensors.value(sensor); sensorDevice->setStateValue(outdoorSensorTemperatureStateTypeId, temperature); } void DevicePluginPhilipsHue::onOutdoorSensorPresenceChanged(bool presence) { HueOutdoorSensor *sensor = static_cast(sender()); Device *sensorDevice = m_outdoorSensors.value(sensor); sensorDevice->setStateValue(outdoorSensorIsPresentStateTypeId, presence); if (presence) sensorDevice->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentDateTime().toTime_t()); } void DevicePluginPhilipsHue::onOutdoorSensorLightIntensityChanged(double lightIntensity) { HueOutdoorSensor *sensor = static_cast(sender()); Device *sensorDevice = m_outdoorSensors.value(sensor); sensorDevice->setStateValue(outdoorSensorLightIntensityStateTypeId, lightIntensity); } void DevicePluginPhilipsHue::onUpnpDiscoveryFinished() { qCDebug(dcPhilipsHue()) << "Upnp discovery finished"; UpnpDiscoveryReply *reply = static_cast(sender()); if (reply->error() != UpnpDiscoveryReply::UpnpDiscoveryReplyErrorNoError) { qCWarning(dcPhilipsHue()) << "Upnp discovery error" << reply->error(); } reply->deleteLater(); if (reply->deviceDescriptors().isEmpty()) { qCDebug(dcPhilipsHue) << "No UPnP device found. Try N-UPNP discovery."; QNetworkRequest request(QUrl("https://www.meethue.com/api/nupnp")); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_discoveryRequests.append(reply); return; } QList deviceDescriptors; foreach (const UpnpDeviceDescriptor &upnpDevice, reply->deviceDescriptors()) { if (upnpDevice.modelDescription().contains("Philips")) { DeviceDescriptor descriptor(bridgeDeviceClassId, "Philips Hue Bridge", 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, upnpDevice.serialNumber().toLower())); // Not known yet... params.append(Param(bridgeDeviceApiKeyParamTypeId, QString())); params.append(Param(bridgeDeviceMacParamTypeId, QString())); params.append(Param(bridgeDeviceZigbeeChannelParamTypeId, -1)); descriptor.setParams(params); Device *dev = bridgeForBridgeId(upnpDevice.serialNumber().toLower()); if (dev) { qCDebug(dcPhilipsHue()) << "Found already added Hue bridge:" << upnpDevice.serialNumber().toLower(); descriptor.setDeviceId(dev->id()); } else { qCDebug(dcPhilipsHue()) << "Found new Hue bridge:" << upnpDevice.serialNumber().toLower(); } deviceDescriptors.append(descriptor); } } emit devicesDiscovered(bridgeDeviceClassId, deviceDescriptors); } void DevicePluginPhilipsHue::refreshLight(Device *device) { HueLight *light = m_lights.key(device); QNetworkRequest request(QUrl("http://" + light->hostAddress().toString() + "/api/" + light->apiKey() + "/lights/" + QString::number(light->id()))); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_lightRefreshRequests.insert(reply, device); } void DevicePluginPhilipsHue::refreshBridge(Device *device) { HueBridge *bridge = m_bridges.key(device); // qCDebug(dcPhilipsHue()) << "refreshing bridge"; QNetworkRequest request(QUrl("http://" + bridge->hostAddress().toString() + "/api/" + bridge->apiKey() + "/config")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_bridgeRefreshRequests.insert(reply, device); } void DevicePluginPhilipsHue::refreshLights(HueBridge *bridge) { Device *device = m_bridges.value(bridge); // qCDebug(dcPhilipsHue()) << "refreshing lights"; QNetworkRequest request(QUrl("http://" + bridge->hostAddress().toString() + "/api/" + bridge->apiKey() + "/lights")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_lightsRefreshRequests.insert(reply, device); } void DevicePluginPhilipsHue::refreshSensors(HueBridge *bridge) { Device *device = m_bridges.value(bridge); // qCDebug(dcPhilipsHue()) << "refreshing sensors"; QNetworkRequest request(QUrl("http://" + bridge->hostAddress().toString() + "/api/" + bridge->apiKey() + "/sensors")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_sensorsRefreshRequests.insert(reply, device); } void DevicePluginPhilipsHue::discoverBridgeDevices(HueBridge *bridge) { Device *device = m_bridges.value(bridge); qCDebug(dcPhilipsHue) << "Discover bridge devices" << bridge->hostAddress(); QPair lightsRequest = bridge->createDiscoverLightsRequest(); QNetworkReply *lightsReply = hardwareManager()->networkManager()->get(lightsRequest.first); connect(lightsReply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_bridgeLightsDiscoveryRequests.insert(lightsReply, device); QPair sensorsRequest = bridge->createSearchSensorsRequest(); QNetworkReply *reply = hardwareManager()->networkManager()->get(sensorsRequest.first); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_bridgeSensorsDiscoveryRequests.insert(reply, device); } void DevicePluginPhilipsHue::searchNewDevices(HueBridge *bridge, const QString &serialNumber) { Device *device = m_bridges.value(bridge); qCDebug(dcPhilipsHue) << "Discover bridge devices" << bridge->hostAddress(); QPair request = bridge->createSearchLightsRequest(serialNumber); QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_bridgeSearchDevicesRequests.insert(reply, device); } void DevicePluginPhilipsHue::setLightName(Device *device) { HueLight *light = m_lights.key(device); QVariantMap requestMap; requestMap.insert("name", device->name()); QJsonDocument jsonDoc = QJsonDocument::fromVariant(requestMap); QNetworkRequest request(QUrl("http://" + light->hostAddress().toString() + "/api/" + light->apiKey() + "/lights/" + QString::number(light->id()))); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->put(request,jsonDoc.toJson()); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_setNameRequests.insert(reply, device); } void DevicePluginPhilipsHue::setRemoteName(Device *device) { HueRemote *remote = m_remotes.key(device); QVariantMap requestMap; requestMap.insert("name", device->name()); QJsonDocument jsonDoc = QJsonDocument::fromVariant(requestMap); QNetworkRequest request(QUrl("http://" + remote->hostAddress().toString() + "/api/" + remote->apiKey() + "/sensors/" + QString::number(remote->id()))); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->put(request,jsonDoc.toJson()); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_setNameRequests.insert(reply, device); } void DevicePluginPhilipsHue::processNUpnpResponse(const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // Check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "N-UPNP discovery JSON error in response" << error.errorString(); return; } QVariantList bridgeList = jsonDoc.toVariant().toList(); QList deviceDescriptors; foreach (const QVariant &bridgeVariant, bridgeList) { QVariantMap bridgeMap = bridgeVariant.toMap(); DeviceDescriptor descriptor(bridgeDeviceClassId, "Philips Hue Bridge", bridgeMap.value("internalipaddress").toString()); ParamList params; params.append(Param(bridgeDeviceHostParamTypeId, bridgeMap.value("internalipaddress").toString())); params.append(Param(bridgeDeviceApiKeyParamTypeId, QString())); params.append(Param(bridgeDeviceMacParamTypeId, QString())); params.append(Param(bridgeDeviceIdParamTypeId, bridgeMap.value("internalipaddress").toString().toLower())); params.append(Param(bridgeDeviceZigbeeChannelParamTypeId, -1)); descriptor.setParams(params); deviceDescriptors.append(descriptor); } qCDebug(dcPhilipsHue) << "N-UPNP discover finished. Found" << deviceDescriptors.count() << "devices."; emit devicesDiscovered(bridgeDeviceClassId, deviceDescriptors); } void DevicePluginPhilipsHue::processBridgeLightDiscoveryResponse(Device *device, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // Check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Bridge light discovery json error in response" << error.errorString(); return; } // Check response error if (data.contains("error")) { if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to discover Hue Bridge lights:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); } else { qCWarning(dcPhilipsHue) << "Failed to discover Hue Bridge lights: Invalid error message format"; } return; } // Create Lights if not already added QList colorLightDescriptors; QList colorTemperatureLightDescriptors; QList dimmableLightDescriptors; QVariantMap lightsMap = jsonDoc.toVariant().toMap(); foreach (QString lightId, lightsMap.keys()) { QVariantMap lightMap = lightsMap.value(lightId).toMap(); QString uuid = lightMap.value("uniqueid").toString(); QString model = lightMap.value("modelid").toString(); if (lightAlreadyAdded(uuid)) continue; if (lightMap.value("type").toString() == "Dimmable light") { DeviceDescriptor descriptor(dimmableLightDeviceClassId, lightMap.value("name").toString(), "Philips Hue White Light", device->id()); ParamList params; params.append(Param(dimmableLightDeviceModelIdParamTypeId, model)); params.append(Param(dimmableLightDeviceTypeParamTypeId, lightMap.value("type").toString())); params.append(Param(dimmableLightDeviceUuidParamTypeId, uuid)); params.append(Param(dimmableLightDeviceLightIdParamTypeId, lightId)); descriptor.setParams(params); dimmableLightDescriptors.append(descriptor); qCDebug(dcPhilipsHue) << "Found new dimmable light" << lightMap.value("name").toString() << model; } else if (lightMap.value("type").toString() == "Color temperature light") { DeviceDescriptor descriptor(colorTemperatureLightDeviceClassId, lightMap.value("name").toString(), "Philips Hue Color Temperature Light", device->id()); ParamList params; params.append(Param(colorTemperatureLightDeviceModelIdParamTypeId, model)); params.append(Param(colorTemperatureLightDeviceTypeParamTypeId, lightMap.value("type").toString())); params.append(Param(colorTemperatureLightDeviceUuidParamTypeId, uuid)); params.append(Param(colorTemperatureLightDeviceLightIdParamTypeId, lightId)); descriptor.setParams(params); colorTemperatureLightDescriptors.append(descriptor); qCDebug(dcPhilipsHue) << "Found new color temperature light" << lightMap.value("name").toString() << model; } else { DeviceDescriptor descriptor(colorLightDeviceClassId, lightMap.value("name").toString(), "Philips Hue Color Light", device->id()); ParamList params; params.append(Param(colorLightDeviceModelIdParamTypeId, model)); params.append(Param(colorLightDeviceTypeParamTypeId, lightMap.value("type").toString())); params.append(Param(colorLightDeviceUuidParamTypeId, uuid)); params.append(Param(colorLightDeviceLightIdParamTypeId, lightId)); descriptor.setParams(params); colorLightDescriptors.append(descriptor); qCDebug(dcPhilipsHue) << "Found new color light" << lightMap.value("name").toString() << model; } } if (!colorLightDescriptors.isEmpty()) emit autoDevicesAppeared(colorLightDeviceClassId, colorLightDescriptors); if (!colorTemperatureLightDescriptors.isEmpty()) emit autoDevicesAppeared(colorTemperatureLightDeviceClassId, colorTemperatureLightDescriptors); if (!dimmableLightDescriptors.isEmpty()) emit autoDevicesAppeared(dimmableLightDeviceClassId, dimmableLightDescriptors); } void DevicePluginPhilipsHue::processBridgeSensorDiscoveryResponse(Device *device, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // Check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Bridge sensor discovery json error in response" << error.errorString(); return; } // Check response error if (data.contains("error")) { if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to discover Hue Bridge sensors:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); } else { qCWarning(dcPhilipsHue) << "Failed to discover Hue Bridge sensors: Invalid error message format"; } return; } // Create sensors if not already added QVariantMap sensorsMap = jsonDoc.toVariant().toMap(); QHash outdoorSensors; foreach (const QString &sensorId, sensorsMap.keys()) { QVariantMap sensorMap = sensorsMap.value(sensorId).toMap(); QString uuid = sensorMap.value("uniqueid").toString(); QString model = sensorMap.value("modelid").toString(); if (sensorAlreadyAdded(uuid)) continue; if (sensorMap.value("type").toString() == "ZLLSwitch") { DeviceDescriptor descriptor(remoteDeviceClassId, sensorMap.value("name").toString(), "Philips Hue Remote", device->id()); ParamList params; params.append(Param(remoteDeviceModelIdParamTypeId, model)); params.append(Param(remoteDeviceTypeParamTypeId, sensorMap.value("type").toString())); params.append(Param(remoteDeviceUuidParamTypeId, uuid)); params.append(Param(remoteDeviceSensorIdParamTypeId, sensorId)); descriptor.setParams(params); emit autoDevicesAppeared(remoteDeviceClassId, {descriptor}); qCDebug(dcPhilipsHue) << "Found new remote" << sensorMap.value("name").toString() << model; } else if (sensorMap.value("type").toString() == "ZGPSwitch") { DeviceDescriptor descriptor(tapDeviceClassId, sensorMap.value("name").toString(), "Philips Hue Tap", device->id()); ParamList params; params.append(Param(tapDeviceUuidParamTypeId, uuid)); params.append(Param(tapDeviceModelIdParamTypeId, model)); params.append(Param(tapDeviceSensorIdParamTypeId, sensorId)); descriptor.setParams(params); emit autoDevicesAppeared(tapDeviceClassId, {descriptor}); qCDebug(dcPhilipsHue()) << "Found hue tap:" << sensorMap << tapDeviceClassId; } else if (model == "SML002") { // Get the base uuid from this sensor QString baseUuid = HueDevice::getBaseUuid(uuid); // Temperature sensor if (sensorMap.value("type").toString() == "ZLLTemperature") { qCDebug(dcPhilipsHue()) << "Found temperature sensor from OurdoorSensor:" << baseUuid << sensorMap; // Check if we haven outdoor sensor for this temperature sensor if (outdoorSensors.keys().contains(baseUuid)) { HueOutdoorSensor *outdoorSensor = outdoorSensors.value(baseUuid); outdoorSensor->setTemperatureSensorUuid(uuid); outdoorSensor->setTemperatureSensorId(sensorId.toInt()); } else { // Create an outdoor sensor HueOutdoorSensor *outdoorSensor = new HueOutdoorSensor(this); outdoorSensor->setModelId(model); outdoorSensor->setUuid(baseUuid); outdoorSensor->setTemperatureSensorUuid(uuid); outdoorSensor->setTemperatureSensorId(sensorId.toInt()); outdoorSensors.insert(baseUuid, outdoorSensor); } } if (sensorMap.value("type").toString() == "ZLLPresence") { qCDebug(dcPhilipsHue()) << "Found presence sensor from OurdoorSensor:" << baseUuid << sensorMap; // Check if we haven outdoor sensor for this presence sensor if (outdoorSensors.keys().contains(baseUuid)) { HueOutdoorSensor *outdoorSensor = outdoorSensors.value(baseUuid); outdoorSensor->setPresenceSensorUuid(uuid); outdoorSensor->setPresenceSensorId(sensorId.toInt()); } else { // Create an outdoor sensor HueOutdoorSensor *outdoorSensor = new HueOutdoorSensor(this); outdoorSensor->setUuid(baseUuid); outdoorSensor->setPresenceSensorUuid(uuid); outdoorSensor->setPresenceSensorId(sensorId.toInt()); outdoorSensors.insert(baseUuid, outdoorSensor); } } if (sensorMap.value("type").toString() == "ZLLLightLevel") { qCDebug(dcPhilipsHue()) << "Found light sensor from OurdoorSensor:" << sensorMap; // Check if we haven outdoor sensor for this light sensor if (outdoorSensors.keys().contains(baseUuid)) { HueOutdoorSensor *outdoorSensor = outdoorSensors.value(baseUuid); outdoorSensor->setLightSensorUuid(uuid); outdoorSensor->setLightSensorId(sensorId.toInt()); } else { // Create an outdoor sensor HueOutdoorSensor *outdoorSensor = new HueOutdoorSensor(this); outdoorSensor->setUuid(baseUuid); outdoorSensor->setLightSensorUuid(uuid); outdoorSensor->setLightSensorId(sensorId.toInt()); outdoorSensors.insert(baseUuid, outdoorSensor); } } } else { qCDebug(dcPhilipsHue()) << "Found unknown sensor:" << model; } } // Create outdoor sensors if there are any new sensors found foreach (HueOutdoorSensor *outdoorSensor, outdoorSensors.values()) { QString baseUuid = outdoorSensors.key(outdoorSensor); if (outdoorSensor->isValid()) { DeviceDescriptor descriptor(outdoorSensorDeviceClassId, "Philips Hue Outdoor sensor", baseUuid, device->id()); ParamList params; params.append(Param(outdoorSensorDeviceUuidParamTypeId, outdoorSensor->uuid())); params.append(Param(outdoorSensorDeviceModelIdParamTypeId, outdoorSensor->modelId())); params.append(Param(outdoorSensorDeviceSensorUuidTemperatureParamTypeId, outdoorSensor->temperatureSensorUuid())); params.append(Param(outdoorSensorDeviceSensorIdTemperatureParamTypeId, outdoorSensor->temperatureSensorId())); params.append(Param(outdoorSensorDeviceSensorUuidPresenceParamTypeId, outdoorSensor->presenceSensorUuid())); params.append(Param(outdoorSensorDeviceSensorIdPresenceParamTypeId, outdoorSensor->presenceSensorId())); params.append(Param(outdoorSensorDeviceSensorUuidLightParamTypeId, outdoorSensor->lightSensorUuid())); params.append(Param(outdoorSensorDeviceSensorIdLightParamTypeId, outdoorSensor->lightSensorId())); descriptor.setParams(params); qCDebug(dcPhilipsHue()) << "Found new Outdoor sensor" << baseUuid << outdoorSensorDeviceClassId; emit autoDevicesAppeared(outdoorSensorDeviceClassId, {descriptor}); } // Clean up outdoorSensors.remove(baseUuid); outdoorSensor->deleteLater(); } } void DevicePluginPhilipsHue::processLightRefreshResponse(Device *device, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); return; } // check response error if (data.contains("error")) { if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to refresh Hue Light:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); } else { qCWarning(dcPhilipsHue) << "Failed to refresh Hue Light: Invalid error message format"; } return; } HueLight *light = m_lights.key(device); light->updateStates(jsonDoc.toVariant().toMap().value("state").toMap()); } void DevicePluginPhilipsHue::processBridgeRefreshResponse(Device *device, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); return; } if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to refresh Hue Bridge:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); bridgeReachableChanged(device, false); return; } //qCDebug(dcPhilipsHue()) << "Bridge refresh response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); QVariantMap configMap = jsonDoc.toVariant().toMap(); // mark bridge as reachable bridgeReachableChanged(device, true); device->setStateValue(bridgeApiVersionStateTypeId, configMap.value("apiversion").toString()); device->setStateValue(bridgeSoftwareVersionStateTypeId, configMap.value("swversion").toString()); int updateStatus = configMap.value("swupdate").toMap().value("updatestate").toInt(); switch (updateStatus) { case 0: device->setStateValue(bridgeUpdateStatusStateTypeId, "Up to date"); break; case 1: device->setStateValue(bridgeUpdateStatusStateTypeId, "Downloading updates"); break; case 2: device->setStateValue(bridgeUpdateStatusStateTypeId, "Updates ready to install"); break; case 3: device->setStateValue(bridgeUpdateStatusStateTypeId, "Installing updates"); break; default: break; } } void DevicePluginPhilipsHue::processLightsRefreshResponse(Device *device, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); return; } // check response error if (data.contains("error")) { if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to refresh Hue Lights:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); } else { qCWarning(dcPhilipsHue) << "Failed to refresh Hue Lights: Invalid error message format"; } return; } // Update light states QVariantMap lightsMap = jsonDoc.toVariant().toMap(); foreach (const QString &lightId, lightsMap.keys()) { QVariantMap lightMap = lightsMap.value(lightId).toMap(); // get the light of this bridge foreach (HueLight *light, m_lights.keys()) { if (light->id() == lightId.toInt() && m_lights.value(light)->parentId() == device->id()) { light->updateStates(lightMap.value("state").toMap()); } } } } void DevicePluginPhilipsHue::processSensorsRefreshResponse(Device *device, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); return; } // check response error if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to refresh Hue Sensors:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); return; } // Update sensor states QVariantMap sensorsMap = jsonDoc.toVariant().toMap(); foreach (const QString &sensorId, sensorsMap.keys()) { QVariantMap sensorMap = sensorsMap.value(sensorId).toMap(); // Remotes foreach (HueRemote *remote, m_remotes.keys()) { if (remote->id() == sensorId.toInt() && m_remotes.value(remote)->parentId() == device->id()) { remote->updateStates(sensorMap.value("state").toMap(), sensorMap.value("config").toMap()); } } // Outdoor sensors foreach (HueOutdoorSensor *outdoorSensor, m_outdoorSensors.keys()) { if (outdoorSensor->hasSensor(sensorId.toInt()) && m_outdoorSensors.value(outdoorSensor)->parentId() == device->id()) { outdoorSensor->updateStates(sensorMap); } } } } void DevicePluginPhilipsHue::processSetNameResponse(Device *device, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); return; } // check response error if (data.contains("error")) { if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to set name of Hue:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); } else { qCWarning(dcPhilipsHue) << "Failed to set name of Hue: Invalid error message format"; } emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); return; } if (device->deviceClassId() == colorLightDeviceClassId || device->deviceClassId() == dimmableLightDeviceClassId) refreshLight(device); } void DevicePluginPhilipsHue::processPairingResponse(PairingInfo *pairingInfo, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); emit pairingFinished(pairingInfo->pairingTransactionId(), DeviceManager::DeviceSetupStatusFailure); pairingInfo->deleteLater(); return; } // check response error if (data.contains("error")) { if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to pair Hue Bridge:" << jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); } else { qCWarning(dcPhilipsHue) << "Failed to pair Hue Bridge: Invalid error message format"; } emit pairingFinished(pairingInfo->pairingTransactionId(), DeviceManager::DeviceSetupStatusFailure); pairingInfo->deleteLater(); return; } pairingInfo->setApiKey(jsonDoc.toVariant().toList().first().toMap().value("success").toMap().value("username").toString()); qCDebug(dcPhilipsHue) << "Got api key from bridge:" << pairingInfo->apiKey(); if (pairingInfo->apiKey().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to pair Hue Bridge: did not get any key from the bridge"; emit pairingFinished(pairingInfo->pairingTransactionId(), DeviceManager::DeviceSetupStatusFailure); pairingInfo->deleteLater(); return; } // Paired successfully, check bridge information QNetworkRequest request(QUrl("http://" + pairingInfo->host().toString() + "/api/" + pairingInfo->apiKey() + "/config")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::networkManagerReplyReady); m_informationRequests.insert(reply, pairingInfo); } void DevicePluginPhilipsHue::processInformationResponse(PairingInfo *pairingInfo, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); emit pairingFinished(pairingInfo->pairingTransactionId(), DeviceManager::DeviceSetupStatusFailure); pairingInfo->deleteLater(); return; } QVariantMap response = jsonDoc.toVariant().toMap(); // check response error if (response.contains("error")) { qCWarning(dcPhilipsHue) << "Failed to get information from Hue Bridge:" << response.value("error").toMap().value("description").toString(); emit pairingFinished(pairingInfo->pairingTransactionId(), DeviceManager::DeviceSetupStatusFailure); pairingInfo->deleteLater(); return; } // create Bridge HueBridge *bridge = new HueBridge(this); bridge->setId(response.value("bridgeid").toString()); bridge->setApiKey(pairingInfo->apiKey()); bridge->setHostAddress(pairingInfo->host()); bridge->setApiVersion(response.value("apiversion").toString()); bridge->setSoftwareVersion(response.value("swversion").toString()); bridge->setMacAddress(response.value("mac").toString()); bridge->setName(response.value("name").toString()); bridge->setZigbeeChannel(response.value("zigbeechannel").toInt()); m_unconfiguredBridges.append(bridge); emit pairingFinished(pairingInfo->pairingTransactionId(), DeviceManager::DeviceSetupStatusSuccess); pairingInfo->deleteLater(); } void DevicePluginPhilipsHue::processActionResponse(Device *device, const ActionId actionId, const QByteArray &data) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); // check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Hue Bridge json error in response" << error.errorString(); emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); return; } // check response error if (data.contains("error")) { if (!jsonDoc.toVariant().toList().isEmpty()) { qCWarning(dcPhilipsHue) << "Failed to execute Hue action:" << jsonDoc.toJson(); //jsonDoc.toVariant().toList().first().toMap().value("error").toMap().value("description").toString(); } else { qCWarning(dcPhilipsHue) << "Failed to execute Hue action: Invalid error message format"; } emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); return; } if (device->deviceClassId() != bridgeDeviceClassId) m_lights.key(device)->processActionResponse(jsonDoc.toVariant().toList()); emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); } void DevicePluginPhilipsHue::bridgeReachableChanged(Device *device, const bool &reachable) { if (reachable) { device->setStateValue(bridgeConnectedStateTypeId, true); } else { // mark bridge and corresponding hue devices unreachable if (device->deviceClassId() == bridgeDeviceClassId) { device->setStateValue(bridgeConnectedStateTypeId, false); foreach (HueLight *light, m_lights.keys()) { if (m_lights.value(light)->parentId() == device->id()) { light->setReachable(false); if (m_lights.value(light)->deviceClassId() == colorLightDeviceClassId) { m_lights.value(light)->setStateValue(colorLightConnectedStateTypeId, false); } else if (m_lights.value(light)->deviceClassId() == colorTemperatureLightDeviceClassId) { m_lights.value(light)->setStateValue(colorTemperatureLightConnectedStateTypeId, false); } else if (m_lights.value(light)->deviceClassId() == dimmableLightDeviceClassId) { m_lights.value(light)->setStateValue(dimmableLightConnectedStateTypeId, false); } } } foreach (HueRemote *remote, m_remotes.keys()) { if (m_remotes.value(remote)->parentId() == device->id()) { remote->setReachable(false); if (m_remotes.value(remote)->deviceClassId() == remoteDeviceClassId) { m_remotes.value(remote)->setStateValue(remoteConnectedStateTypeId, false); } else if (m_remotes.value(remote)->deviceClassId() == tapDeviceClassId) { m_remotes.value(remote)->setStateValue(tapConnectedStateTypeId, false); } } } foreach (HueOutdoorSensor *outdoorSensor, m_outdoorSensors.keys()) { if (m_outdoorSensors.value(outdoorSensor)->parentId() == device->id()) { outdoorSensor->setReachable(false); if (m_outdoorSensors.value(outdoorSensor)->deviceClassId() == outdoorSensorDeviceClassId) { m_outdoorSensors.value(outdoorSensor)->setStateValue(outdoorSensorConnectedStateTypeId, false); } } } } } } Device* DevicePluginPhilipsHue::bridgeForBridgeId(const QString &id) { foreach (Device *device, myDevices()) { if (device->deviceClassId() == bridgeDeviceClassId) { qCDebug(dcPhilipsHue()) << "Have bridge" << device->name() << device->paramValue(bridgeDeviceIdParamTypeId).toString().toLower() << id; if (device->paramValue(bridgeDeviceIdParamTypeId).toString().toLower() == id) { return device; } } } return nullptr; } bool DevicePluginPhilipsHue::lightAlreadyAdded(const QString &uuid) { foreach (Device *device, myDevices()) { if (device->deviceClassId() == colorLightDeviceClassId) { if (device->paramValue(colorLightDeviceUuidParamTypeId).toString() == uuid) { return true; } } else if (device->deviceClassId() == dimmableLightDeviceClassId) { if (device->paramValue(dimmableLightDeviceUuidParamTypeId).toString() == uuid) { return true; } } if (device->deviceClassId() == colorTemperatureLightDeviceClassId) { if (device->paramValue(colorTemperatureLightDeviceUuidParamTypeId).toString() == uuid) { return true; } } } return false; } bool DevicePluginPhilipsHue::sensorAlreadyAdded(const QString &uuid) { foreach (Device *device, myDevices()) { // Hue remote if (device->deviceClassId() == remoteDeviceClassId) { if (device->paramValue(remoteDeviceUuidParamTypeId).toString() == uuid) { return true; } } // Hue tap if (device->deviceClassId() == tapDeviceClassId) { if (device->paramValue(tapDeviceUuidParamTypeId).toString() == uuid) { return true; } } // Outdoor sensor consits out of 3 sensors if (device->deviceClassId() == outdoorSensorDeviceClassId) { if (device->paramValue(outdoorSensorDeviceSensorUuidLightParamTypeId).toString() == uuid) { return true; } else if (device->paramValue(outdoorSensorDeviceSensorUuidPresenceParamTypeId).toString() == uuid) { return true; } else if (device->paramValue(outdoorSensorDeviceSensorUuidTemperatureParamTypeId).toString() == uuid) { return true; } } } return false; } int DevicePluginPhilipsHue::brightnessToPercentage(int brightness) { return qRound((100.0 * brightness) / 255.0); } int DevicePluginPhilipsHue::percentageToBrightness(int percentage) { return qRound((255.0 * percentage) / 100.0); } void DevicePluginPhilipsHue::abortRequests(QHash requestList, Device *device) { foreach (QNetworkReply* reply, requestList.keys()) { if (requestList.value(reply) == device) { reply->abort(); } } }