/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2016 Simon Stürz * * * * This file is part of guh. * * * * 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 * * . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifdef BLUETOOTH_LE #include "nuimo.h" #include "extern-plugininfo.h" #include #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( " " " * " " ** " " *** ** " " ***** " " ** " " ** " " * " " "); showMatrix(matrix, 10); } void Nuimo::showArrowUp() { QByteArray matrix( " * " " *** " " * * * " " * * * " "* * *" " * " " * " " * " " * "); showMatrix(matrix, 3); } void Nuimo::showArrowDown() { QByteArray matrix( " * " " * " " * " " * " "* * *" " * * * " " * * * " " *** " " * "); showMatrix(matrix, 3); } 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::showMatrix(const QByteArray &matrix, const int &seconds) { 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")); quint8 time = quint8(seconds * 10); bytes.append((char)time); m_ledMatrixService->writeCharacteristic(m_ledMatrixCharacteristic, bytes); } 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_ledMatrixService) 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"; // Button m_inputButtonCharacteristic = m_inputService->characteristic(QBluetoothUuid(QUuid("F29B1529-CB19-40F3-BE5C-7241ECB82FD2"))); if (!m_inputButtonCharacteristic.isValid()) { qCWarning(dcSenic()) << "Button characteristc not valid 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")); } // Swipe m_inputSwipeCharacteristic = m_inputService->characteristic(QBluetoothUuid(QUuid("F29B1527-CB19-40F3-BE5C-7241ECB82FD2"))); if (!m_inputSwipeCharacteristic.isValid()) { qCWarning(dcSenic()) << "Swipe characteristc not valid for device " << name() << address().toString(); return; } foreach (const QLowEnergyDescriptor &descriptor, m_inputSwipeCharacteristic.descriptors()) { qCDebug(dcSenic()) << descriptor.name() << descriptor.uuid().toString(); m_inputService->writeDescriptor(descriptor, QByteArray::fromHex("0100")); } // Swipe m_inputRotationCharacteristic = m_inputService->characteristic(QBluetoothUuid(QUuid("F29B1528-CB19-40F3-BE5C-7241ECB82FD2"))); if (!m_inputRotationCharacteristic.isValid()) { qCWarning(dcSenic()) << "Rotation characteristc not valid for device " << name() << address().toString(); return; } foreach (const QLowEnergyDescriptor &descriptor, m_inputRotationCharacteristic.descriptors()) { qCDebug(dcSenic()) << descriptor.name() << descriptor.uuid().toString(); m_inputService->writeDescriptor(descriptor, QByteArray::fromHex("0100")); } } // Led Matrix 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; } if (characteristic.uuid() == m_inputSwipeCharacteristic.uuid()) { quint8 swipe = (quint8)value.toHex().toUInt(0, 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 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::CharacteristicReadError: errorString = "Characteristic read error"; break; case QLowEnergyService::CharacteristicWriteError: errorString = "Characteristic write error"; break; case QLowEnergyService::DescriptorReadError: errorString = "Descriptor read error"; break; case QLowEnergyService::DescriptorWriteError: errorString = "Descriptor write error"; break; case QLowEnergyService::UnknownError: errorString = "Unknown error"; break; default: errorString = "Unknown error"; break; } qCWarning(dcSenic()) << "Service error of" << name() << address().toString() << ":" << errorString; } #endif // BLUETOOTH_LE