From 9d175bcfb9a09740ae659b14f1a9fb7477f2b648 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 12 Mar 2018 23:55:56 +0100 Subject: [PATCH] add support for the Hue Tap button --- philipshue/devicepluginphilipshue.cpp | 98 +++++++++++++++++++++----- philipshue/devicepluginphilipshue.json | 98 +++++++++++++++++++++++++- philipshue/hueremote.cpp | 8 ++- philipshue/hueremote.h | 6 +- 4 files changed, 188 insertions(+), 22 deletions(-) diff --git a/philipshue/devicepluginphilipshue.cpp b/philipshue/devicepluginphilipshue.cpp index cc9e3c22..da71589a 100644 --- a/philipshue/devicepluginphilipshue.cpp +++ b/philipshue/devicepluginphilipshue.cpp @@ -2,6 +2,7 @@ * * * Copyright (C) 2014 Michael Zanetti * * Copyright (C) 2015 Simon Stürz * + * Copyright (C) 2018 Michael Zanetti * * * * This file is part of nymea. * * * @@ -114,8 +115,9 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev bridge->setName(device->paramValue(hueBridgeBridgeNameParamTypeId).toString()); bridge->setMacAddress(device->paramValue(hueBridgeBridgeMacParamTypeId).toString()); bridge->setZigbeeChannel(device->paramValue(hueBridgeBridgeZigbeeChannelParamTypeId).toInt()); - m_bridges.insert(bridge, device); + + discoverBridgeDevices(bridge); return DeviceManager::DeviceSetupStatusSuccess; } @@ -173,7 +175,7 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev // hue remote if (device->deviceClassId() == hueRemoteDeviceClassId) { - qCDebug(dcPhilipsHue) << "Setup Hue remote" << device->params(); + qCDebug(dcPhilipsHue) << "Setup Hue remote" << device->params() << device->deviceClassId(); HueRemote *hueRemote = new HueRemote(this); hueRemote->setId(device->paramValue(hueRemoteSensorIdParamTypeId).toInt()); @@ -195,6 +197,24 @@ DeviceManager::DeviceSetupStatus DevicePluginPhilipsHue::setupDevice(Device *dev return DeviceManager::DeviceSetupStatusSuccess; } + // hue tap + if (device->deviceClassId() == hueTapDeviceClassId) { + HueRemote *hueTap = new HueRemote(this); + hueTap->setName(device->name()); + hueTap->setId(device->paramValue(hueTapSensorIdParamTypeId).toInt()); + hueTap->setBridgeId(DeviceId(device->paramValue(hueTapBridgeParamTypeId).toString())); + hueTap->setModelId(device->paramValue(hueTapModelIdParamTypeId).toString()); + device->setParentId(hueTap->bridgeId()); + + connect(hueTap, &HueRemote::stateChanged, this, &DevicePluginPhilipsHue::remoteStateChanged); + connect(hueTap, &HueRemote::buttonPressed, this, &DevicePluginPhilipsHue::onRemoteButtonEvent); + + m_remotes.insert(hueTap, device); + return DeviceManager::DeviceSetupStatusSuccess; + } + + qCWarning(dcPhilipsHue()) << "Unhandled setupDevice call" << device->deviceClassId(); + return DeviceManager::DeviceSetupStatusFailure; } @@ -212,7 +232,7 @@ void DevicePluginPhilipsHue::deviceRemoved(Device *device) light->deleteLater(); } - if (device->deviceClassId() == hueRemoteDeviceClassId) { + if (device->deviceClassId() == hueRemoteDeviceClassId || device->deviceClassId() == hueTapDeviceClassId) { HueRemote *remote = m_remotes.key(device); m_remotes.remove(remote); remote->deleteLater(); @@ -366,7 +386,7 @@ void DevicePluginPhilipsHue::networkManagerReplyReady() // check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { - if (device->stateValue(hueRemoteConnectedStateTypeId).toBool()) { + if (device->stateValue(hueRemoteConnectedStateTypeId).toBool() || device->stateValue(hueTapConnectedStateTypeId).toBool()) { qCWarning(dcPhilipsHue) << "Refresh Hue sensors request error:" << status << reply->errorString(); bridgeReachableChanged(device, false); } @@ -399,6 +419,8 @@ void DevicePluginPhilipsHue::networkManagerReplyReady() return; } processSetNameResponse(device, reply->readAll()); + } else { + qCWarning(dcPhilipsHue()) << "Unhandled bridge reply" << reply->readAll(); } reply->deleteLater(); } @@ -548,18 +570,27 @@ void DevicePluginPhilipsHue::remoteStateChanged() qCWarning(dcPhilipsHue) << "Could not find device for remote" << remote->name(); return; } - - device->setStateValue(hueRemoteConnectedStateTypeId, remote->reachable()); - device->setStateValue(hueRemoteBatteryLevelStateTypeId, remote->battery()); - device->setStateValue(hueRemoteBatteryCriticalStateTypeId, remote->battery() < 5); + if (device->deviceClassId() == hueTapDeviceClassId) { + device->setStateValue(hueTapConnectedStateTypeId, remote->reachable()); + } else { + device->setStateValue(hueRemoteConnectedStateTypeId, remote->reachable()); + device->setStateValue(hueRemoteBatteryLevelStateTypeId, remote->battery()); + device->setStateValue(hueRemoteBatteryCriticalStateTypeId, remote->battery() < 5); + } } void DevicePluginPhilipsHue::onRemoteButtonEvent(const int &buttonCode) { HueRemote *remote = static_cast(sender()); + Device *device = m_remotes.value(remote); - Param param(hueRemoteButtonNameParamTypeId); EventTypeId id; + Param param; + if (device->deviceClassId() == hueRemoteDeviceClassId) { + param = Param(hueRemoteButtonNameParamTypeId); + } else if (device->deviceClassId() == hueTapDeviceClassId) { + param = Param(hueTapButtonNameParamTypeId); + } // TODO: Legacy events should be removed eventually switch (buttonCode) { @@ -603,6 +634,22 @@ void DevicePluginPhilipsHue::onRemoteButtonEvent(const int &buttonCode) param.setValue("OFF"); id = hueRemoteLongPressedEventTypeId; break; + case HueRemote::TapButton1Pressed: + param.setValue("•"); + id = hueTapPressedEventTypeId; + break; + case HueRemote::TapButton2Pressed: + param.setValue("••"); + id = hueTapPressedEventTypeId; + break; + case HueRemote::TapButton3Pressed: + param.setValue("•••"); + id = hueTapPressedEventTypeId; + break; + case HueRemote::TapButton4Pressed: + param.setValue("•••••"); + id = hueTapPressedEventTypeId; + break; default: break; } @@ -879,7 +926,6 @@ void DevicePluginPhilipsHue::processBridgeSensorDiscoveryResponse(Device *device } // create sensors if not already added - QList sensorDescriptors; QVariantMap sensorsMap = jsonDoc.toVariant().toMap(); foreach (QString sensorId, sensorsMap.keys()) { QVariantMap sensorMap = sensorsMap.value(sensorId).toMap(); @@ -890,7 +936,6 @@ void DevicePluginPhilipsHue::processBridgeSensorDiscoveryResponse(Device *device if (sensorAlreadyAdded(uuid)) continue; - // check if this is a white light if (model == "RWL021" || model == "RWL020") { DeviceDescriptor descriptor(hueRemoteDeviceClassId, "Philips Hue Remote", sensorMap.value("name").toString()); ParamList params; @@ -903,14 +948,22 @@ void DevicePluginPhilipsHue::processBridgeSensorDiscoveryResponse(Device *device params.append(Param(hueRemoteUuidParamTypeId, uuid)); params.append(Param(hueRemoteSensorIdParamTypeId, sensorId)); descriptor.setParams(params); - sensorDescriptors.append(descriptor); + emit autoDevicesAppeared(hueRemoteDeviceClassId, {descriptor}); qCDebug(dcPhilipsHue) << "Found new remote" << sensorMap.value("name").toString() << model; + } else if (model == "ZGPSWITCH") { + DeviceDescriptor descriptor(hueTapDeviceClassId, "Hue Tap", sensorMap.value("name").toString()); + ParamList params; + params.append(Param(hueTapBridgeParamTypeId, device->id().toString())); + params.append(Param(hueTapUuidParamTypeId, uuid)); + params.append(Param(hueTapModelIdParamTypeId, model)); + params.append(Param(hueTapSensorIdParamTypeId, sensorId)); + descriptor.setParams(params); + emit autoDevicesAppeared(hueTapDeviceClassId, {descriptor}); + qCDebug(dcPhilipsHue()) << "Found hue tap:" << sensorMap << hueTapDeviceClassId; + } else { + qCDebug(dcPhilipsHue()) << "Found unknown sensor:" << model; } } - - if (!sensorDescriptors.isEmpty()) - emit autoDevicesAppeared(hueRemoteDeviceClassId, sensorDescriptors); - } void DevicePluginPhilipsHue::processLightRefreshResponse(Device *device, const QByteArray &data) @@ -1045,7 +1098,7 @@ void DevicePluginPhilipsHue::processSensorsRefreshResponse(Device *device, const QVariantMap sensorMap = sensorsMap.value(sensorId).toMap(); foreach (HueRemote *remote, m_remotes.keys()) { if (remote->id() == sensorId.toInt() && remote->bridgeId() == device->id()) { - //qCDebug(dcPhilipsHue) << "update remote" << remote->id() << remote->name(); + qCDebug(dcPhilipsHue) << "update remote" << remote->id() << remote->name(); remote->updateStates(sensorMap.value("state").toMap(), sensorMap.value("config").toMap()); } } @@ -1225,7 +1278,11 @@ void DevicePluginPhilipsHue::bridgeReachableChanged(Device *device, const bool & foreach (HueRemote *remote, m_remotes.keys()) { if (remote->bridgeId() == device->id()) { remote->setReachable(false); - m_remotes.value(remote)->setStateValue(hueRemoteConnectedStateTypeId, false); + if (m_remotes.value(remote)->deviceClassId() == hueRemoteDeviceClassId) { + m_remotes.value(remote)->setStateValue(hueRemoteConnectedStateTypeId, false); + } else if (m_remotes.value(remote)->deviceClassId() == hueTapDeviceClassId) { + m_remotes.value(remote)->setStateValue(hueTapConnectedStateTypeId, false); + } } } } @@ -1269,6 +1326,11 @@ bool DevicePluginPhilipsHue::sensorAlreadyAdded(const QString &uuid) return true; } } + if (device->deviceClassId() == hueTapDeviceClassId) { + if (device->paramValue(hueTapUuidParamTypeId).toString() == uuid) { + return true; + } + } } return false; } diff --git a/philipshue/devicepluginphilipshue.json b/philipshue/devicepluginphilipshue.json index ec0f34ee..81b3bbfd 100644 --- a/philipshue/devicepluginphilipshue.json +++ b/philipshue/devicepluginphilipshue.json @@ -596,7 +596,7 @@ "name": "buttonName", "displayName": "Button name", "type": "QString", - "possibleValues": ["ON", "OFF", "DIM UP", "DIM DOWN"] + "allowedValues": ["ON", "OFF", "DIM UP", "DIM DOWN"] } ] }, @@ -610,7 +610,101 @@ "name": "buttonName", "displayName": "Button name", "type": "QString", - "possibleValues": ["ON", "OFF", "DIM UP", "DIM DOWN"] + "allowedValues": ["ON", "OFF", "DIM UP", "DIM DOWN"] + } + ] + } + ] + }, + { + "id": "2b8c1fb8-67ee-42e9-947b-16e0a09f0d4e", + "name": "hueTap", + "displayName": "Hue Tap", + "deviceIcon": "Switch", + "interfaces": ["simplemultibutton", "connectable"], + "createMethods": ["auto"], + "paramTypes": [ + { + "id": "3d450f00-b521-4a2c-985f-046fad5122cb", + "name": "name", + "displayName": "name", + "type" : "QString", + "inputType": "TextLine" + }, + { + "id": "c9e7483b-2f4e-4280-a437-8471f4dc9b22", + "name": "bridge", + "displayName": "bridge", + "type" : "QString", + "readOnly": true + }, + { + "id": "62d92175-db3a-4da2-a72b-f58f34cb6911", + "name": "modelId", + "displayName": "model id", + "type" : "QString", + "readOnly": true + }, + { + "id": "eace85b9-5369-466f-89eb-46c4de718305", + "name": "type", + "displayName": "type", + "type" : "QString", + "readOnly": true + }, + { + "id": "25cf4167-6c28-4497-9fa9-3d02faf4f3ed", + "name": "uuid", + "displayName": "uuid", + "type" : "QString", + "readOnly": true + }, + { + "id": "fb507641-9c78-4a28-937d-711775454e90", + "name": "apiKey", + "displayName": "api key", + "type" : "QString", + "inputType": "TextLine", + "readOnly": true + }, + { + "id": "6be56a1b-f64a-4188-a052-1fab11e01718", + "name": "host", + "displayName": "host address", + "type" : "QString", + "inputType": "IPv4Address", + "readOnly": true + }, + { + "id": "5eca2b24-8986-4487-bc12-50e91d023d97", + "name": "sensorId", + "displayName": "sensor id", + "type" : "int", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "5e21b032-1230-4e93-8543-0c4773da17d3", + "name": "connected", + "displayName": "reachable", + "displayNameEvent": "reachable changed", + "defaultValue": false, + "type": "bool" + } + ], + "eventTypes": [ + { + "id": "c45dd703-7cbd-48f7-88dc-31045cc3d39c", + "name": "pressed", + "displayName": "Button pressed", + "paramTypes": [ + { + "id": "8ed643c0-1b8a-4709-8abf-717cf213f4a4", + "name": "buttonName", + "displayName": "Button name", + "type": "QString", + "allowedValues": ["•", "••", "•••", "••••"] } ] } diff --git a/philipshue/hueremote.cpp b/philipshue/hueremote.cpp index 898cc290..4ff69d69 100644 --- a/philipshue/hueremote.cpp +++ b/philipshue/hueremote.cpp @@ -40,7 +40,13 @@ void HueRemote::setBattery(const int &battery) void HueRemote::updateStates(const QVariantMap &statesMap, const QVariantMap &configMap) { - setReachable(configMap.value("reachable", false).toBool()); + if (configMap.contains("reachable")) { + setReachable(configMap.value("reachable", false).toBool()); + } else { + // Hue Tap doesn't have a reachable property as it's a ultra low power device. Let's mark it reachable by default as we only + // get this response if the bridge is reachable. + setReachable(true); + } setBattery(configMap.value("battery", 0).toInt()); emit stateChanged(); diff --git a/philipshue/hueremote.h b/philipshue/hueremote.h index b1453c72..0ffb6ade 100644 --- a/philipshue/hueremote.h +++ b/philipshue/hueremote.h @@ -44,7 +44,11 @@ public: DimDownLongPressed = 3001, DimDownPressed = 3002, OffLongPressed = 4001, - OffPressed = 4002 + OffPressed = 4002, + TapButton1Pressed = 34, + TapButton2Pressed = 16, + TapButton3Pressed = 17, + TapButton4Pressed = 18 }; explicit HueRemote(QObject *parent = 0);