From c4c19c6558fd5f1205ea8ed63ea409f1c3671c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 18 Mar 2019 16:22:13 +0100 Subject: [PATCH] Add Hue Outdoor sensor --- philipshue/devicepluginphilipshue.cpp | 241 ++++++++++++++++++++++--- philipshue/devicepluginphilipshue.h | 11 +- philipshue/devicepluginphilipshue.json | 131 ++++++++++++++ philipshue/huedevice.cpp | 16 ++ philipshue/huedevice.h | 6 + philipshue/hueoutdoorsensor.cpp | 185 +++++++++++++++++++ philipshue/hueoutdoorsensor.h | 96 ++++++++++ philipshue/philipshue.pro | 6 +- 8 files changed, 661 insertions(+), 31 deletions(-) create mode 100644 philipshue/hueoutdoorsensor.cpp create mode 100644 philipshue/hueoutdoorsensor.h diff --git a/philipshue/devicepluginphilipshue.cpp b/philipshue/devicepluginphilipshue.cpp index 770b6326..602025b1 100644 --- a/philipshue/devicepluginphilipshue.cpp +++ b/philipshue/devicepluginphilipshue.cpp @@ -110,7 +110,9 @@ DeviceManager::DeviceError DevicePluginPhilipsHue::discoverDevices(const DeviceC 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) @@ -118,7 +120,7 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev 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 + // Set data which was not known during discovery device->setParamValue(bridgeDeviceApiKeyParamTypeId, b->apiKey()); device->setParamValue(bridgeDeviceZigbeeChannelParamTypeId, b->zigbeeChannel()); device->setParamValue(bridgeDeviceMacParamTypeId, b->macAddress()); @@ -129,7 +131,7 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev } } - // loaded bridge + // Loaded bridge qCDebug(dcPhilipsHue) << "Setup Hue Bridge" << device->params(); HueBridge *bridge = new HueBridge(this); @@ -144,7 +146,7 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev return DeviceManager::DeviceSetupStatusSuccess; } - // hue color light + // Hue color light if (device->deviceClassId() == colorLightDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue color light" << device->params(); @@ -165,7 +167,7 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev return DeviceManager::DeviceSetupStatusSuccess; } - // hue color temperature light + // Hue color temperature light if (device->deviceClassId() == colorTemperatureLightDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue color temperature light" << device->params(); @@ -186,7 +188,7 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev return DeviceManager::DeviceSetupStatusSuccess; } - // hue white light + // Hue white light if (device->deviceClassId() == dimmableLightDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue white light" << device->params(); @@ -207,7 +209,7 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev return DeviceManager::DeviceSetupStatusSuccess; } - // hue remote + // Hue remote if (device->deviceClassId() == remoteDeviceClassId) { qCDebug(dcPhilipsHue) << "Setup Hue remote" << device->params() << device->deviceClassId(); @@ -227,7 +229,7 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev return DeviceManager::DeviceSetupStatusSuccess; } - // hue tap + // Hue tap if (device->deviceClassId() == tapDeviceClassId) { HueRemote *hueTap = new HueRemote(this); hueTap->setName(device->name()); @@ -241,6 +243,31 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev 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; @@ -276,6 +303,12 @@ void DevicePluginPhilipsHue::deviceRemoved(Device *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) @@ -283,7 +316,6 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::confirmPairing(const Pa Q_UNUSED(secret) qCDebug(dcPhilipsHue()) << "Confirming pairing for transactionId" << pairingTransactionId; - if (deviceClassId != bridgeDeviceClassId) return DeviceManager::DeviceSetupStatusFailure; @@ -313,7 +345,7 @@ void DevicePluginPhilipsHue::networkManagerReplyReady() int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); -// qCDebug(dcPhilipsHue()) << "Hue reply:" << status << reply->error() << reply->errorString(); + // qCDebug(dcPhilipsHue()) << "Hue reply:" << status << reply->error() << reply->errorString(); // create user finished if (m_pairingRequests.contains(reply)) { @@ -463,6 +495,7 @@ void DevicePluginPhilipsHue::onDeviceNameChanged() if (m_lights.values().contains(device)) { setLightName(device); } + if (m_remotes.values().contains(device)) { setRemoteName(device); } @@ -472,7 +505,7 @@ DeviceManager::DeviceError DevicePluginPhilipsHue::executeAction(Device *device, { qCDebug(dcPhilipsHue) << "Execute action" << action.actionTypeId() << action.params(); - // color light + // Color light if (device->deviceClassId() == colorLightDeviceClassId) { HueLight *light = m_lights.key(device); @@ -521,7 +554,7 @@ DeviceManager::DeviceError DevicePluginPhilipsHue::executeAction(Device *device, return DeviceManager::DeviceErrorActionTypeNotFound; } - // color temperature light + // Color temperature light if (device->deviceClassId() == colorTemperatureLightDeviceClassId) { HueLight *light = m_lights.key(device); @@ -558,7 +591,7 @@ DeviceManager::DeviceError DevicePluginPhilipsHue::executeAction(Device *device, return DeviceManager::DeviceErrorActionTypeNotFound; } - // dimmable light + // Dimmable light if (device->deviceClassId() == dimmableLightDeviceClassId) { HueLight *light = m_lights.key(device); @@ -589,6 +622,7 @@ DeviceManager::DeviceError DevicePluginPhilipsHue::executeAction(Device *device, return DeviceManager::DeviceErrorActionTypeNotFound; } + // Hue bridge if (device->deviceClassId() == bridgeDeviceClassId) { HueBridge *bridge = m_bridges.key(device); if (!device->stateValue(bridgeConnectedStateTypeId).toBool()) { @@ -664,7 +698,7 @@ void DevicePluginPhilipsHue::remoteStateChanged() } } -void DevicePluginPhilipsHue::onRemoteButtonEvent(const int &buttonCode) +void DevicePluginPhilipsHue::onRemoteButtonEvent(int buttonCode) { HueRemote *remote = static_cast(sender()); @@ -734,6 +768,43 @@ void DevicePluginPhilipsHue::onRemoteButtonEvent(const int &buttonCode) 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::currentSecsSinceEpoch()); +} + +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"; @@ -800,7 +871,7 @@ void DevicePluginPhilipsHue::refreshLight(Device *device) void DevicePluginPhilipsHue::refreshBridge(Device *device) { HueBridge *bridge = m_bridges.key(device); -// qCDebug(dcPhilipsHue()) << "refreshing bridge"; + // qCDebug(dcPhilipsHue()) << "refreshing bridge"; QNetworkRequest request(QUrl("http://" + bridge->hostAddress().toString() + "/api/" + bridge->apiKey() + "/config")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -812,7 +883,7 @@ void DevicePluginPhilipsHue::refreshBridge(Device *device) void DevicePluginPhilipsHue::refreshLights(HueBridge *bridge) { Device *device = m_bridges.value(bridge); -// qCDebug(dcPhilipsHue()) << "refreshing lights"; + // qCDebug(dcPhilipsHue()) << "refreshing lights"; QNetworkRequest request(QUrl("http://" + bridge->hostAddress().toString() + "/api/" + bridge->apiKey() + "/lights")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -824,7 +895,7 @@ void DevicePluginPhilipsHue::refreshLights(HueBridge *bridge) void DevicePluginPhilipsHue::refreshSensors(HueBridge *bridge) { Device *device = m_bridges.value(bridge); -// qCDebug(dcPhilipsHue()) << "refreshing sensors"; + // qCDebug(dcPhilipsHue()) << "refreshing sensors"; QNetworkRequest request(QUrl("http://" + bridge->hostAddress().toString() + "/api/" + bridge->apiKey() + "/sensors")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -899,7 +970,7 @@ void DevicePluginPhilipsHue::processNUpnpResponse(const QByteArray &data) QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - // check JSON error + // Check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "N-UPNP discovery JSON error in response" << error.errorString(); return; @@ -929,13 +1000,13 @@ void DevicePluginPhilipsHue::processBridgeLightDiscoveryResponse(Device *device, QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - // check JSON error + // Check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Bridge light discovery json error in response" << error.errorString(); return; } - // check response error + // 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(); @@ -945,7 +1016,7 @@ void DevicePluginPhilipsHue::processBridgeLightDiscoveryResponse(Device *device, return; } - // create Lights if not already added + // Create Lights if not already added QList colorLightDescriptors; QList colorTemperatureLightDescriptors; QList dimmableLightDescriptors; @@ -1008,13 +1079,13 @@ void DevicePluginPhilipsHue::processBridgeSensorDiscoveryResponse(Device *device QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - // check JSON error + // Check JSON error if (error.error != QJsonParseError::NoError) { qCWarning(dcPhilipsHue) << "Bridge sensor discovery json error in response" << error.errorString(); return; } - // check response error + // 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(); @@ -1024,11 +1095,12 @@ void DevicePluginPhilipsHue::processBridgeSensorDiscoveryResponse(Device *device return; } - // create sensors if not already added + // Create sensors if not already added QVariantMap sensorsMap = jsonDoc.toVariant().toMap(); - foreach (QString sensorId, sensorsMap.keys()) { - QVariantMap sensorMap = sensorsMap.value(sensorId).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(); @@ -1054,10 +1126,91 @@ void DevicePluginPhilipsHue::processBridgeSensorDiscoveryResponse(Device *device 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) @@ -1102,6 +1255,8 @@ void DevicePluginPhilipsHue::processBridgeRefreshResponse(Device *device, const return; } + //qCDebug(dcPhilipsHue()) << "Bridge refresh response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); + QVariantMap configMap = jsonDoc.toVariant().toMap(); // mark bridge as reachable @@ -1183,11 +1338,20 @@ void DevicePluginPhilipsHue::processSensorsRefreshResponse(Device *device, const 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); + } + } } } @@ -1214,8 +1378,6 @@ void DevicePluginPhilipsHue::processSetNameResponse(Device *device, const QByteA return; } - //emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); - if (device->deviceClassId() == colorLightDeviceClassId || device->deviceClassId() == dimmableLightDeviceClassId) refreshLight(device); @@ -1366,9 +1528,17 @@ void DevicePluginPhilipsHue::bridgeReachableChanged(Device *device, const bool & } } } + + 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) @@ -1408,17 +1578,32 @@ bool DevicePluginPhilipsHue::lightAlreadyAdded(const QString &uuid) 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; } diff --git a/philipshue/devicepluginphilipshue.h b/philipshue/devicepluginphilipshue.h index ce76f6db..ba3f1600 100644 --- a/philipshue/devicepluginphilipshue.h +++ b/philipshue/devicepluginphilipshue.h @@ -30,6 +30,7 @@ #include "hueremote.h" #include "pairinginfo.h" #include "plugintimer.h" +#include "hueoutdoorsensor.h" #include "network/networkaccessmanager.h" #include "network/upnp/upnpdiscovery.h" @@ -58,7 +59,14 @@ public slots: private slots: void lightStateChanged(); void remoteStateChanged(); - void onRemoteButtonEvent(const int &buttonCode); + void onRemoteButtonEvent(int buttonCode); + + // Outdoor sensor + void onOutdoorSensorReachableChanged(bool reachable); + void onOutdoorSensorBatteryLevelChanged(int batteryLevel); + void onOutdoorSensorTemperatureChanged(double temperature); + void onOutdoorSensorPresenceChanged(bool presence); + void onOutdoorSensorLightIntensityChanged(double lightIntensity); private slots: void onUpnpDiscoveryFinished(); @@ -91,6 +99,7 @@ private: QHash m_bridges; QHash m_lights; QHash m_remotes; + QHash m_outdoorSensors; void refreshLight(Device *device); void refreshBridge(Device *device); diff --git a/philipshue/devicepluginphilipshue.json b/philipshue/devicepluginphilipshue.json index 972c593b..3e6ae85b 100644 --- a/philipshue/devicepluginphilipshue.json +++ b/philipshue/devicepluginphilipshue.json @@ -702,6 +702,137 @@ ] } ] + }, + { + "id": "32dc6390-600f-4eb4-b349-cc2d6796a82a", + "name": "outdoorSensor", + "displayName": "Hue Outdoor Sensor", + "deviceIcon": "MotionDetectors", + "interfaces": ["presencesensor", "temperaturesensor", "lightsensor", "batterylevel", "connectable"], + "basicTags": [ "Device", "Sensor" ], + "createMethods": ["auto"], + "paramTypes": [ + { + "id": "3ca8632d-7bd1-45a9-86af-c856e006c334", + "name": "modelId", + "displayName": "Model id", + "type" : "QString", + "readOnly": true + }, + { + "id": "4a15f861-cad6-464a-b250-08793c68ae30", + "name": "uuid", + "displayName": "Uuid", + "type" : "QString", + "readOnly": true + }, + { + "id": "c732fefd-ca6b-4e27-a6d2-11595c4aab3e", + "name": "sensorIdTemperature", + "displayName": "Temperature sensor id", + "type" : "int", + "readOnly": true + }, + { + "id": "2fdb34e8-25ca-4c5d-85c9-9a12bd48dbed", + "name": "sensorUuidTemperature", + "displayName": "Temperature sensor uuid", + "type" : "QString", + "readOnly": true + }, + { + "id": "3ca82a24-5eca-4285-83c2-f862d387c3bc", + "name": "sensorIdPresence", + "displayName": "Presence sensor id", + "type" : "int", + "readOnly": true + }, + { + "id": "7d55ed97-3a32-41e9-9112-8cc5b44aab23", + "name": "sensorUuidPresence", + "displayName": "Presence sensor uuid", + "type" : "QString", + "readOnly": true + }, + { + "id": "22a164fc-fa6e-427a-9a60-7a1872901fd6", + "name": "sensorIdLight", + "displayName": "Light sensor id", + "type" : "int", + "readOnly": true + }, + { + "id": "db678144-de2b-4767-a2f6-9ada8377b96c", + "name": "sensorUuidLight", + "displayName": "Light sensor uuid", + "type" : "QString", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "9fe43e6b-3c29-43a9-bb96-3b80eacc10db", + "name": "connected", + "displayName": "Reachable", + "displayNameEvent": "Reachable changed", + "defaultValue": false, + "type": "bool" + }, + { + "id": "19b18531-61e5-4998-89d1-765d740e24eb", + "name": "batteryLevel", + "displayName": "battery", + "displayNameEvent": "Battery changed", + "type": "int", + "unit": "Percentage", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100 + }, + { + "id": "617aa352-789c-46e7-bf55-7455b1e5018e", + "name": "batteryCritical", + "displayName": "battery critical", + "displayNameEvent": "Battery critical changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "88f5b708-65bb-41a7-885f-01be46074713", + "name": "temperature", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "unit": "DegreeCelsius", + "type": "double", + "defaultValue": 0 + }, + { + "id": "4fb12c06-981c-4c42-b55c-46bdfe68681a", + "name": "lightIntensity", + "displayName": "Ambient light", + "displayNameEvent": "Ambient light changed", + "unit": "Lux", + "type": "double", + "defaultValue": 0 + }, + { + "id": "680f79cf-c17c-4ffd-96fa-a5b286e2c117", + "name": "isPresent", + "displayName": "Is present", + "displayNameEvent": "Is present changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "6fa16fb2-053c-4c3c-a39b-9548c1b15089", + "name": "lastSeenTime", + "displayName": "Last seen time", + "displayNameEvent": "Last seen time changed", + "type": "int", + "unit": "UnixTime", + "defaultValue": 0 + } + ] } ] } diff --git a/philipshue/huedevice.cpp b/philipshue/huedevice.cpp index a0503579..24e3aab7 100644 --- a/philipshue/huedevice.cpp +++ b/philipshue/huedevice.cpp @@ -115,6 +115,22 @@ bool HueDevice::reachable() const void HueDevice::setReachable(const bool &reachable) { + if (m_reachable == reachable) + return; + m_reachable = reachable; + emit reachableChanged(m_reachable); +} + +QString HueDevice::getBaseUuid(const QString &uuid) +{ + // Example: the hue gives uuid's starting with a mac address, followd by an id + // "00:17:88:01:06:44:36:86-02-0406" -> "00:17:88:01:06:44:36:86" + int dashIndex = uuid.indexOf("-"); + if (dashIndex < 0) { + return uuid; + } + + return uuid.left(dashIndex); } diff --git a/philipshue/huedevice.h b/philipshue/huedevice.h index 94811667..6aa24533 100644 --- a/philipshue/huedevice.h +++ b/philipshue/huedevice.h @@ -62,6 +62,8 @@ public: bool reachable() const; void setReachable(const bool &reachable); + static QString getBaseUuid(const QString &uuid); + private: int m_id; QString m_name; @@ -73,6 +75,10 @@ private: QString m_softwareVersion; bool m_reachable; + +signals: + void reachableChanged(bool reachable); + }; #endif // HUEDEVICE_H diff --git a/philipshue/hueoutdoorsensor.cpp b/philipshue/hueoutdoorsensor.cpp new file mode 100644 index 00000000..05c3879a --- /dev/null +++ b/philipshue/hueoutdoorsensor.cpp @@ -0,0 +1,185 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * 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 "hueoutdoorsensor.h" +#include "extern-plugininfo.h" + +HueOutdoorSensor::HueOutdoorSensor(QObject *parent) : + HueDevice(parent) +{ + +} + +QString HueOutdoorSensor::uuid() const +{ + return m_uuid; +} + +void HueOutdoorSensor::setUuid(const QString &uuid) +{ + m_uuid = uuid; +} + +int HueOutdoorSensor::temperatureSensorId() const +{ + return m_temperatureSensorId; +} + +void HueOutdoorSensor::setTemperatureSensorId(int sensorId) +{ + m_temperatureSensorId = sensorId; +} + +QString HueOutdoorSensor::temperatureSensorUuid() const +{ + return m_temperatureSensorUuid; +} + +void HueOutdoorSensor::setTemperatureSensorUuid(const QString &temperatureSensorUuid) +{ + m_temperatureSensorUuid = temperatureSensorUuid; +} + +int HueOutdoorSensor::presenceSensorId() const +{ + return m_presenceSensorId; +} + +void HueOutdoorSensor::setPresenceSensorId(int sensorId) +{ + m_presenceSensorId = sensorId; +} + +QString HueOutdoorSensor::presenceSensorUuid() const +{ + return m_presenceSensorUuid; +} + +void HueOutdoorSensor::setPresenceSensorUuid(const QString &presenceSensorUuid) +{ + m_presenceSensorUuid = presenceSensorUuid; +} + +int HueOutdoorSensor::lightSensorId() const +{ + return m_lightSensorId; +} + +void HueOutdoorSensor::setLightSensorId(int sensorId) +{ + m_lightSensorId = sensorId; +} + +QString HueOutdoorSensor::lightSensorUuid() const +{ + return m_lightSensorUuid; +} + +void HueOutdoorSensor::setLightSensorUuid(const QString &lightSensorUuid) +{ + m_lightSensorUuid = lightSensorUuid; +} + +double HueOutdoorSensor::temperature() const +{ + return m_temperature; +} + +double HueOutdoorSensor::lightIntensity() const +{ + return m_lightIntensity; +} + +bool HueOutdoorSensor::present() const +{ + return m_presence; +} + +int HueOutdoorSensor::batteryLevel() const +{ + return m_batteryLevel; +} + +void HueOutdoorSensor::updateStates(const QVariantMap &sensorMap) +{ + //qCDebug(dcPhilipsHue()) << "Outdoor sensor: Process sensor map" << qUtf8Printable(QJsonDocument::fromVariant(sensorMap).toJson(QJsonDocument::Indented)); + + // Config + QVariantMap configMap = sensorMap.value("config").toMap(); + if (configMap.contains("reachable")) { + setReachable(configMap.value("reachable", false).toBool()); + } + + if (configMap.contains("battery")) { + int batteryLevel = configMap.value("battery", 0).toInt(); + if (m_batteryLevel != batteryLevel) { + m_batteryLevel = batteryLevel; + emit batteryLevelChanged(m_batteryLevel); + } + } + + // If temperature sensor + QVariantMap stateMap = sensorMap.value("state").toMap(); + if (sensorMap.value("uniqueid").toString() == m_temperatureSensorUuid) { + double temperature = stateMap.value("temperature", 0).toInt() / 100.0; + if (m_temperature != temperature) { + m_temperature = temperature; + emit temperatureChanged(m_temperature); + qCDebug(dcPhilipsHue) << "Outdoor sensor temperature changed" << m_temperature; + } + } + + // If presence sensor + if (sensorMap.value("uniqueid").toString() == m_presenceSensorUuid) { + bool presence = stateMap.value("presence", false).toBool(); + if (m_presence != presence) { + m_presence = presence; + emit presenceChanged(m_presence); + qCDebug(dcPhilipsHue) << "Outdoor sensor presence changed" << presence; + } + } + + // If light sensor + if (sensorMap.value("uniqueid").toString() == m_lightSensorUuid) { + int lightIntensity = stateMap.value("lightlevel", 0).toInt(); + if (m_lightIntensity != lightIntensity) { + m_lightIntensity = lightIntensity; + emit lightIntensityChanged(m_lightIntensity); + qCDebug(dcPhilipsHue) << "Outdoor sensor light intensity changed" << m_lightIntensity; + } + } +} + +bool HueOutdoorSensor::isValid() +{ + return !m_temperatureSensorUuid.isEmpty() && !m_presenceSensorUuid.isEmpty() && !m_lightSensorUuid.isEmpty(); +} + +bool HueOutdoorSensor::hasSensor(int sensorId) +{ + return m_temperatureSensorId == sensorId || m_presenceSensorId == sensorId || m_lightSensorId == sensorId; +} + +bool HueOutdoorSensor::hasSensor(const QString &sensorUuid) +{ + return m_temperatureSensorUuid == sensorUuid || m_presenceSensorUuid == sensorUuid || m_lightSensorUuid == sensorUuid; +} diff --git a/philipshue/hueoutdoorsensor.h b/philipshue/hueoutdoorsensor.h new file mode 100644 index 00000000..d5e0b919 --- /dev/null +++ b/philipshue/hueoutdoorsensor.h @@ -0,0 +1,96 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HUEOUTDOORSENSOR_H +#define HUEOUTDOORSENSOR_H + +#include + +#include "huedevice.h" + +class HueOutdoorSensor : public HueDevice +{ + Q_OBJECT +public: + explicit HueOutdoorSensor(QObject *parent = nullptr); + + QString uuid() const; + void setUuid(const QString &uuid); + + int temperatureSensorId() const; + void setTemperatureSensorId(int sensorId); + + QString temperatureSensorUuid() const; + void setTemperatureSensorUuid(const QString &temperatureSensorUuid); + + int presenceSensorId() const; + void setPresenceSensorId(int sensorId); + + QString presenceSensorUuid() const; + void setPresenceSensorUuid(const QString &presenceSensorUuid); + + int lightSensorId() const; + void setLightSensorId(int sensorId); + + QString lightSensorUuid() const; + void setLightSensorUuid(const QString &lightSensorUuid); + + double temperature() const; + double lightIntensity() const; + bool present() const; + int batteryLevel() const; + + void updateStates(const QVariantMap &sensorMap); + + bool isValid(); + bool hasSensor(int sensorId); + bool hasSensor(const QString &sensorUuid); + +private: + // Params + QString m_uuid; + + int m_temperatureSensorId; + QString m_temperatureSensorUuid; + + int m_presenceSensorId; + QString m_presenceSensorUuid; + + int m_lightSensorId; + QString m_lightSensorUuid; + + // States + QString m_lastUpdate; + double m_temperature = 0; + double m_lightIntensity = 0; + bool m_presence = false; + int m_batteryLevel = 0; + +signals: + void temperatureChanged(double temperature); + void lightIntensityChanged(double lightIntensity); + void presenceChanged(bool presence); + void batteryLevelChanged(int batteryLevel); + +}; + +#endif // HUEOUTDOORSENSOR_H diff --git a/philipshue/philipshue.pro b/philipshue/philipshue.pro index aa095613..63e5a1cc 100644 --- a/philipshue/philipshue.pro +++ b/philipshue/philipshue.pro @@ -12,7 +12,8 @@ SOURCES += \ huelight.cpp \ pairinginfo.cpp \ hueremote.cpp \ - huedevice.cpp + huedevice.cpp \ + hueoutdoorsensor.cpp HEADERS += \ devicepluginphilipshue.h \ @@ -23,7 +24,8 @@ HEADERS += \ huelight.h \ pairinginfo.h \ hueremote.h \ - huedevice.h + huedevice.h \ + hueoutdoorsensor.h