diff --git a/plugins/deviceplugins/deviceplugins.pro b/plugins/deviceplugins/deviceplugins.pro index 0a452e8e..76921d5a 100644 --- a/plugins/deviceplugins/deviceplugins.pro +++ b/plugins/deviceplugins/deviceplugins.pro @@ -27,4 +27,5 @@ SUBDIRS += elro \ orderbutton \ denon \ avahimonitor \ + senic \ diff --git a/plugins/deviceplugins/senic/nuimo.cpp b/plugins/deviceplugins/senic/nuimo.cpp new file mode 100644 index 00000000..6028d893 --- /dev/null +++ b/plugins/deviceplugins/senic/nuimo.cpp @@ -0,0 +1,332 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifdef BLUETOOTH_LE + +#include "nuimo.h" +#include "extern-plugininfo.h" + +#include + +Nuimo::Nuimo(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType, QObject *parent) : + BluetoothLowEnergyDevice(deviceInfo, addressType, parent), + m_deviceInfoService(NULL), + m_batteryService(NULL), + m_inputService(NULL), + m_ledMatrixService(NULL), + m_isAvailable(false) +{ + connect(this, SIGNAL(connectionStatusChanged()), this,SLOT(onConnectionStatusChanged())); + connect(this, SIGNAL(servicesDiscoveryFinished()), this, SLOT(serviceScanFinished())); +} + +bool Nuimo::isAvailable() +{ + return m_isAvailable; +} + +void Nuimo::showGuhLogo() +{ + QByteArray matrix( + " " + " * " + " ** ** " + " **** " + " ** " + " * " + " * " + " * " + " "); + + QBitArray bits; + bits.resize(81); + for (int i = 0; i < matrix.size(); i++) { + if (matrix.at(i) != ' ') { + bits[i] = true; + } + } + + QByteArray bytes; + bytes.resize(bits.count() / 8+1); + bytes.fill(0); + // Convert from QBitArray to QByteArray + for(int b = 0; b < bits.count(); ++b) + bytes[b / 8] = (bytes.at(b / 8) | ((bits[b] ? 1 : 0) << (b % 8))); + + bytes.append(QByteArray::fromHex("FF")); + bytes.append(QByteArray::fromHex("AA")); + + m_ledMatrixService->writeCharacteristic(m_ledMatrixCharacteristic, bytes); +} + +void Nuimo::registerService(QLowEnergyService *service) +{ + connect(service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(serviceStateChanged(QLowEnergyService::ServiceState))); + connect(service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic, QByteArray)), this, SLOT(serviceCharacteristicChanged(QLowEnergyCharacteristic, QByteArray))); + connect(service, SIGNAL(characteristicWritten(QLowEnergyCharacteristic, QByteArray)), this, SLOT(confirmedCharacteristicWritten(QLowEnergyCharacteristic, QByteArray))); + connect(service, SIGNAL(descriptorWritten(QLowEnergyDescriptor, QByteArray)), this, SLOT(confirmedDescriptorWritten(QLowEnergyDescriptor, QByteArray))); + connect(service, SIGNAL(error(QLowEnergyService::ServiceError)), this, SLOT(serviceError(QLowEnergyService::ServiceError))); + + service->discoverDetails(); +} + + +void Nuimo::serviceScanFinished() +{ + QBluetoothUuid deviceInfoUuid(QUuid("0000180A-0000-1000-8000-00805F9B34FB")); + QBluetoothUuid batteryUuid(QUuid("0000180F-0000-1000-8000-00805F9B34FB")); + QBluetoothUuid inputUuid(QUuid("F29B1525-CB19-40F3-BE5C-7241ECB82FD2")); + QBluetoothUuid ledMatrixUuid(QUuid("F29B1523-CB19-40F3-BE5C-7241ECB82FD1")); + + qCDebug(dcSenic()) << "Service scan finised"; + + if (!controller()->services().contains(deviceInfoUuid)) { + qCWarning(dcSenic()) << "Device Information service not found for device" << name() << address().toString(); + return; + } + + if (!controller()->services().contains(batteryUuid)) { + qCWarning(dcSenic()) << "Battery service not found for device" << name() << address().toString(); + return; + } + + if (!controller()->services().contains(ledMatrixUuid)) { + qCWarning(dcSenic()) << "Led matrix service not found for device" << name() << address().toString(); + return; + } + + if (!controller()->services().contains(inputUuid)) { + qCWarning(dcSenic()) << "Input service not found for device" << name() << address().toString(); + return; + } + + m_isAvailable = true; + emit availableChanged(); + + // Device information + m_deviceInfoService = controller()->createServiceObject(deviceInfoUuid, this); + if (!m_deviceInfoService) { + qCWarning(dcSenic()) << "Could not create device information service for device" << name() << address().toString(); + return; + } + + // Battery service + m_batteryService = controller()->createServiceObject(batteryUuid, this); + if (!m_batteryService) { + qCWarning(dcSenic()) << "Could not create battery service for device" << name() << address().toString(); + return; + } + + // Input service + m_inputService = controller()->createServiceObject(inputUuid, this); + if (!m_inputService) { + qCWarning(dcSenic()) << "Could not create input service for device" << name() << address().toString(); + return; + } + + // Led Matrix + m_ledMatrixService = controller()->createServiceObject(ledMatrixUuid, this); + if (!m_ledMatrixService) { + qCWarning(dcSenic()) << "Could not create led matrix service for device" << name() << address().toString(); + return; + } + + registerService(m_deviceInfoService); + registerService(m_batteryService); + registerService(m_inputService); + registerService(m_ledMatrixService); + +} + +void Nuimo::onConnectionStatusChanged() +{ + if (!isConnected()) { + // delete the services, they need to be recreated and + // rediscovered once the device will be reconnected + + if (m_deviceInfoService) { + delete m_deviceInfoService; + m_deviceInfoService = 0; + } + + if (m_batteryService) { + delete m_batteryService; + m_batteryService = 0; + } + + if (m_inputService) { + delete m_inputService; + m_inputService = 0; + } + + if (m_ledMatrixService) { + delete m_ledMatrixService; + m_ledMatrixService = 0; + } + + m_isAvailable = false; + emit availableChanged(); + } +} + +void Nuimo::serviceStateChanged(const QLowEnergyService::ServiceState &state) +{ + QLowEnergyService *service =static_cast(sender()); + + switch (state) { + case QLowEnergyService::DiscoveringServices: + if (service == m_batteryService) { + qCDebug(dcSenic()) << "Start discovering battery service..."; + } + if (service == m_deviceInfoService) { + qCDebug(dcSenic()) << "Start discovering device information service..."; + } + if (service == m_inputService) { + qCDebug(dcSenic()) << "Start discovering input service..."; + } + if (service == m_inputService) { + qCDebug(dcSenic()) << "Start discovering led matrix service..."; + } + break; + case QLowEnergyService::ServiceDiscovered: + + // Device information + if (service == m_deviceInfoService) { + qCDebug(dcSenic()) << "Device information service discovered"; + m_deviceInfoCharacteristic = m_deviceInfoService->characteristic(QBluetoothUuid(QUuid("00002A29-0000-1000-8000-00805F9B34FB"))); + if (!m_deviceInfoCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Device information characteristc not found for device " << name() << address().toString(); + return; + } + + qCDebug(dcSenic()) << "Device information:" << m_deviceInfoCharacteristic.value(); + } + + // Battery + if (service == m_batteryService) { + qCDebug(dcSenic()) << "Battery service discovered"; + m_batteryCharacteristic = m_batteryService->characteristic(QBluetoothUuid(QUuid("00002A19-0000-1000-8000-00805F9B34FB"))); + if (!m_batteryCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Battery characteristc not found for device " << name() << address().toString(); + return; + } + + int batteryPercentage = m_batteryCharacteristic.value().toHex().toUInt(0, 16); + qCDebug(dcSenic()) << "Battery:" << batteryPercentage << "%"; + + // Enable notification + foreach (const QLowEnergyDescriptor &descriptor, m_batteryCharacteristic.descriptors()) { + qCDebug(dcSenic()) << descriptor.name() << descriptor.uuid().toString(); + m_batteryService->writeDescriptor(descriptor, QByteArray::fromHex("0100")); + } + + emit batteryValueChaged(batteryPercentage); + } + + // Input + if (service == m_inputService) { + qCDebug(dcSenic()) << "Input service discovered"; + m_inputButtonCharacteristic = m_inputService->characteristic(QBluetoothUuid(QUuid("F29B1529-CB19-40F3-BE5C-7241ECB82FD2"))); + if (!m_inputButtonCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Button characteristc not found for device " << name() << address().toString(); + return; + } + + foreach (const QLowEnergyDescriptor &descriptor, m_inputButtonCharacteristic.descriptors()) { + qCDebug(dcSenic()) << descriptor.name() << descriptor.uuid().toString(); + m_inputService->writeDescriptor(descriptor, QByteArray::fromHex("0100")); + } + } + + // Input + if (service == m_ledMatrixService) { + qCDebug(dcSenic()) << "Led matrix service discovered"; + m_ledMatrixCharacteristic = m_ledMatrixService->characteristic(QBluetoothUuid(QUuid("F29B1524-CB19-40F3-BE5C-7241ECB82FD1"))); + if (!m_ledMatrixCharacteristic.isValid()) { + qCWarning(dcSenic()) << "Led matrix characteristc not found for device " << name() << address().toString(); + return; + } + + showGuhLogo(); + } + + break; + default: + break; + } +} + +void Nuimo::serviceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic.uuid() == m_batteryCharacteristic.uuid()) { + uint batteryValue = value.toHex().toUInt(0, 16); + qCDebug(dcSenic()) << "Battery:" << batteryValue; + emit batteryValueChaged(batteryValue); + return; + } + + if (characteristic.uuid() == m_inputButtonCharacteristic.uuid()) { + bool pressed = (bool)value.toHex().toUInt(0, 16); + qCDebug(dcSenic()) << "Button:" << (pressed ? "pressed": "released"); + if (pressed) { + emit buttonPressed(); + } else { + emit buttonReleased(); + } + return; + } + + qCDebug(dcSenic()) << "Service characteristic changed" << characteristic.name() << value.toHex(); +} + +void Nuimo::confirmedCharacteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + qCDebug(dcSenic()) << characteristic.name() << value; +} + +void Nuimo::confirmedDescriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &value) +{ + qCDebug(dcSenic()) << "Descriptor:" << descriptor.name() << "value:" << value.toHex() << "written successfully"; +} + +void Nuimo::serviceError(const QLowEnergyService::ServiceError &error) +{ + QString errorString; + switch (error) { + case QLowEnergyService::NoError: + errorString = "No error"; + break; + case QLowEnergyService::OperationError: + errorString = "Operation error"; + break; + case QLowEnergyService::CharacteristicWriteError: + errorString = "Characteristic write error"; + break; + case QLowEnergyService::DescriptorWriteError: + errorString = "Descriptor write error"; + break; + default: + break; + } + + qCWarning(dcSenic()) << "Service of " << name() << address().toString() << ":" << errorString; +} + +#endif // BLUETOOTH_LE diff --git a/plugins/deviceplugins/senic/nuimo.h b/plugins/deviceplugins/senic/nuimo.h new file mode 100644 index 00000000..a2ba166d --- /dev/null +++ b/plugins/deviceplugins/senic/nuimo.h @@ -0,0 +1,77 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef NUIMO_H +#define NUIMO_H + +#ifdef BLUETOOTH_LE + +#include +#include + +#include "typeutils.h" +#include "bluetooth/bluetoothlowenergydevice.h" + +class Nuimo : public BluetoothLowEnergyDevice +{ + Q_OBJECT +public: + explicit Nuimo(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType, QObject *parent = 0); + + bool isAvailable(); + + void showGuhLogo(); + +private: + QLowEnergyService *m_deviceInfoService; + QLowEnergyService *m_batteryService; + QLowEnergyService *m_inputService; + QLowEnergyService *m_ledMatrixService; + + QLowEnergyCharacteristic m_deviceInfoCharacteristic; + QLowEnergyCharacteristic m_batteryCharacteristic; + QLowEnergyCharacteristic m_inputButtonCharacteristic; + QLowEnergyCharacteristic m_ledMatrixCharacteristic; + + bool m_isAvailable; + + void registerService(QLowEnergyService *service); + +signals: + void availableChanged(); + void buttonPressed(); + void buttonReleased(); + void batteryValueChaged(const uint &percentage); + +private slots: + void serviceScanFinished(); + void onConnectionStatusChanged(); + + void serviceStateChanged(const QLowEnergyService::ServiceState &state); + void serviceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + void confirmedCharacteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + void confirmedDescriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &value); + void serviceError(const QLowEnergyService::ServiceError &error); + +}; + +#endif // BLUETOOTH_LE + +#endif // NUIMO_H