diff --git a/philipshue/hueremote.cpp b/philipshue/hueremote.cpp index 8e171bba..0e9cec44 100644 --- a/philipshue/hueremote.cpp +++ b/philipshue/hueremote.cpp @@ -61,24 +61,20 @@ void HueRemote::updateStates(const QVariantMap &statesMap, const QVariantMap &co QString lastUpdate = statesMap.value("lastupdated").toString(); int buttonCode = statesMap.value("buttonevent").toInt(); - int rotationCode = statesMap.value("expectedrotation").toInt(); // If we never polled, just store lastUpdate/buttonCode/rotationCode and not emit a falsely button pressed event if (m_lastUpdate.isEmpty() || m_lastButtonCode == -1) { m_lastUpdate = lastUpdate; m_lastButtonCode = buttonCode; - m_lastRotationCode = rotationCode; } - if (m_lastUpdate != lastUpdate || m_lastButtonCode != buttonCode || m_lastRotationCode != rotationCode) { + if (m_lastUpdate != lastUpdate || m_lastButtonCode != buttonCode) { m_lastUpdate = lastUpdate; m_lastButtonCode = buttonCode; - m_lastRotationCode = rotationCode; qCDebug(dcPhilipsHue) << "button pressed" << buttonCode; emit buttonPressed(buttonCode); - emit rotated(rotationCode); } } diff --git a/philipshue/hueremote.h b/philipshue/hueremote.h index 9e5ea769..9f3d1253 100644 --- a/philipshue/hueremote.h +++ b/philipshue/hueremote.h @@ -55,12 +55,10 @@ private: int m_battery; QString m_lastUpdate; int m_lastButtonCode = -1; - int m_lastRotationCode = 0; signals: void stateChanged(); void buttonPressed(int buttonCode); - void rotated(int rotationCode); }; diff --git a/philipshue/huetapdial.cpp b/philipshue/huetapdial.cpp new file mode 100644 index 00000000..31d64e56 --- /dev/null +++ b/philipshue/huetapdial.cpp @@ -0,0 +1,163 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "huetapdial.h" +#include "extern-plugininfo.h" + +#include + +HueTapDial::HueTapDial(HueBridge *bridge, QObject *parent) : + HueDevice(bridge, parent) +{ +} + +int HueTapDial::rotaryId() const +{ + return m_rotaryId; +} + +void HueTapDial::setRotaryId(int sensorId) +{ + m_rotaryId = sensorId; +} + +QString HueTapDial::rotaryUuid() const +{ + return m_rotaryUuid; +} + +void HueTapDial::setRotaryUuid(const QString &rotaryUuid) +{ + m_rotaryUuid = rotaryUuid; +} + +int HueTapDial::switchId() const +{ + return m_switchId; +} + +void HueTapDial::setSwitchId(int sensorId) +{ + m_SwitchId = sensorId; +} + +QString HueTapDial::switchUuid() const +{ + return m_switchUuid; +} + +void HueTapDial::setSwitchUuid(const QString &switchUuid) +{ + m_switchUuid = switchUuid; +} + +double HueTapDial::level() const +{ + return m_level; +} + +int HueTapDial::batteryLevel() const +{ + return m_batteryLevel; +} + +void HueTapDial::updateStates(const QVariantMap &sensorMap) +{ + qCDebug(dcPhilipsHue()) << "Hue Tap Dial data:" << qUtf8Printable(QJsonDocument::fromVariant(sensorMap).toJson(QJsonDocument::Indented)); + + // Config + QVariantMap configMap = sensorMap.value("config").toMap(); + if (configMap.contains("reachable")) { + setReachable(configMap.value("reachable", false).toBool()); + // emit reachableChanged???? + } + + if (configMap.contains("battery")) { + int batteryLevel = configMap.value("battery", 0).toInt(); + if (m_batteryLevel != batteryLevel) { + m_batteryLevel = batteryLevel; + emit batteryLevelChanged(m_batteryLevel); + } + } + + // States + QVariantMap stateMap = sensorMap.value("state").toMap(); + + // If rotated + if (sensorMap.value("uniqueid").toString() == m_rotaryUuid) { + QString lastUpdate = stateMap.value("lastupdated").toString(); + int rotationCode = stateMap.value("expectedrotation").toInt(); + + // If we never polled, just store lastUpdate/rotationCode and not emit a false rotated event + if (m_lastUpdate.isEmpty() || m_lastRotationCode == 0) { + m_lastUpdate = lastUpdate; + m_lastRotationCode = rotationCode; + } + if (m_lastUpdate != lastUpdate || m_lastRotationCode != rotationCode) { + m_lastUpdate = lastUpdate; + m_lastRotationCode = rotationCode; + qCDebug(dcPhilipsHue) << "rotated" << rotationCode; + emit rotated(rotationCode); + } + } + + // If button press + if (sensorMap.value("uniqueid").toString() == m_switchUuid) { + QString lastUpdate = stateMap.value("lastupdated").toString(); + int buttonCode = stateMap.value("buttonevent").toInt(); + + // If we never polled, just store lastUpdate/buttonCode and not emit a false button pressed event + if (m_lastUpdate.isEmpty() || m_lastButtonCode == -1) { + m_lastUpdate = lastUpdate; + m_lastButtonCode = buttonCode; + } + if (m_lastUpdate != lastUpdate || m_lastButtonCode != buttonCode) { + m_lastUpdate = lastUpdate; + m_lastButtonCode = buttonCode; + qCDebug(dcPhilipsHue) << "button pressed" << buttonCode; + emit buttonPressed(buttonCode); + } + } +} + +bool HueTapDial::isValid() +{ + return !m_rotaryUuid.isEmpty() && !m_switchUuid.isEmpty(); +} + +bool HueTapDial::hasSensor(int sensorId) +{ + return m_rotaryId == sensorId || m_switchId == sensorId; +} + +bool HueTapDial::hasSensor(const QString &sensorUuid) +{ + return m_rotaryUuid == sensorUuid || m_switchUuid == sensorUuid; +} diff --git a/philipshue/huetapdial.h b/philipshue/huetapdial.h new file mode 100644 index 00000000..546f251a --- /dev/null +++ b/philipshue/huetapdial.h @@ -0,0 +1,101 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HUETAPDIAL_H +#define HUETAPDIAL_H + +#include +#include + +#include "extern-plugininfo.h" +#include "huedevice.h" + +class HueTapDial : public HueDevice +{ + Q_OBJECT +public: + explicit HueTapDial(HueBridge *bridge, QObject *parent = nullptr); + //virtual ~HueTapDial() = default; + + int rotaryId() const; + void setRotaryId(int sensorId); + + QString rotaryUuid() const; + void setRotaryUuid(const QString &rotaryUuid); + + int switchId() const; + void setSwitchId(int sensorId); + + QString switchUuid() const; + void setSwitchUuid(const QString &switchUuid); + + int level() const; + int batteryLevel() const; + + void updateStates(const QVariantMap &sensorMap); + + bool isValid(); + bool hasSensor(int sensorId); + bool hasSensor(const QString &sensorUuid); + + // virtual StateTypeId connectedStateTypeId() const = 0; + // virtual StateTypeId levelStateTypeId() const = 0; + // virtual StateTypeId batteryLevelStateTypeId() const = 0; + // virtual StateTypeId batteryCriticalStateTypeId() const = 0; + + // StateTypeId connectedStateTypeId() const override { return tapDialConnectedStateTypeId; } + // StateTypeId levelStateTypeId() const override { return tapDialLevelStateTypeId; } + // StateTypeId batteryLevelStateTypeId() const override { return tapDialBatteryLevelStateTypeId; } + // StateTypeId batteryCriticalStateTypeId() const override { return tapDialBatteryCriticalStateTypeId; } + +private: + // Params + int m_rotaryId; + QString m_rotaryUuid; + + int m_switchId; + QString m_switchUuid; + + // States + QString m_lastUpdate; + double m_level = 0; + int m_batteryLevel = 0; + int m_lastButtonCode = -1; + int m_lastRotationCode = 0; + +signals: + void levelChanged(double level); + void batteryLevelChanged(int batteryLevel); + void buttonPressed(int buttonCode); + void rotated(int rotationCode); + +}; + +#endif // HUETAPDIAL_H diff --git a/philipshue/integrationpluginphilipshue.cpp b/philipshue/integrationpluginphilipshue.cpp index 37efc1ce..a89b3bfc 100644 --- a/philipshue/integrationpluginphilipshue.cpp +++ b/philipshue/integrationpluginphilipshue.cpp @@ -519,18 +519,21 @@ void IntegrationPluginPhilipsHue::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == tapDialThingClassId) { qCDebug(dcPhilipsHue) << "Setup Hue Tap Dial" << thing->params() << thing->thingClassId(); - HueRemote *hueTapDial = new HueRemote(bridge, this); + HueTapDial *hueTapDial = new HueTapDial(bridge, this); - hueTapDial->setId(thing->paramValue(tapDialThingSensorIdParamTypeId).toInt()); hueTapDial->setModelId(thing->paramValue(tapDialThingModelIdParamTypeId).toString()); - hueTapDial->setType(thing->paramValue(tapDialThingTypeParamTypeId).toString()); hueTapDial->setUuid(thing->paramValue(tapDialThingUuidParamTypeId).toString()); + hueTapDial->setRotaryId(thing->paramValue(tapDialThingIdRotaryParamTypeId).toInt()); + hueTapDial->setRotaryUuid(thing->paramValue(tapDialThingUuidRotaryParamTypeId).toString()); + hueTapDial->setSwitchId(thing->paramValue(tapDialThingIdSwitchParamTypeId).toInt()); + hueTapDial->setSwitchUuid(thing->paramValue(tapDialThingUuidSwitchParamTypeId).toString()); - connect(hueTapDial, &HueRemote::stateChanged, this, &IntegrationPluginPhilipsHue::remoteStateChanged); - connect(hueTapDial, &HueRemote::buttonPressed, this, &IntegrationPluginPhilipsHue::onRemoteButtonEvent); - connect(hueTapDial, &HueRemote::rotated, this, &IntegrationPluginPhilipsHue::onRemoteRotaryEvent); + connect(hueTapDial, &HueTapDial::reachableChanged, this, &IntegrationPluginPhilipsHue::onTapDialReachableChanged); + connect(hueTapDial, &HueTapDial::batteryLevelChanged, this, &IntegrationPluginPhilipsHue::onTapDialBatteryLevelChanged); + connect(hueTapDial, &HueTapDial::buttonPressed, this, &IntegrationPluginPhilipsHue::onTapDialButtonEvent); + connect(hueTapDial, &HueTapDial::rotated, this, &IntegrationPluginPhilipsHue::onTapDialRotaryEvent); - m_remotes.insert(hueTapDial, thing); + m_tapDials.insert(hueTapDial, thing); return info->finish(Thing::ThingErrorNoError); } @@ -707,12 +710,18 @@ void IntegrationPluginPhilipsHue::thingRemoved(Thing *thing) light->deleteLater(); } - if (thing->thingClassId() == remoteThingClassId || thing->thingClassId() == dimmerSwitch2ThingClassId || thing->thingClassId() == tapDialThingClassId || thing->thingClassId() == tapThingClassId || thing->thingClassId() == fohThingClassId || thing->thingClassId() == smartButtonThingClassId || thing->thingClassId() == wallSwitchThingClassId) { + if (thing->thingClassId() == remoteThingClassId || thing->thingClassId() == dimmerSwitch2ThingClassId || thing->thingClassId() == tapThingClassId || thing->thingClassId() == fohThingClassId || thing->thingClassId() == smartButtonThingClassId || thing->thingClassId() == wallSwitchThingClassId) { HueRemote *remote = m_remotes.key(thing); m_remotes.remove(remote); remote->deleteLater(); } + if (thing->thingClassId() == tapDialThingClassId) { + HueTapDial *tapDial = m_tapDials.key(thing); + m_tapDials.remove(tapDial); + tapDial->deleteLater(); + } + if (thing->thingClassId() == outdoorSensorThingClassId || thing->thingClassId() == motionSensorThingClassId) { HueMotionSensor *motionSensor = m_motionSensors.key(thing); m_motionSensors.remove(motionSensor); @@ -1340,49 +1349,6 @@ void IntegrationPluginPhilipsHue::onRemoteButtonEvent(int buttonCode) // * codes ending in 0 (e.g. 1000) indicate start of long press // * codes ending in 3 (e.g. 1003) indicate end of long press // * codes ending in 1 (e.g. 1001) are sent during the long press - } else if (thing->thingClassId() == tapDialThingClassId) { - switch (buttonCode) { - case 1002: - param = Param(tapDialPressedEventButtonNameParamTypeId, "•"); - id = tapDialPressedEventTypeId; - break; - case 1001: - param = Param(tapDialLongPressedEventButtonNameParamTypeId, "•"); - id = tapDialLongPressedEventTypeId; - break; - case 2002: - param = Param(tapDialPressedEventButtonNameParamTypeId, "••"); - id = tapDialPressedEventTypeId; - break; - case 2001: - param = Param(tapDialLongPressedEventButtonNameParamTypeId, "••"); - id = tapDialLongPressedEventTypeId; - break; - case 3002: - param = Param(tapDialPressedEventButtonNameParamTypeId, "•••"); - id = tapDialPressedEventTypeId; - break; - case 3001: - param = Param(tapDialLongPressedEventButtonNameParamTypeId, "•••"); - id = tapDialLongPressedEventTypeId; - break; - case 4002: - param = Param(tapDialPressedEventButtonNameParamTypeId, "••••"); - id = tapDialPressedEventTypeId; - break; - case 4001: - param = Param(tapDialLongPressedEventButtonNameParamTypeId, "••••"); - id = tapDialLongPressedEventTypeId; - break; - default: - qCDebug(dcPhilipsHue()) << "Unhandled button code received from Hue Tap Dial:" << buttonCode << "Thing name:" << thing->name(); - return; - } - // codes ending in 2 (e.g. 1002) are short presses; - // for long presses the Dimmer Switch V2 sends 3 codes: - // * codes ending in 0 (e.g. 1000) indicate start of long press - // * codes ending in 3 (e.g. 1003) indicate end of long press --> not yet supported by this plugin, but e.g. LongPressEnded action could be added - // * codes ending in 1 (e.g. 1001) are sent during the long press --> probably for backwards compatibility with earlier version, and therefore not added to this plugin } else if (thing->thingClassId() == tapThingClassId) { switch (buttonCode) { case 34: @@ -1457,15 +1423,75 @@ void IntegrationPluginPhilipsHue::onRemoteButtonEvent(int buttonCode) emitEvent(Event(id, m_remotes.value(remote)->id(), ParamList() << param)); } -void IntegrationPluginPhilipsHue::onRemoteRotaryEvent(int rotationCode) +void IntegrationPluginPhilipsHue::onTapDialButtonEvent(int buttonCode) { - HueRemote *remote = static_cast(sender()); - Thing *thing = m_remotes.value(remote); + HueTapDial *tapDial = static_cast(sender()); + Thing *thing = m_tapDials.value(tapDial); if (!thing) { qCWarning(dcPhilipsHue()) << "Received a button press event for a thing we don't know!"; return; } + EventTypeId id; + Param param; + + if (thing->thingClassId() == tapDialThingClassId) { + switch (buttonCode) { + case 1002: + param = Param(tapDialPressedEventButtonNameParamTypeId, "•"); + id = tapDialPressedEventTypeId; + break; + case 1001: + param = Param(tapDialLongPressedEventButtonNameParamTypeId, "•"); + id = tapDialLongPressedEventTypeId; + break; + case 2002: + param = Param(tapDialPressedEventButtonNameParamTypeId, "••"); + id = tapDialPressedEventTypeId; + break; + case 2001: + param = Param(tapDialLongPressedEventButtonNameParamTypeId, "••"); + id = tapDialLongPressedEventTypeId; + break; + case 3002: + param = Param(tapDialPressedEventButtonNameParamTypeId, "•••"); + id = tapDialPressedEventTypeId; + break; + case 3001: + param = Param(tapDialLongPressedEventButtonNameParamTypeId, "•••"); + id = tapDialLongPressedEventTypeId; + break; + case 4002: + param = Param(tapDialPressedEventButtonNameParamTypeId, "••••"); + id = tapDialPressedEventTypeId; + break; + case 4001: + param = Param(tapDialLongPressedEventButtonNameParamTypeId, "••••"); + id = tapDialLongPressedEventTypeId; + break; + default: + qCDebug(dcPhilipsHue()) << "Unhandled button code received from Hue Tap Dial:" << buttonCode << "Thing name:" << thing->name(); + return; + } + // codes ending in 2 (e.g. 1002) are short presses; + // for long presses the Dimmer Switch V2 sends 3 codes: + // * codes ending in 0 (e.g. 1000) indicate start of long press + // * codes ending in 3 (e.g. 1003) indicate end of long press --> not yet supported by this plugin, but e.g. LongPressEnded action could be added + // * codes ending in 1 (e.g. 1001) are sent during the long press --> probably for backwards compatibility with earlier version, and therefore not added to this plugin + } + emitEvent(Event(id, m_tapDials.value(tapDial)->id(), ParamList() << param)); +} + + +void IntegrationPluginPhilipsHue::onTapDialRotaryEvent(int rotationCode) +{ + HueTapDial *tapDial = static_cast(sender()); + Thing *thing = m_tapDials.value(tapDial); + if (!thing) { + qCWarning(dcPhilipsHue()) << "Received a rotary event for a thing we don't know!"; + return; + } + EventTypeId id; Param param; int currentLevel = thing->stateValue(tapDialLevelStateTypeId).toUInt(); @@ -1491,7 +1517,22 @@ void IntegrationPluginPhilipsHue::onRemoteRotaryEvent(int rotationCode) return; } } - emitEvent(Event(id, m_remotes.value(remote)->id())); + emitEvent(Event(id, m_tapDials.value(tapDial)->id())); +} + +void IntegrationPluginPhilipsHue::onTapDialReachableChanged(bool reachable) +{ + HueTapDial *tapDial = static_cast(sender()); + Thing *tapDialDevice = m_tapDials.value(tapDial); + tapDialDevice->setStateValue(tapDialConnectedStateTypeId, reachable); +} + +void IntegrationPluginPhilipsHue::onTapDialBatteryLevelChanged(int batteryLevel) +{ + HueTapDial *tapDial = static_cast(sender()); + Thing *tapDialDevice = m_tapDials.value(tapDial); + tapDialDevice->setStateValue(tapDialBatteryLevelStateTypeId, batteryLevel); + tapDialDevice->setStateValue(tapDialBatteryCriticalStateTypeId, (batteryLevel < 5)); } void IntegrationPluginPhilipsHue::onMotionSensorReachableChanged(bool reachable) @@ -1784,8 +1825,10 @@ void IntegrationPluginPhilipsHue::processBridgeSensorDiscoveryResponse(Thing *th // Create sensors if not already added QVariantMap sensorsMap = jsonDoc.toVariant().toMap(); QHash motionSensors; + QHash tapDials; QList remotesToRemove = m_remotes.keys(); QList sensorsToRemove = m_motionSensors.keys(); + QList tapDialsToRemove = m_tapDials.keys(); foreach (const QString &sensorId, sensorsMap.keys()) { QVariantMap sensorMap = sensorsMap.value(sensorId).toMap(); @@ -1800,6 +1843,12 @@ void IntegrationPluginPhilipsHue::processBridgeSensorDiscoveryResponse(Thing *th break; } } + foreach (HueTapDial* tapDial, tapDialsToRemove) { + if (tapDial->uuid() == uuid.split("-").first()) { + tapDialsToRemove.removeAll(tapDial); + break; + } + } foreach (HueMotionSensor* sensor, sensorsToRemove) { if (sensor->uuid() == uuid.split("-").first()) { sensorsToRemove.removeAll(sensor); @@ -1836,26 +1885,49 @@ void IntegrationPluginPhilipsHue::processBridgeSensorDiscoveryResponse(Thing *th // Tap Dial } else if (model == "RDM002") { + // Get the base uuid from this sensor + QString baseUuid = HueDevice::getBaseUuid(uuid); + qCDebug(dcPhilipsHue) << "Base uuid:" << baseUuid; + + // Rotary dial if (sensorMap.value("type").toString() == "ZLLRelativeRotary") { - ThingDescriptor descriptor(tapDialThingClassId, sensorMap.value("name").toString(), "Philips Hue Tap Dial", thing->id()); - ParamList params; - params.append(Param(tapDialThingModelIdParamTypeId, model)); - params.append(Param(tapDialThingTypeParamTypeId, sensorMap.value("type").toString())); - params.append(Param(tapDialThingUuidParamTypeId, uuid)); - params.append(Param(tapDialThingSensorIdParamTypeId, sensorId)); - descriptor.setParams(params); - emit autoThingsAppeared({descriptor}); - qCDebug(dcPhilipsHue) << "Found new tap dial" << sensorMap.value("name").toString() << model; - } else if (sensorMap.value("type").toString() == "ZLLSwitch") { - ThingDescriptor descriptor(tapDialThingClassId, sensorMap.value("name").toString(), "Philips Hue Tap Dial", thing->id()); - ParamList params; - params.append(Param(tapDialThingModelIdParamTypeId, model)); - params.append(Param(tapDialThingTypeParamTypeId, sensorMap.value("type").toString())); - params.append(Param(tapDialThingUuidParamTypeId, uuid)); - params.append(Param(tapDialThingSensorIdParamTypeId, sensorId)); - descriptor.setParams(params); - emit autoThingsAppeared({descriptor}); - qCDebug(dcPhilipsHue) << "Found new tap dial" << sensorMap.value("name").toString() << model; + qCDebug(dcPhilipsHue()) << "Found rotary dial from tap dial:" << baseUuid << sensorMap; + // Check if we have tap dial for this rotary dial + if (tapDials.contains(baseUuid)) { + HueTapDial *tapDial = tapDials.value(baseUuid); + tapDial->setRotaryUuid(uuid); + tapDial->setRotaryId(sensorId.toInt()); + } else { + // Create a tap dial + HueTapDial *tapDial = nullptr; + tapDial = new HueTapDial(bridge, this); + + tapDial->setModelId(model); + tapDial->setUuid(baseUuid); + tapDial->setRotaryUuid(uuid); + tapDial->setRotaryId(sensorId.toInt()); + tapDials.insert(baseUuid, tapDial); + } + } + // Buttons + if (sensorMap.value("type").toString() == "ZLLSwitch") { + qCDebug(dcPhilipsHue()) << "Found switch from tap dial:" << baseUuid << sensorMap; + // Check if we have tap dial for this switch + if (tapDials.contains(baseUuid)) { + HueTapDial *tapDial = tapDials.value(baseUuid); + tapDial->setSwitchUuid(uuid); + tapDial->setSwitchId(sensorId.toInt()); + } else { + // Create a tap dial + HueTapDial *tapDial = nullptr; + tapDial = new HueTapDial(bridge, this); + + tapDial->setModelId(model); + tapDial->setUuid(baseUuid); + tapDial->setSwitchUuid(uuid); + tapDial->setSwitchId(sensorId.toInt()); + tapDials.insert(baseUuid, tapDial); + } } // Smart Button @@ -2023,6 +2095,28 @@ void IntegrationPluginPhilipsHue::processBridgeSensorDiscoveryResponse(Thing *th motionSensor->deleteLater(); } + // Create tap dials if there are any new devices found + foreach (HueTapDial *tapDial, tapDials.values()) { + QString baseUuid = tapDials.key(tapDial); + if (tapDial->isValid()) { + ThingDescriptor descriptor(tapDialThingClassId, tr("Philips Hue Tap Dial"), baseUuid, thing->id()); + ParamList params; + params.append(Param(tapDialThingModelIdParamTypeId, tapDial->modelId())); + params.append(Param(tapDialThingUuidParamTypeId, tapDial->uuid())); + params.append(Param(tapDialThingIdRotaryParamTypeId, tapDial->rotaryId())); + params.append(Param(tapDialThingUuidRotaryParamTypeId, tapDial->rotaryUuid())); + params.append(Param(tapDialThingIdSwitchParamTypeId, tapDial->switchId())); + params.append(Param(tapDialThingUuidSwitchParamTypeId, tapDial->switchUuid())); + descriptor.setParams(params); + qCDebug(dcPhilipsHue()) << "Found new tap dial" << baseUuid << tapDialThingClassId; + emit autoThingsAppeared({descriptor}); + } + + // Clean up + tapDials.remove(baseUuid); + tapDial->deleteLater(); + } + foreach (HueRemote* remote, remotesToRemove) { Thing *remoteThing = m_remotes.value(remote); if (remoteThing->parentId() == thing->id()) { @@ -2031,6 +2125,14 @@ void IntegrationPluginPhilipsHue::processBridgeSensorDiscoveryResponse(Thing *th } } + foreach (HueTapDial* tapDial, tapDialsToRemove) { + Thing *tapDialThing = m_tapDials.value(tapDial); + if (tapDialThing->parentId() == thing->id()) { + qCDebug(dcPhilipsHue()) << "Hue tap dial disappeared from bridge"; + emit autoThingDisappeared(tapDialThing->id()); + } + } + foreach (HueMotionSensor* sensor, sensorsToRemove) { Thing *sensorThing = m_motionSensors.value(sensor); if (sensorThing->parentId() == thing->id()) { @@ -2191,6 +2293,13 @@ void IntegrationPluginPhilipsHue::processSensorsRefreshResponse(Thing *thing, co } } + // Tap dials + foreach (HueTapDial *tapDial, m_tapDials.keys()) { + if (tapDial->hasSensor(sensorId.toInt()) && m_tapDials.value(tapDial)->parentId() == thing->id()) { + tapDial->updateStates(sensorMap); + } + } + // Motion sensors foreach (HueMotionSensor *motionSensor, m_motionSensors.keys()) { if (motionSensor->hasSensor(sensorId.toInt()) && m_motionSensors.value(motionSensor)->parentId() == thing->id()) { @@ -2273,6 +2382,13 @@ void IntegrationPluginPhilipsHue::bridgeReachableChanged(Thing *thing, bool reac } } + foreach (HueTapDial *tapDial, m_tapDials.keys()) { + if (m_tapDials.value(tapDial)->parentId() == thing->id()) { + tapDial->setReachable(false); + m_tapDials.value(tapDial)->setStateValue(tapDialConnectedStateTypeId, false); + } + } + foreach (HueMotionSensor *motionSensor, m_motionSensors.keys()) { if (m_motionSensors.value(motionSensor)->parentId() == thing->id()) { motionSensor->setReachable(false); @@ -2377,6 +2493,15 @@ bool IntegrationPluginPhilipsHue::sensorAlreadyAdded(const QString &uuid) } } + // Tap Dial consists out of 2 devices + if (thing->thingClassId() == tapDialThingClassId) { + if (thing->paramValue(tapDialThingUuidRotaryParamTypeId).toString() == uuid) { + return true; + } else if (thing->paramValue(tapDialThingUuidSwitchParamTypeId).toString() == uuid) { + return true; + } + } + // Outdoor sensor consits out of 3 sensors if (thing->thingClassId() == outdoorSensorThingClassId) { if (thing->paramValue(outdoorSensorThingSensorUuidLightParamTypeId).toString() == uuid) { diff --git a/philipshue/integrationpluginphilipshue.h b/philipshue/integrationpluginphilipshue.h index 9e5003db..f67d57a3 100644 --- a/philipshue/integrationpluginphilipshue.h +++ b/philipshue/integrationpluginphilipshue.h @@ -36,6 +36,7 @@ #include "huelight.h" #include "hueremote.h" #include "huemotionsensor.h" +#include "huetapdial.h" #include "plugintimer.h" #include "network/networkaccessmanager.h" @@ -73,7 +74,13 @@ private slots: void lightStateChanged(); void remoteStateChanged(); void onRemoteButtonEvent(int buttonCode); - void onRemoteRotaryEvent(int rotationCode); + + // Tap Dial + void onTapDialReachableChanged(bool reachable); + void onTapDialBatteryLevelChanged(int batteryLevel); + void onTapDialRotaryEvent(int rotationCode); + void onTapDialButtonEvent(int buttonCode); + // Motion sensor void onMotionSensorReachableChanged(bool reachable); @@ -122,6 +129,7 @@ private: QHash m_bridges; QHash m_lights; QHash m_remotes; + QHash m_tapDials; QHash m_motionSensors; void refreshLight(Thing *thing); diff --git a/philipshue/integrationpluginphilipshue.json b/philipshue/integrationpluginphilipshue.json index caeada94..134de668 100644 --- a/philipshue/integrationpluginphilipshue.json +++ b/philipshue/integrationpluginphilipshue.json @@ -687,13 +687,6 @@ "type" : "QString", "readOnly": true }, - { - "id": "65748463-dd3f-430d-871c-dd7ee4db0b1c", - "name": "type", - "displayName": "type", - "type" : "QString", - "readOnly": true - }, { "id": "e2365a3c-cdf3-4b1e-b908-e7642e467f20", "name": "uuid", @@ -702,11 +695,32 @@ "readOnly": true }, { - "id": "9263bef9-4dd9-4658-b798-cd39fcb70fee", - "name": "sensorId", - "displayName": "sensor id", + "id": "dead7cf0-3ecc-4332-8fc6-42d4e71f508c", + "name": "idRotary", + "displayName": "Rotary dial id", "type" : "int", "readOnly": true + }, + { + "id": "267bcb24-b5c1-421d-bc6a-9b8fc7b43696", + "name": "uuidRotary", + "displayName": "Rotary dial uuid", + "type" : "QString", + "readOnly": true + }, + { + "id": "5269c70c-49e6-4090-aa57-3c89ee328b1e", + "name": "idSwitch", + "displayName": "Switch id", + "type" : "int", + "readOnly": true + }, + { + "id": "ae7ce90a-9bfb-45e4-90e4-8e491b374249", + "name": "uuidSwitch", + "displayName": "Switch uuid", + "type" : "QString", + "readOnly": true } ], "settingsTypes": [