From 0aee95891d211a4ca1b62aece0b9f7382257614e Mon Sep 17 00:00:00 2001 From: nymea Date: Wed, 9 Oct 2019 14:06:53 +0200 Subject: [PATCH] added new plug-in Luke Roberts --- lukeroberts/README.md | 3 + lukeroberts/devicepluginlukeroberts.cpp | 336 +++++++++++++++++++++++ lukeroberts/devicepluginlukeroberts.h | 64 +++++ lukeroberts/devicepluginlukeroberts.json | 120 ++++++++ lukeroberts/lukeroberts.cpp | 303 ++++++++++++++++++++ lukeroberts/lukeroberts.h | 106 +++++++ lukeroberts/lukeroberts.pro | 15 + nymea-plugins.pro | 1 + 8 files changed, 948 insertions(+) create mode 100644 lukeroberts/README.md create mode 100644 lukeroberts/devicepluginlukeroberts.cpp create mode 100644 lukeroberts/devicepluginlukeroberts.h create mode 100644 lukeroberts/devicepluginlukeroberts.json create mode 100644 lukeroberts/lukeroberts.cpp create mode 100644 lukeroberts/lukeroberts.h create mode 100644 lukeroberts/lukeroberts.pro diff --git a/lukeroberts/README.md b/lukeroberts/README.md new file mode 100644 index 00000000..ffbef3f3 --- /dev/null +++ b/lukeroberts/README.md @@ -0,0 +1,3 @@ +# Luke Roberts + +This plugin allows you to controle Luke Roberts lights. https://www.luke-roberts.com diff --git a/lukeroberts/devicepluginlukeroberts.cpp b/lukeroberts/devicepluginlukeroberts.cpp new file mode 100644 index 00000000..dcff7cfd --- /dev/null +++ b/lukeroberts/devicepluginlukeroberts.cpp @@ -0,0 +1,336 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016-2018 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 "devicepluginsenic.h" +#include "devices/device.h" +#include "plugininfo.h" +#include "hardware/bluetoothlowenergy/bluetoothlowenergymanager.h" + +DevicePluginSenic::DevicePluginSenic() +{ + +} + +void DevicePluginSenic::init() +{ + // Initialize plugin configurations + m_autoSymbolMode = configValue(senicPluginAutoSymbolsParamTypeId).toBool(); + connect(this, &DevicePluginSenic::configValueChanged, this, &DevicePluginSenic::onPluginConfigurationChanged); +} + + +void DevicePluginSenic::discoverDevices(DeviceDiscoveryInfo *info) +{ + if (!hardwareManager()->bluetoothLowEnergyManager()->available()) + return info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Bluetooth is not available on this system.")); + + if (!hardwareManager()->bluetoothLowEnergyManager()->enabled()) + return info->finish(Device::DeviceErrorHardwareNotAvailable, 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(dcSenic()) << "Bluetooth discovery error:" << reply->error(); + info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("An error happened during Bluetooth discovery.")); + return; + } + + foreach (const QBluetoothDeviceInfo &deviceInfo, reply->discoveredDevices()) { + if (deviceInfo.name().contains("Nuimo")) { + DeviceDescriptor descriptor(nuimoDeviceClassId, "Nuimo", deviceInfo.name() + " (" + deviceInfo.address().toString() + ")"); + ParamList params; + + foreach (Device *existingDevice, myDevices()) { + if (existingDevice->paramValue(nuimoDeviceMacParamTypeId).toString() == deviceInfo.address().toString()) { + descriptor.setDeviceId(existingDevice->id()); + break; + } + } + params.append(Param(nuimoDeviceMacParamTypeId, deviceInfo.address().toString())); + descriptor.setParams(params); + info->addDeviceDescriptor(descriptor); + } + } + info->finish(Device::DeviceErrorNoError); + }); +} + + +void DevicePluginSenic::setupDevice(DeviceSetupInfo *info) +{ + Device *device = info->device(); + + qCDebug(dcSenic()) << "Setup device" << device->name() << device->params(); + + QBluetoothAddress address = QBluetoothAddress(device->paramValue(nuimoDeviceMacParamTypeId).toString()); + QBluetoothDeviceInfo deviceInfo = QBluetoothDeviceInfo(address, device->name(), 0); + + BluetoothLowEnergyDevice *bluetoothDevice = hardwareManager()->bluetoothLowEnergyManager()->registerDevice(deviceInfo, QLowEnergyController::RandomAddress); + + Nuimo *nuimo = new Nuimo(bluetoothDevice, this); + nuimo->setLongPressTime(configValue(senicPluginLongPressTimeParamTypeId).toInt()); + connect(nuimo, &Nuimo::buttonPressed, this, &DevicePluginSenic::onButtonPressed); + connect(nuimo, &Nuimo::buttonLongPressed, this, &DevicePluginSenic::onButtonLongPressed); + connect(nuimo, &Nuimo::swipeDetected, this, &DevicePluginSenic::onSwipeDetected); + connect(nuimo, &Nuimo::rotationValueChanged, this, &DevicePluginSenic::onRotationValueChanged); + connect(nuimo, &Nuimo::connectedChanged, this, &DevicePluginSenic::onConnectedChanged); + connect(nuimo, &Nuimo::deviceInformationChanged, this, &DevicePluginSenic::onDeviceInformationChanged); + connect(nuimo, &Nuimo::batteryValueChanged, this, &DevicePluginSenic::onBatteryValueChanged); + + m_nuimos.insert(nuimo, device); + + connect(nuimo, &Nuimo::deviceInitializationFinished, info, [this, info, nuimo](bool success){ + Device *device = info->device(); + + if (!device->setupComplete()) { + if (success) { + info->finish(Device::DeviceErrorNoError); + } else { + m_nuimos.take(nuimo); + + hardwareManager()->bluetoothLowEnergyManager()->unregisterDevice(nuimo->bluetoothDevice()); + nuimo->deleteLater(); + + info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error connecting to nuimo.")); + } + } + + }); + + + nuimo->bluetoothDevice()->connectDevice(); +} + +void DevicePluginSenic::postSetupDevice(Device *device) +{ + Q_UNUSED(device) + + if (!m_reconnectTimer) { + m_reconnectTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_reconnectTimer, &PluginTimer::timeout, this, &DevicePluginSenic::onReconnectTimeout); + } +} + + +void DevicePluginSenic::executeAction(DeviceActionInfo *info) +{ + Device *device = info->device(); + Action action = info->action(); + + QPointer nuimo = m_nuimos.key(device); + if (nuimo.isNull()) + return info->finish(Device::DeviceErrorHardwareFailure); + + if (!nuimo->bluetoothDevice()->connected()) { + return info->finish(Device::DeviceErrorHardwareNotAvailable); + } + + if (action.actionTypeId() == nuimoShowLogoActionTypeId) { + + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Up") + nuimo->showImage(Nuimo::MatrixTypeUp); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Down") + nuimo->showImage(Nuimo::MatrixTypeDown); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Left") + nuimo->showImage(Nuimo::MatrixTypeLeft); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Right") + nuimo->showImage(Nuimo::MatrixTypeRight); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Play") + nuimo->showImage(Nuimo::MatrixTypePlay); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Pause") + nuimo->showImage(Nuimo::MatrixTypePause); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Stop") + nuimo->showImage(Nuimo::MatrixTypeStop); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Music") + nuimo->showImage(Nuimo::MatrixTypeMusic); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Heart") + nuimo->showImage(Nuimo::MatrixTypeHeart); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Next") + nuimo->showImage(Nuimo::MatrixTypeNext); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Previous") + nuimo->showImage(Nuimo::MatrixTypePrevious); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Circle") + nuimo->showImage(Nuimo::MatrixTypeCircle); + if (action.param(nuimoShowLogoActionLogoParamTypeId).value().toString() == "Light") + nuimo->showImage(Nuimo::MatrixTypeLight); + + return info->finish(Device::DeviceErrorNoError); + } +} + + +void DevicePluginSenic::deviceRemoved(Device *device) +{ + if (!m_nuimos.values().contains(device)) + return; + + Nuimo *nuimo = m_nuimos.key(device); + m_nuimos.take(nuimo); + + hardwareManager()->bluetoothLowEnergyManager()->unregisterDevice(nuimo->bluetoothDevice()); + nuimo->deleteLater(); + + if (myDevices().isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer); + m_reconnectTimer = nullptr; + } +} + + +void DevicePluginSenic::onReconnectTimeout() +{ + foreach (Nuimo *nuimo, m_nuimos.keys()) { + if (!nuimo->bluetoothDevice()->connected()) { + nuimo->bluetoothDevice()->connectDevice(); + } + } +} + +void DevicePluginSenic::onConnectedChanged(bool connected) +{ + Nuimo *nuimo = static_cast(sender()); + Device *device = m_nuimos.value(nuimo); + device->setStateValue(nuimoConnectedStateTypeId, connected); +} + + +void DevicePluginSenic::onButtonPressed() +{ + Nuimo *nuimo = static_cast(sender()); + Device *device = m_nuimos.value(nuimo); + emitEvent(Event(nuimoPressedEventTypeId, device->id(), ParamList() << Param(nuimoPressedEventButtonNameParamTypeId, "•"))); + + if (m_autoSymbolMode) { + nuimo->showImage(Nuimo::MatrixTypeCircle); + } +} + + +void DevicePluginSenic::onButtonLongPressed() +{ + Nuimo *nuimo = static_cast(sender()); + Device *device = m_nuimos.value(nuimo); + emitEvent(Event(nuimoLongPressedEventTypeId, device->id())); + + if (m_autoSymbolMode) { + nuimo->showImage(Nuimo::MatrixTypeFilledCircle); + } +} + + +void DevicePluginSenic::onSwipeDetected(const Nuimo::SwipeDirection &direction) +{ + Nuimo *nuimo = static_cast(sender()); + Device *device = m_nuimos.value(nuimo); + + switch (direction) { + case Nuimo::SwipeDirectionLeft: + emitEvent(Event(nuimoPressedEventTypeId, device->id(), ParamList() << Param(nuimoPressedEventButtonNameParamTypeId, "←"))); + break; + case Nuimo::SwipeDirectionRight: + emitEvent(Event(nuimoPressedEventTypeId, device->id(), ParamList() << Param(nuimoPressedEventButtonNameParamTypeId, "→"))); + break; + case Nuimo::SwipeDirectionUp: + emitEvent(Event(nuimoPressedEventTypeId, device->id(), ParamList() << Param(nuimoPressedEventButtonNameParamTypeId, "↑"))); + break; + case Nuimo::SwipeDirectionDown: + emitEvent(Event(nuimoPressedEventTypeId, device->id(), ParamList() << Param(nuimoPressedEventButtonNameParamTypeId, "↓"))); + break; + } + + if (m_autoSymbolMode) { + switch (direction) { + case Nuimo::SwipeDirectionLeft: + nuimo->showImage(Nuimo::MatrixType::MatrixTypeLeft); + break; + case Nuimo::SwipeDirectionRight: + nuimo->showImage(Nuimo::MatrixType::MatrixTypeRight); + break; + case Nuimo::SwipeDirectionUp: + nuimo->showImage(Nuimo::MatrixType::MatrixTypeUp); + break; + case Nuimo::SwipeDirectionDown: + nuimo->showImage(Nuimo::MatrixType::MatrixTypeDown); + break; + } + } +} + + +void DevicePluginSenic::onRotationValueChanged(const uint &value) +{ + Nuimo *nuimo = static_cast(sender()); + Device *device = m_nuimos.value(nuimo); + device->setStateValue(nuimoRotationStateTypeId, value); +} + + +void DevicePluginSenic::onDeviceInformationChanged(const QString &firmwareRevision, const QString &hardwareRevision, const QString &softwareRevision) +{ + Nuimo *nuimo = static_cast(sender()); + Device *device = m_nuimos.value(nuimo); + + device->setStateValue(nuimoFirmwareRevisionStateTypeId, firmwareRevision); + device->setStateValue(nuimoHardwareRevisionStateTypeId, hardwareRevision); + device->setStateValue(nuimoSoftwareRevisionStateTypeId, softwareRevision); +} + + +void DevicePluginSenic::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) +{ + qCDebug(dcSenic()) << "Plugin configuration changed"; + + // Check auto symbol mode + if (paramTypeId == senicPluginAutoSymbolsParamTypeId) { + qCDebug(dcSenic()) << "Auto symbol mode" << (value.toBool() ? "enabled." : "disabled."); + m_autoSymbolMode = value.toBool(); + } + + if (paramTypeId == senicPluginLongPressTimeParamTypeId) { + qCDebug(dcSenic()) << "Long press time" << value.toInt(); + foreach(Nuimo *nuimo, m_nuimos.keys()) { + nuimo->setLongPressTime(value.toInt()); + } + } +} + +void DevicePluginSenic::onBatteryValueChanged(const uint &percentage) +{ + Nuimo *nuimo = static_cast(sender()); + Device *device = m_nuimos.value(nuimo); + + device->setStateValue(nuimoBatteryLevelStateTypeId, percentage); + if (percentage < 20) { + device->setStateValue(nuimoBatteryCriticalStateTypeId, true); + } else { + device->setStateValue(nuimoBatteryCriticalStateTypeId, false); + } +} + +void DevicePluginLukeRoberts::onStatusCodeReceived(LukeRoberts::StatusCodes statusCode) +{ + + +} diff --git a/lukeroberts/devicepluginlukeroberts.h b/lukeroberts/devicepluginlukeroberts.h new file mode 100644 index 00000000..97e1507d --- /dev/null +++ b/lukeroberts/devicepluginlukeroberts.h @@ -0,0 +1,64 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Bernhard Trinnes . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINLUKEROBERTS_H +#define DEVICEPLUGINLUKEROBERTS_H + +#include "plugintimer.h" +#include "devices/deviceplugin.h" +#include "hardware/bluetoothlowenergy/bluetoothlowenergydevice.h" + +#include "lukeroberts.h" + +class DevicePluginLukeRoberts : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginlukeroberts.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginLukeRoberts(); + + void init() override; + void discoverDevices(DeviceDiscoveryInfo *info) override; + void setupDevice(DeviceSetupInfo *info) override; + void postSetupDevice(Device *device) override; + void executeAction(DeviceActionInfo *info) override; + + void deviceRemoved(Device *device) override; + +private: + QHash m_nuimos; + PluginTimer *m_reconnectTimer = nullptr; + bool m_autoSymbolMode = true; + +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); +}; + +#endif // DEVICEPLUGINLUKEROBERTS_H diff --git a/lukeroberts/devicepluginlukeroberts.json b/lukeroberts/devicepluginlukeroberts.json new file mode 100644 index 00000000..92449c4c --- /dev/null +++ b/lukeroberts/devicepluginlukeroberts.json @@ -0,0 +1,120 @@ +{ + "displayName": "LukeRoberts", + "id": "40f368ee-1815-46fd-9d74-4b8e5b5f21e7", + "name": "Luke Roberts", + "vendors": [ + { + "displayName": "Luke Roberts", + "name": "senic", + "id": "4e942850-9553-493c-8a4a-5a468e124099", + "deviceClasses": [ + { + "id": "5de74ba9-4a46-4235-94b4-8aefc1e2b27f", + "name": "Model F", + "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": [ + { + "id": "24649eb2-47d1-4a2b-8c09-1f074382e2c4", + "name": "pressed", + "displayName": "Button pressed", + "paramTypes": [ + { + "id": "8ed643c0-1b8a-4709-8abf-717cf213f4a4", + "name": "buttonName", + "displayName": "Button name", + "type": "QString", + "allowedValues": ["•", "←", "↑", "→", "↓"] + } + ] + }, + { + "id": "a2f4add5-f76a-4dca-ae68-4107533bee0e", + "name": "longPressed", + "displayName": "Button long pressed" + } + ], + "browserItemActionTypes": [ + { + "id": "51407fb3-9966-43bf-b3d4-98e1fe6588ec", + "name": "updateScenes", + "displayName": "Update scenes" + } + ] + } + ] + } + ] +} diff --git a/lukeroberts/lukeroberts.cpp b/lukeroberts/lukeroberts.cpp new file mode 100644 index 00000000..975eab0a --- /dev/null +++ b/lukeroberts/lukeroberts.cpp @@ -0,0 +1,303 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016-2018 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 "LukeRoberts.h" +#include "extern-plugininfo.h" + +#include +#include + +static QBluetoothUuid ledMatrinxServiceUuid = QBluetoothUuid(QUuid("f29b1523-cb19-40f3-be5c-7241ecb82fd1")); +static QBluetoothUuid ledMatrixCharacteristicUuid = QBluetoothUuid(QUuid("f29b1524-cb19-40f3-be5c-7241ecb82fd1")); + + +LukeRoberts::LukeRoberts(BluetoothLowEnergyDevice *bluetoothDevice, QObject *parent) : + QObject(parent), + m_bluetoothDevice(bluetoothDevice) +{ + connect(m_bluetoothDevice, &BluetoothLowEnergyDevice::connectedChanged, this, &LukeRoberts::onConnectedChanged); + connect(m_bluetoothDevice, &BluetoothLowEnergyDevice::servicesDiscoveryFinished, this, &LukeRoberts::onServiceDiscoveryFinished); +} + +BluetoothLowEnergyDevice *LukeRoberts::bluetoothDevice() +{ + return m_bluetoothDevice; +} + + +void LukeRoberts::printService(QLowEnergyService *service) +{ + foreach (const QLowEnergyCharacteristic &characteristic, service->characteristics()) { + qCDebug(dcSenic()) << " -->" << characteristic.name() << characteristic.uuid().toString() << characteristic.value(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcSenic()) << " -->" << descriptor.name() << descriptor.uuid().toString() << descriptor.value(); + } + } +} + + +void LukeRoberts::onConnectedChanged(bool connected) +{ + qCDebug(dcSenic()) << m_bluetoothDevice->name() << m_bluetoothDevice->address().toString() << (connected ? "connected" : "disconnected"); + + m_longPressTimer->stop(); + emit connectedChanged(connected); + + if (!connected) { + // Clean up services + m_deviceInfoService->deleteLater(); + m_batteryService->deleteLater(); + m_ledMatrixService->deleteLater(); + m_inputService->deleteLater(); + + m_deviceInfoService = nullptr; + m_batteryService = nullptr; + m_ledMatrixService = nullptr; + m_inputService = nullptr; + } + +} + +void LukeRoberts::onServiceDiscoveryFinished() +{ + qCDebug(dcSenic()) << "Service scan finised"; + + if (!m_bluetoothDevice->serviceUuids().contains(QBluetoothUuid::DeviceInformation)) { + qCWarning(dcSenic()) << "Device Information service not found for device" << bluetoothDevice()->name() << bluetoothDevice()->address().toString(); + emit deviceInitializationFinished(false); + return; + } + + if (!m_bluetoothDevice->serviceUuids().contains(ledMatrinxServiceUuid)) { + qCWarning(dcSenic()) << "Led matrix service not found for device" << bluetoothDevice()->name() << bluetoothDevice()->address().toString(); + emit deviceInitializationFinished(false); + return; + } + + if (!m_bluetoothDevice->serviceUuids().contains(inputServiceUuid)) { + qCWarning(dcSenic()) << "Input service not found for device" << bluetoothDevice()->name() << bluetoothDevice()->address().toString(); + emit deviceInitializationFinished(false); + return; + } + + // Device info service + if (!m_deviceInfoService) { + m_deviceInfoService = m_bluetoothDevice->controller()->createServiceObject(QBluetoothUuid::DeviceInformation, this); + if (!m_deviceInfoService) { + qCWarning(dcSenic()) << "Could not create device info service."; + emit deviceInitializationFinished(false); + return; + } + + connect(m_deviceInfoService, &QLowEnergyService::stateChanged, this, &LukeRoberts::onDeviceInfoServiceStateChanged); + + if (m_deviceInfoService->state() == QLowEnergyService::DiscoveryRequired) { + m_deviceInfoService->discoverDetails(); + } + } + + // Custom control service + if (!m_ledMatrixService) { + m_ledMatrixService = m_bluetoothDevice->controller()->createServiceObject(ledMatrinxServiceUuid, this); + if (!m_ledMatrixService) { + qCWarning(dcSenic()) << "Could not create led matrix service."; + emit deviceInitializationFinished(false); + return; + } + + connect(m_ledMatrixService, &QLowEnergyService::stateChanged, this, &LukeRoberts::onLedMatrixServiceStateChanged); + + if (m_ledMatrixService->state() == QLowEnergyService::DiscoveryRequired) { + m_ledMatrixService->discoverDetails(); + } + } + emit deviceInitializationFinished(true); +} + +void LukeRoberts::onDeviceInfoServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcSenic()) << "Device info service discovered."; + + printService(m_deviceInfoService); + QString firmware = QString::fromUtf8(m_deviceInfoService->characteristic(QBluetoothUuid::FirmwareRevisionString).value()); + QString hardware = QString::fromUtf8(m_deviceInfoService->characteristic(QBluetoothUuid::HardwareRevisionString).value()); + QString software = QString::fromUtf8(m_deviceInfoService->characteristic(QBluetoothUuid::SoftwareRevisionString).value()); + + emit deviceInformationChanged(firmware, hardware, software); +} + +void LukeRoberts::onBatteryServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcSenic()) << "Battery service discovered."; + + printService(m_batteryService); + + m_batteryCharacteristic = m_batteryService->characteristic(QBluetoothUuid::BatteryLevel); + if (!m_batteryCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Battery characteristc not found for device " << bluetoothDevice()->name() << bluetoothDevice()->address().toString(); + return; + } + + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_batteryCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_batteryService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + uint batteryPercentage = m_batteryCharacteristic.value().toHex().toUInt(nullptr, 16); + emit batteryValueChanged(batteryPercentage); +} + +void LukeRoberts::onBatteryCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic.uuid() == m_batteryCharacteristic.uuid()) { + uint batteryPercentage = value.toHex().toUInt(nullptr, 16); + emit batteryValueChanged(batteryPercentage); + } +} + +void LukeRoberts::onInputServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcSenic()) << "Input service discovered."; + + printService(m_inputService); + + // Button + m_inputButtonCharacteristic = m_inputService->characteristic(inputButtonCharacteristicUuid); + if (!m_inputButtonCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Input button characteristc not found for device " << bluetoothDevice()->name() << bluetoothDevice()->address().toString(); + return; + } + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_inputButtonCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_inputService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + + // Swipe + m_inputSwipeCharacteristic = m_inputService->characteristic(inputSwipeCharacteristicUuid); + if (!m_inputSwipeCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Input swipe characteristc not found for device " << bluetoothDevice()->name() << bluetoothDevice()->address().toString(); + return; + } + // Enable notifications + notificationDescriptor = m_inputSwipeCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_inputService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + + // Rotation + m_inputRotationCharacteristic = m_inputService->characteristic(inputRotationCharacteristicUuid); + if (!m_inputRotationCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Input rotation characteristc not found for device " << bluetoothDevice()->name() << bluetoothDevice()->address().toString(); + return; + } + // Enable notifications + notificationDescriptor = m_inputRotationCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_inputService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); +} + +void LukeRoberts::onInputCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic.uuid() == m_inputButtonCharacteristic.uuid()) { + bool pressed = (bool)value.toHex().toUInt(nullptr, 16); + qCDebug(dcSenic()) << "Button:" << (pressed ? "pressed": "released"); + if (pressed) { + m_longPressTimer->start(m_longPressTime); + } else { + if (m_longPressTimer->isActive()) { + m_longPressTimer->stop(); + emit buttonPressed(); + } + // else the time run out and has the long pressed event emittted + } + return; + } + + if (characteristic.uuid() == m_inputSwipeCharacteristic.uuid()) { + quint8 swipe = (quint8)value.toHex().toUInt(nullptr, 16); + switch (swipe) { + case 0: + qCDebug(dcSenic()) << "Swipe: Left"; + emit swipeDetected(SwipeDirectionLeft); + break; + case 1: + qCDebug(dcSenic()) << "Swipe: Right"; + emit swipeDetected(SwipeDirectionRight); + break; + case 2: + qCDebug(dcSenic()) << "Swipe: Up"; + emit swipeDetected(SwipeDirectionUp); + break; + case 3: + qCDebug(dcSenic()) << "Swipe: Down"; + emit swipeDetected(SwipeDirectionDown); + break; + default: + break; + } + return; + } + + if (characteristic.uuid() == m_inputRotationCharacteristic.uuid()) { + qint16 intValue = qFromLittleEndian((uchar *)value.constData()); + qCDebug(dcSenic()) << "Rotation" << value.toHex() << intValue; + int finalValue = m_rotationValue + qRound(intValue / 10.0); + if (finalValue <= 0) { + m_rotationValue = 0; + } else if (finalValue >= 100) { + m_rotationValue = 100; + } else { + m_rotationValue = finalValue; + } + emit rotationValueChanged(m_rotationValue); + return; + } + + qCDebug(dcSenic()) << "Service characteristic changed" << characteristic.name() << value.toHex(); +} + +void LukeRoberts::onLedMatrixServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcSenic()) << "Led matrix service discovered."; + + printService(m_ledMatrixService); + + // Led matrix + m_ledMatrixCharacteristic = m_ledMatrixService->characteristic(ledMatrixCharacteristicUuid); + if (!m_ledMatrixCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Led matrix characteristc not found for device " << bluetoothDevice()->name() << bluetoothDevice()->address().toString(); + return; + } +} diff --git a/lukeroberts/lukeroberts.h b/lukeroberts/lukeroberts.h new file mode 100644 index 00000000..65ce1ec5 --- /dev/null +++ b/lukeroberts/lukeroberts.h @@ -0,0 +1,106 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016-2018 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 NUIMO_H +#define NUIMO_H + +#include +#include +#include + +#include "typeutils.h" +#include "hardware/bluetoothlowenergy/bluetoothlowenergydevice.h" + +class LukeRoberts : public QObject +{ + Q_OBJECT +public: +enum StatusCodes { + StatusCodeSuccess = 0x00, + StatusCodeInvalidParameters = 0x81, + StatusCodeInvalidId = 0x84, + StatusCodeInvalidVersion = 0x87, + StatusCodeBadCommand = 0xbc, + StatusCodeForbidden = 0xfc, + StatusCodeFilteredDuplicate = 0x88 +}; + +enum Opcode { + OpcodePing, + OpcodeQueryScene, + OpcodeImmediateLight, + OpcodeDuration, + OpcodeBrightness, + OpcodeColorTemperature, + OpcodeSelectScene, + OpcodeNextSceneByBrightness, + OpcodeAdjustColorTemperature, + OpcodeRelativeBrightness +}; + + explicit LukeRoberts(BluetoothLowEnergyDevice *bluetoothDevice, QObject *parent = nullptr); + + BluetoothLowEnergyDevice *bluetoothDevice(); + +private: + BluetoothLowEnergyDevice *m_bluetoothDevice = nullptr; + + QLowEnergyService *m_deviceInfoService = nullptr; + QLowEnergyService *m_controlService = nullptr; //Luke Roberts Custom control service + QLowEnergyService *m_dfuService = nullptr; + + QLowEnergyCharacteristic m_deviceInfoCharacteristic; + QLowEnergyCharacteristic m_externalApiEndpoint; + + void ping(); + void queryScene(int id); + void immediateLight(int flags, int duration); + void brightness(int percent); + void colorTemperature(int kelvin); + void selectScene(int id); + void nextSceneByBrightness(); + void adjustColorTemperature(); + void relativeBrightness(int percent); + + void printService(QLowEnergyService *service); + void onLongPressTimer(); + +signals: + void connectedChanged(bool connected); + void deviceInformationChanged(const QString &firmwareRevision, const QString &hardwareRevision, const QString &softwareRevision); + void deviceInitializationFinished(bool success); + void statusCodeReveiced(StatusCodes statusCode); + +private slots: + void onConnectedChanged(bool connected); + void onServiceDiscoveryFinished(); + + void onDeviceInfoServiceStateChanged(const QLowEnergyService::ServiceState &state); + + void onBatteryServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onBatteryCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + + void onInputServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onInputCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); +}; + +#endif // NUIMO_H diff --git a/lukeroberts/lukeroberts.pro b/lukeroberts/lukeroberts.pro new file mode 100644 index 00000000..2b8782aa --- /dev/null +++ b/lukeroberts/lukeroberts.pro @@ -0,0 +1,15 @@ +include(../plugins.pri) + +QT += bluetooth + +TARGET = $$qtLibraryTarget(nymea_devicepluginlukeroberts) + +SOURCES += \ + devicepluginlukeroberts.cpp \ + lukeroberts.cpp + +HEADERS += \ + devicepluginlukeroberts.h \ + lukeroberts.h + + diff --git a/nymea-plugins.pro b/nymea-plugins.pro index eb037d01..f17a171c 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -31,6 +31,7 @@ PLUGIN_DIRS = \ kodi \ lgsmarttv \ lifx \ + lukeroberts \ mailnotification \ mqttclient \ nanoleaf \