diff --git a/lukeroberts/integrationpluginlukeroberts.cpp b/lukeroberts/integrationpluginlukeroberts.cpp new file mode 100644 index 00000000..91f64596 --- /dev/null +++ b/lukeroberts/integrationpluginlukeroberts.cpp @@ -0,0 +1,286 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginlukeroberts.h" +#include "plugininfo.h" +#include "hardware/bluetoothlowenergy/bluetoothlowenergymanager.h" + +#include +#include + +IntegrationPluginLukeRoberts::IntegrationPluginLukeRoberts() +{ + +} + +void IntegrationPluginLukeRoberts::init() +{ +} + + +void IntegrationPluginLukeRoberts::discoverThings(ThingDiscoveryInfo *info) +{ + if (!hardwareManager()->bluetoothLowEnergyManager()->available()) + return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Bluetooth is not available on this system.")); + + if (!hardwareManager()->bluetoothLowEnergyManager()->enabled()) + return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Bluetooth is disabled. Please enable Bluetooth and try again.")); + + BluetoothDiscoveryReply *reply = hardwareManager()->bluetoothLowEnergyManager()->discoverDevices(); + connect(reply, &BluetoothDiscoveryReply::finished, reply, &BluetoothDiscoveryReply::deleteLater); + + connect(reply, &BluetoothDiscoveryReply::finished, info, [this, info, reply](){ + + if (reply->error() != BluetoothDiscoveryReply::BluetoothDiscoveryReplyErrorNoError) { + qCWarning(dcLukeRoberts()) << "Bluetooth discovery error:" << reply->error(); + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("An error happened during Bluetooth discovery.")); + return; + } + + foreach (const QBluetoothDeviceInfo &deviceInfo, reply->discoveredDevices()) { + if (deviceInfo.serviceUuids().contains(customControlServiceUuid)) { + ThingDescriptor descriptor(modelFThingClassId, "Model F", deviceInfo.name() + " (" + deviceInfo.address().toString() + ")"); + ParamList params; + + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(modelFThingMacParamTypeId).toString() == deviceInfo.address().toString()) { + descriptor.setThingId(existingThing->id()); + break; + } + } + params.append(Param(modelFThingMacParamTypeId, deviceInfo.address().toString())); + descriptor.setParams(params); + info->addThingDescriptor(descriptor); + } + } + info->finish(Thing::ThingErrorNoError); + }); +} + + +void IntegrationPluginLukeRoberts::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + qCDebug(dcLukeRoberts()) << "Setup device" << thing->name() << thing->params(); + + QBluetoothAddress address = QBluetoothAddress(thing->paramValue(modelFThingMacParamTypeId).toString()); + QBluetoothDeviceInfo deviceInfo = QBluetoothDeviceInfo(address, thing->name(), 0); + + BluetoothLowEnergyDevice *bluetoothDevice = hardwareManager()->bluetoothLowEnergyManager()->registerDevice(deviceInfo, QLowEnergyController::RandomAddress); + + LukeRoberts *lamp = new LukeRoberts(bluetoothDevice, this); + connect(lamp, &LukeRoberts::connectedChanged, this, &IntegrationPluginLukeRoberts::onConnectedChanged); + connect(lamp, &LukeRoberts::deviceInformationChanged, this, &IntegrationPluginLukeRoberts::onDeviceInformationChanged); + + m_lamps.insert(lamp, thing); + + connect(lamp, &LukeRoberts::deviceInitializationFinished, info, [this, info, lamp](bool success){ + Thing *thing = info->thing(); + + if (!thing->setupComplete()) { + if (success) { + info->finish(Thing::ThingErrorNoError); + } else { + m_lamps.take(lamp); + + hardwareManager()->bluetoothLowEnergyManager()->unregisterDevice(lamp->bluetoothDevice()); + lamp->deleteLater(); + + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error connecting to lamp.")); + } + } + + }); + lamp->bluetoothDevice()->connectDevice(); +} + +void IntegrationPluginLukeRoberts::postSetupThing(Thing *thing) +{ + Q_UNUSED(thing) + + if (!m_reconnectTimer) { + m_reconnectTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_reconnectTimer, &PluginTimer::timeout, this, &IntegrationPluginLukeRoberts::onReconnectTimeout); + } +} + + +void IntegrationPluginLukeRoberts::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + Action action = info->action(); + + QPointer lamp = m_lamps.key(thing); + if (lamp.isNull()) + return info->finish(Thing::ThingErrorHardwareFailure); + + if (!lamp->bluetoothDevice()->connected()) { + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + + if (action.actionTypeId() == modelFPowerActionTypeId) { + bool power = action.param(modelFPowerActionPowerParamTypeId).value().toBool(); + if (!power) { + lamp->selectScene(0); //Scene 0 is the off scene + } else { + lamp->selectScene(0xff); // Scene 0xff is the default scene + } + + } else if (action.actionTypeId() == modelFBrightnessActionTypeId) { + int brightness = action.param(modelFBrightnessActionBrightnessParamTypeId).value().toInt(); + lamp->modifyBrightness(brightness); + + } else if (action.actionTypeId() == modelFColorActionTypeId) { + QColor rgb = QColor::fromRgb(QRgb(action.param(modelFColorActionColorParamTypeId).value().toInt())); + lamp->setImmediateLight(0, rgb.saturation(), rgb.hue(), rgb.lightness()); + + } else if (action.actionTypeId() == modelFColorTemperatureActionTypeId) { + int kelvin = action.param(modelFColorTemperatureActionColorTemperatureParamTypeId).value().toInt(); + lamp->modifyColorTemperature(kelvin); + } +} + + +void IntegrationPluginLukeRoberts::thingRemoved(Thing *thing) +{ + if (!m_lamps.values().contains(thing)) + return; + + LukeRoberts *lamp = m_lamps.key(thing); + m_lamps.take(lamp); + + hardwareManager()->bluetoothLowEnergyManager()->unregisterDevice(lamp->bluetoothDevice()); + lamp->deleteLater(); + + if (myThings().isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer); + m_reconnectTimer = nullptr; + } +} + +void IntegrationPluginLukeRoberts::browseThing(BrowseResult *result) +{ + LukeRoberts *lamp = m_lamps.key(result->thing()); + if (!lamp) { + result->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + qDebug(dcLukeRoberts()) << "Browse thing called"; + m_pendingBrowseResults.insert(lamp, result); + lamp->getSceneList(); +} + +void IntegrationPluginLukeRoberts::browserItem(BrowserItemResult *result) +{ + LukeRoberts *lamp = m_lamps.key(result->thing()); + if (!lamp) { + result->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + qDebug(dcLukeRoberts()) << "Browse item called"; + +} + +void IntegrationPluginLukeRoberts::executeBrowserItem(BrowserActionInfo *info) +{ + LukeRoberts *lamp = m_lamps.key(info->thing()); + if (!lamp) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + lamp->selectScene(info->browserAction().itemId().toInt()); + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginLukeRoberts::executeBrowserItemAction(BrowserItemActionInfo *info) +{ + LukeRoberts *lamp = m_lamps.key(info->thing()); + if (!lamp) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } +} + +void IntegrationPluginLukeRoberts::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) +{ + Q_UNUSED(paramTypeId) + Q_UNUSED(value) +} + + +void IntegrationPluginLukeRoberts::onReconnectTimeout() +{ + foreach (LukeRoberts *lamp, m_lamps.keys()) { + if (!lamp->bluetoothDevice()->connected()) { + lamp->bluetoothDevice()->connectDevice(); + } + } +} + +void IntegrationPluginLukeRoberts::onConnectedChanged(bool connected) +{ + LukeRoberts *lamp = static_cast(sender()); + Thing *thing = m_lamps.value(lamp); + thing->setStateValue(modelFConnectedStateTypeId, connected); +} + +void IntegrationPluginLukeRoberts::onDeviceInformationChanged(const QString &firmwareRevision, const QString &hardwareRevision, const QString &softwareRevision) +{ + LukeRoberts *lamp = static_cast(sender()); + Thing *thing = m_lamps.value(lamp); + + qDebug(dcLukeRoberts()) << thing->name() << "Firmware" << firmwareRevision << "Hardware" << hardwareRevision << "Software" << softwareRevision; + //thing->setStateValue(modelFFirmwareRevisionStateTypeId, firmwareRevision); + //thing->setStateValue(modelFardwareRevisionStateTypeId, hardwareRevision); + //thing->setStateValue(modelFSoftwareRevisionStateTypeId, softwareRevision); +} + +void IntegrationPluginLukeRoberts::onSceneListReceived(QList scenes) +{ + LukeRoberts *lamp = static_cast(sender()); + if (m_pendingBrowseResults.contains(lamp)) { + BrowseResult *result = m_pendingBrowseResults.value(lamp); + foreach (LukeRoberts::Scene scene, scenes) { + BrowserItem item; + item.setId(QString::number(scene.id)); + item.setDisplayName(scene.name); + item.setBrowsable(false); + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconApplication); + result->addItem(item); + } + } +} + +void IntegrationPluginLukeRoberts::onStatusCodeReceived(LukeRoberts::StatusCodes statusCode) +{ + qDebug(dcLukeRoberts()) << "Status code received" << statusCode; +} diff --git a/lukeroberts/integrationpluginlukeroberts.h b/lukeroberts/integrationpluginlukeroberts.h new file mode 100644 index 00000000..39481721 --- /dev/null +++ b/lukeroberts/integrationpluginlukeroberts.h @@ -0,0 +1,82 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 Licene 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 INTEGRATIONPLUGINLUKEROBERTS_H +#define INTEGRATIONPLUGINLUKEROBERTS_H + +#include "plugintimer.h" +#include "integrations/integrationplugin.h" +#include "hardware/bluetoothlowenergy/bluetoothlowenergydevice.h" + +#include "lukeroberts.h" + +class IntegrationPluginLukeRoberts : public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginlukeroberts.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginLukeRoberts(); + + void init() override; + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; + + void thingRemoved(Thing *thing) override; + + void browseThing(BrowseResult *result) override; + void browserItem(BrowserItemResult *result) override; + void executeBrowserItem(BrowserActionInfo *info) override; + void executeBrowserItemAction(BrowserItemActionInfo *info) override; +private: + QHash m_lamps; + PluginTimer *m_reconnectTimer = nullptr; + bool m_autoSymbolMode = true; + + QHash m_pendingBrowseResults; + QHash m_pendingBrowserActions; + QHash m_pendingBrowserItemActions; + +private slots: + void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value); + void onReconnectTimeout(); + + void onConnectedChanged(bool connected); + void onStatusCodeReceived(LukeRoberts::StatusCodes statusCode); + void onDeviceInformationChanged(const QString &firmwareRevision, const QString &hardwareRevision, const QString &softwareRevision); + + void onSceneListReceived(QList scenes); +}; + +#endif // INTEGRATIONPLUGINLUKEROBERTS_H diff --git a/lukeroberts/integrationpluginlukeroberts.json b/lukeroberts/integrationpluginlukeroberts.json new file mode 100644 index 00000000..4cf36d96 --- /dev/null +++ b/lukeroberts/integrationpluginlukeroberts.json @@ -0,0 +1,98 @@ +{ + "displayName": "Luke Roberts", + "id": "40f368ee-1815-46fd-9d74-4b8e5b5f21e7", + "name": "LukeRoberts", + "vendors": [ + { + "displayName": "Luke Roberts", + "name": "LukeRoberts", + "id": "4e942850-9553-493c-8a4a-5a468e124099", + "thingClasses": [ + { + "id": "5de74ba9-4a46-4235-94b4-8aefc1e2b27f", + "name": "modelF", + "displayName": "Model F", + "createMethods": ["discovery"], + "interfaces": ["colorlight", "connectable"], + "browsable": true, + "paramTypes": [ + { + "id": "5cde31de-8c5d-4c10-890e-1dd3b5efcad1", + "name": "mac", + "displayName": "Mac address", + "type": "QString", + "inputType": "MacAddress" + } + ], + "stateTypes": [ + { + "id": "09b9e571-74a8-4d47-a432-ae9743915d8d", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "ac3a55e1-48af-402d-b9ef-6020d4a1cb4f", + "name": "power", + "displayName": "power", + "displayNameEvent": "power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true + }, + { + "id": "ce156b88-b142-4516-8110-5fe044ed0ba8", + "name": "colorTemperature", + "displayName": "color temperature", + "displayNameEvent": "color temperature changed", + "displayNameAction": "Set color temperature", + "type": "int", + "unit": "Mired", + "defaultValue": 170, + "minValue": 153, + "maxValue": 500, + "writable": true + }, + { + "id": "01844169-43de-42df-9908-f015bb3ea7c2", + "name": "color", + "displayName": "color", + "displayNameEvent": "color changed", + "displayNameAction": "Set color", + "type": "QColor", + "defaultValue": "#000000", + "writable": true + }, + { + "id": "70c92741-9c7f-47df-85c1-bdd05cef90b6", + "name": "brightness", + "displayName": "brightness", + "displayNameEvent": "brightness changed", + "displayNameAction": "Set brigtness", + "type": "int", + "unit": "Percentage", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100, + "writable": true + } + ], + "actionTypes": [ + ], + "eventTypes": [ + ], + "browserItemActionTypes": [ + { + "id": "51407fb3-9966-43bf-b3d4-98e1fe6588ec", + "name": "updateScenes", + "displayName": "Update scenes" + } + ] + } + ] + } + ] +}