blob: 322a86d786d1a856125af23fb521853b2aa2442c [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Lorn Potter
** Copyright (C) 2016 Canonical, Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtSensors module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "sensortagbase.h"
#include <QLowEnergyCharacteristic>
#include <QtMath>
#include <QTimer>
#include <QDeadlineTimer>
Q_GLOBAL_STATIC(SensorTagBasePrivate, sensortagBasePrivate)
SensorTagBasePrivate::SensorTagBasePrivate(QObject *parent)
: QObject(parent)
{
QTimer::singleShot(50, this, &SensorTagBasePrivate::deviceSearch);
}
void SensorTagBasePrivate::deviceSearch()
{
m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
this, &SensorTagBasePrivate::deviceFound);
connect(m_deviceDiscoveryAgent, QOverload<QBluetoothDeviceDiscoveryAgent::Error>::of(&QBluetoothDeviceDiscoveryAgent::error),
this, &SensorTagBasePrivate::deviceScanError);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
this, &SensorTagBasePrivate::scanFinished);
QTimer::singleShot(20000, this, &SensorTagBasePrivate::deviceSearchTimeout); //make sure to timeout
m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
}
void SensorTagBasePrivate::deviceSearchTimeout()
{
if (m_deviceDiscoveryAgent->isActive() && m_control == nullptr) {
m_deviceDiscoveryAgent->stop();
qWarning("No Sensor Tag devices found");
}
}
void SensorTagBasePrivate::deviceFound(const QBluetoothDeviceInfo &device)
{
if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
const QString idString = QString::fromLatin1(qgetenv("QT_SENSORTAG_ID"));
const QBluetoothAddress watchForAddress(idString);
//mac uses deviceUuid
const QUuid watchForId(idString);
bool ok;
if ((!watchForAddress.isNull() && watchForAddress == device.address()) ||
(!watchForId.isNull() && watchForId == device.deviceUuid())) {
ok = true;
}
if (ok || device.name().contains("SensorTag")) {
m_deviceDiscoveryAgent->stop();
m_control = new QLowEnergyController(device.address(), this);
connect(m_control, &QLowEnergyController::discoveryFinished,
this, &SensorTagBasePrivate::serviceDiscoveryFinished);
connect(m_control, &QLowEnergyController::serviceDiscovered,
this, &SensorTagBasePrivate::serviceDiscovered);
connect(m_control, QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
this, &SensorTagBasePrivate::controllerError);
connect(m_control, &QLowEnergyController::connected,
this, &SensorTagBasePrivate::sensortagDeviceConnected);
connect(m_control, &QLowEnergyController::disconnected,
this, &SensorTagBasePrivate::deviceDisconnected);
m_control->connectToDevice();
}
}
}
void SensorTagBasePrivate::serviceDiscoveryFinished()
{
discoveryDone = true;
}
void SensorTagBasePrivate::scanFinished()
{
if (m_control == nullptr)
qWarning("No Sensor Tag devices found");
}
void SensorTagBasePrivate::deviceScanError(QBluetoothDeviceDiscoveryAgent::Error error)
{
switch (error) {
case QBluetoothDeviceDiscoveryAgent::PoweredOffError:
qWarning("The Bluetooth adaptor is powered off, power it on before doing discovery.");
break;
case QBluetoothDeviceDiscoveryAgent::InputOutputError:
qWarning("Writing or reading from the device resulted in an error.");
break;
default:
qWarning("An unknown error has occurred.");
break;
};
}
void SensorTagBasePrivate::serviceError(QLowEnergyService::ServiceError e)
{
switch (e) {
case QLowEnergyService::DescriptorWriteError:
qWarning("Cannot obtain SensorTag notifications");
break;
default:
case QLowEnergyService::CharacteristicWriteError:
qWarning() << "SensorTag service error:" << e;
break;
};
}
void SensorTagBasePrivate::controllerError(QLowEnergyController::Error error)
{
qWarning("Cannot connect to remote device.");
qWarning() << "Controller Error:" << error;
}
void SensorTagBasePrivate::sensortagDeviceConnected()
{
m_control->discoverServices();
}
void SensorTagBasePrivate::deviceDisconnected()
{
if (q_ptr && q_ptr->sensor()->isActive())
q_ptr->sensorStopped();
}
void SensorTagBasePrivate::serviceDiscovered(const QBluetoothUuid &gatt)
{
if (enabledServiceUuids.contains(gatt)) {
if (gatt == TI_SENSORTAG_LIGHT_SERVICE) {
lightService = m_control->createServiceObject(gatt, this);
doConnections(lightService);
} else if (gatt == TI_SENSORTAG_TEMPERATURE_SERVICE) {
temperatureService = m_control->createServiceObject(gatt, this);
doConnections(temperatureService);
} else if (gatt == TI_SENSORTAG_BAROMETER_SERVICE) {
barometerService = m_control->createServiceObject(gatt, this);
doConnections(barometerService);
} else if (gatt == TI_SENSORTAG_HUMIDTIY_SERVICE) {
humidityService = m_control->createServiceObject(gatt, this);
doConnections(humidityService);
} else if (gatt == TI_SENSORTAG_INFO_SERVICE) {
infoService = m_control->createServiceObject(gatt, this);
doConnections(infoService);
} else if (gatt == TI_SENSORTAG_ACCELEROMETER_SERVICE) {
acceleratorService = m_control->createServiceObject(gatt, this);
doConnections(acceleratorService);
} else if (gatt == TI_SENSORTAG_GYROSCOPE_SERVICE) {
gyroscopeService = m_control->createServiceObject(gatt, this);
doConnections(gyroscopeService);
} else if (gatt == TI_SENSORTAG_MAGNETOMETER_SERVICE) {
magnetometerService = m_control->createServiceObject(gatt, this);
doConnections(magnetometerService);
} else if (movementService == nullptr) {
if (gatt == TI_SENSORTAG_MOVEMENT_SERVICE) {
movementService = m_control->createServiceObject(gatt, this);
doConnections(movementService);
}
}
}
}
void SensorTagBasePrivate::doConnections(QLowEnergyService *service)
{
if (service) {
connect(service, &QLowEnergyService::stateChanged,
this, &SensorTagBasePrivate::serviceStateChanged);
connect(service, &QLowEnergyService::characteristicChanged,
this, &SensorTagBasePrivate::updateCharacteristic);
connect(service,SIGNAL(error(QLowEnergyService::ServiceError)),
this,SLOT(serviceError(QLowEnergyService::ServiceError)));
if (service->state() == QLowEnergyService::DiscoveryRequired) {
service->discoverDetails();
} else if (!enabledServiceUuids.isEmpty()
&& enabledServiceUuids.contains(service->serviceUuid())) {
enableService(service->serviceUuid());
}
}
}
void SensorTagBasePrivate::serviceStateChanged(QLowEnergyService::ServiceState newState)
{
if (newState != QLowEnergyService::ServiceDiscovered)
return;
QLowEnergyService *m_service = qobject_cast<QLowEnergyService *>(sender());
if (!m_service)
return;
if (!enabledServiceUuids.isEmpty()
&& enabledServiceUuids.contains(m_service->serviceUuid())) {
enableService(m_service->serviceUuid());
}
}
void SensorTagBasePrivate::enableLight(bool on)
{
if (!lightService && discoveryDone)
serviceDiscovered(TI_SENSORTAG_LIGHT_SERVICE);
if (!lightService)
return;
const QLowEnergyCharacteristic hrChar = lightService->characteristic(TI_SENSORTAG_LIGHT_CONTROL);
lightService->writeCharacteristic(hrChar, on ? enableSensorCharacteristic : disableSensorCharacteristic);
const QLowEnergyCharacteristic hrChar2 = lightService->characteristic(TI_SENSORTAG_LIGHT_DATA);
if (hrChar2.descriptors().count() > 0) {
const QLowEnergyDescriptor m_notificationDesc = hrChar2.descriptors().at(0);
lightService->writeDescriptor(m_notificationDesc,
on ? enableNotificationsCharacteristic : disableNotificationsCharacteristic);
}
}
void SensorTagBasePrivate::enableTemp(bool on)
{
if (!temperatureService && discoveryDone)
serviceDiscovered(TI_SENSORTAG_TEMPERATURE_SERVICE);
if (!temperatureService)
return;
const QLowEnergyCharacteristic hrChar = temperatureService->characteristic(TI_SENSORTAG_IR_TEMPERATURE_CONTROL);
temperatureService->writeCharacteristic(hrChar,on ? enableSensorCharacteristic : disableSensorCharacteristic);
const QLowEnergyCharacteristic hrChar2 = temperatureService->characteristic(TI_SENSORTAG_IR_TEMPERATURE_DATA);
if (hrChar2.descriptors().count() > 0) {
const QLowEnergyDescriptor m_notificationDesc = hrChar2.descriptors().at(0);
temperatureService->writeDescriptor(m_notificationDesc,
on ? enableNotificationsCharacteristic : disableNotificationsCharacteristic);
}
}
void SensorTagBasePrivate::enablePressure(bool on)
{
if (!barometerService && discoveryDone)
serviceDiscovered(TI_SENSORTAG_BAROMETER_SERVICE);
if (!barometerService)
return;
const QLowEnergyCharacteristic hrChar = barometerService->characteristic(TI_SENSORTAG_BAROMETER_CONTROL);
barometerService->writeCharacteristic(hrChar, on ? enableSensorCharacteristic : disableSensorCharacteristic);
const QLowEnergyCharacteristic hrChar2 = barometerService->characteristic(TI_SENSORTAG_BAROMETER_DATA);
if (hrChar2.descriptors().count() > 0) {
const QLowEnergyDescriptor m_notificationDesc = hrChar2.descriptors().at(0);
barometerService->writeDescriptor(m_notificationDesc,
on ? enableNotificationsCharacteristic : disableNotificationsCharacteristic);
}
}
void SensorTagBasePrivate::enableHumidity(bool on)
{
if (!humidityService && discoveryDone)
serviceDiscovered(TI_SENSORTAG_HUMIDTIY_SERVICE);
if (!humidityService)
return;
const QLowEnergyCharacteristic hrChar = humidityService->characteristic(TI_SENSORTAG_HUMIDTIY_CONTROL);
humidityService->writeCharacteristic(hrChar, on ? enableSensorCharacteristic : disableSensorCharacteristic);
const QLowEnergyCharacteristic hrChar2 = humidityService->characteristic(TI_SENSORTAG_HUMIDTIY_DATA);
if (hrChar2.descriptors().count() > 0) {
const QLowEnergyDescriptor m_notificationDesc = hrChar2.descriptors().at(0);
humidityService->writeDescriptor(m_notificationDesc,
on ? enableNotificationsCharacteristic : disableNotificationsCharacteristic);
}
}
void SensorTagBasePrivate::enableMovement(bool on)
{
if (!movementService && discoveryDone)
serviceDiscovered(TI_SENSORTAG_MOVEMENT_SERVICE);
if (!movementService)
return;
QByteArray controlCharacteristic;
int movementControl = 0;
//movement service has different syntax here
if (on) {
if (gyroscopeEnabled)
movementControl += 7;
if (accelerometerEnabled)
movementControl += 56;
if (magnetometerEnabled)
movementControl += 64;
controlCharacteristic = QByteArray::number(movementControl, 16);
controlCharacteristic.append("04");
} else {
controlCharacteristic = "00";
}
const QLowEnergyCharacteristic hrChar = movementService->characteristic(TI_SENSORTAG_MOVEMENT_CONTROL);
movementService->writeCharacteristic(hrChar, QByteArray::fromHex(controlCharacteristic));
const QLowEnergyCharacteristic hrChar2 = movementService->characteristic(TI_SENSORTAG_MOVEMENT_DATA);
if (hrChar2.descriptors().count() > 0) {
QLowEnergyDescriptor m_notificationDesc = hrChar2.descriptors().at(0);
movementService->writeDescriptor(m_notificationDesc,
on ? enableNotificationsCharacteristic : disableNotificationsCharacteristic);
}
}
void SensorTagBasePrivate::enableService(const QBluetoothUuid &uuid)
{
if (uuid == TI_SENSORTAG_ACCELEROMETER_SERVICE
|| uuid == TI_SENSORTAG_MAGNETOMETER_SERVICE
|| uuid == TI_SENSORTAG_GYROSCOPE_SERVICE) {
if ((uuid != TI_SENSORTAG_MOVEMENT_SERVICE)
&& (accelerometerEnabled || magnetometerEnabled || gyroscopeEnabled))
return;
if (!enabledServiceUuids.contains(TI_SENSORTAG_MOVEMENT_SERVICE))
enabledServiceUuids.append(TI_SENSORTAG_MOVEMENT_SERVICE);
if (uuid == TI_SENSORTAG_ACCELEROMETER_SERVICE)
accelerometerEnabled = true;
else if (uuid == TI_SENSORTAG_MAGNETOMETER_SERVICE)
magnetometerEnabled = true;
else if (uuid == TI_SENSORTAG_GYROSCOPE_SERVICE)
gyroscopeEnabled = true;
} else if (!enabledServiceUuids.contains(uuid))
enabledServiceUuids.append(uuid);
if (discoveryDone) {
if (uuid == TI_SENSORTAG_LIGHT_SERVICE)
enableLight(true);
else if (uuid == TI_SENSORTAG_TEMPERATURE_SERVICE)
enableTemp(true);
else if (uuid == TI_SENSORTAG_BAROMETER_SERVICE)
enablePressure(true);
else if (uuid == TI_SENSORTAG_HUMIDTIY_SERVICE)
enableHumidity(true);
else if (uuid == TI_SENSORTAG_MOVEMENT_SERVICE)
enableMovement(true);
else if (uuid == TI_SENSORTAG_ACCELEROMETER_SERVICE)
enableMovement(true);
else if (uuid == TI_SENSORTAG_MAGNETOMETER_SERVICE)
enableMovement(true);
else if (uuid == TI_SENSORTAG_GYROSCOPE_SERVICE)
enableMovement(true);
}
}
void SensorTagBasePrivate::disableService(const QBluetoothUuid &uuid)
{
enabledServiceUuids.removeOne(uuid);
if (uuid == TI_SENSORTAG_LIGHT_SERVICE) {
enableLight(false);
} else if (uuid == TI_SENSORTAG_TEMPERATURE_SERVICE) {
enableTemp(false);
} else if (uuid == TI_SENSORTAG_BAROMETER_SERVICE) {
enablePressure(false);
} else if (uuid == TI_SENSORTAG_HUMIDTIY_SERVICE) {
enableHumidity(false);
} else if (uuid == TI_SENSORTAG_MOVEMENT_SERVICE) {
enableMovement(false);
} else if (uuid == TI_SENSORTAG_ACCELEROMETER_SERVICE) {
enableMovement(false);
accelerometerEnabled = false;
} else if (uuid == TI_SENSORTAG_MAGNETOMETER_SERVICE) {
enableMovement(false);
magnetometerEnabled = false;
} else if (uuid == TI_SENSORTAG_GYROSCOPE_SERVICE) {
enableMovement(false);
gyroscopeEnabled = false;
}
}
void SensorTagBasePrivate::updateCharacteristic(const QLowEnergyCharacteristic &c,
const QByteArray &value)
{
if (c.uuid() == TI_SENSORTAG_LIGHT_DATA) {
convertLux(value);
} else if (c.uuid()== TI_SENSORTAG_IR_TEMPERATURE_DATA) {
convertTemperature(value);
} else if (c.uuid() == TI_SENSORTAG_BAROMETER_DATA) {
convertBarometer(value);
} else if (c.uuid()== TI_SENSORTAG_HUMIDTIY_DATA) {
convertHumidity(value);
} else if (c.uuid()== TI_SENSORTAG_BAROMETER_DATA) {
convertBarometer(value);
} else if ((c.uuid() == TI_SENSORTAG_ACCELEROMETER_DATA
|| c.uuid() == TI_SENSORTAG_MOVEMENT_DATA) && accelerometerEnabled) {
convertAccelerometer(value);
} else if ((c.uuid() == TI_SENSORTAG_MAGNETOMETER_DATA
|| c.uuid()== TI_SENSORTAG_MOVEMENT_DATA) && magnetometerEnabled) {
convertMagnetometer(value);
} else if ((c.uuid() == TI_SENSORTAG_GYROSCOPE_DATA
|| c.uuid() == TI_SENSORTAG_MOVEMENT_DATA) && gyroscopeEnabled) {
convertGyroscope(value);
}
}
void SensorTagBasePrivate::convertLux(const QByteArray &bytes)
{
if (bytes.size() < 1)
return;
quint16 dat = ((quint16)bytes[1] & 0xFF) << 8;
dat |= (quint16)(bytes[0] & 0xFF);
qreal lux = dat * .01;
emit luxDataAvailable(lux);
}
void SensorTagBasePrivate::convertTemperature(const QByteArray &bytes)
{
if (bytes.size() < 3)
return;
qint16 objTemp = ((bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00));
objTemp >>= 2;
qreal objectTemperature = objTemp * 0.03125;
// change to this if you want to use the ambient/die temp sensor
// qreal ambientTemperature = ambTemp / 128.0;
emit tempDataAvailable(objectTemperature);
}
void SensorTagBasePrivate::convertHumidity(const QByteArray &bytes)
{
if (bytes.size() < 3)
return;
quint16 rawH = (bytes[2] & 0xff) | ((bytes[3] << 8) & 0xff00);
qreal rHumidity = (qreal)(rawH / 65535) * 100.0;
emit humidityDataAvailable(rHumidity);
}
void SensorTagBasePrivate::convertBarometer(const QByteArray &bytes)
{
if (bytes.size() < 5)
return;
quint32 pressure = (bytes[3] & 0xff) | ((bytes[4] << 8) & 0xff00) | ((bytes[5] << 16) & 0xff0000);
qreal mbars = (qreal)pressure / 100.0;
emit pressureDataAvailable(mbars);
}
void SensorTagBasePrivate::convertAccelerometer(const QByteArray &bytes)
{
if (bytes.size() < 3)
return;
int range = 8;
qint16 X = (qint16)((bytes[8]) + ((bytes[9] << 8)));
qint16 Y = (qint16)((bytes[6]) + ((bytes[7] << 8)));
qint16 Z = (qint16)((bytes[10]) + ((bytes[11] << 8)));
accelReading.setX((qreal)(X * 1.0) / (32768 / range) * 9.80665);
accelReading.setY(-(qreal)(Y * 1.0) / (32768 / range) * 9.80665);
accelReading.setZ((qreal)(Z * 1.0) / (32768 / range) * 9.80665);
// TODO needs calibration
emit accelDataAvailable(accelReading);
}
void SensorTagBasePrivate::convertMagnetometer(const QByteArray &bytes)
{
if (bytes.size() < 3)
return;
qreal scale = 6.67100977199; // 32768 / 4912;
qint16 X = (qint16)((bytes[12]) + ((bytes[13] << 8)));
qint16 Y = (qint16)((bytes[14]) + ((bytes[15] << 8)));
qint16 Z = (qint16)((bytes[16]) + ((bytes[17] << 8)));
// TODO needs calibration
magReading.setX((qreal)(X / scale));
magReading.setY((qreal)(Y / scale));
magReading.setZ((qreal)(Z / scale));
emit magDataAvailable(magReading);
}
void SensorTagBasePrivate::convertGyroscope(const QByteArray &bytes)
{
if (bytes.size() < 3)
return;
qreal scale = 128.0;
qint16 X = (qint16)((bytes[2]) + ((bytes[3] << 8)));
qint16 Y = (qint16)((bytes[0]) + ((bytes[1] << 8)));
qint16 Z = (qint16)((bytes[4]) + ((bytes[5] << 8)));
gyroReading.setX((qreal)(X / scale));
gyroReading.setY((qreal)(Y / scale));
gyroReading.setZ((qreal)(Z / scale));
emit gyroDataAvailable(gyroReading);
}
SensorTagBasePrivate * SensorTagBasePrivate::instance()
{
SensorTagBasePrivate *priv = sensortagBasePrivate();
return priv;
}
SensorTagBase::SensorTagBase(QSensor *sensor)
: QSensorBackend(sensor),
leService(nullptr),
serviceId(nullptr),
d_ptr(SensorTagBasePrivate::instance())
{
connect(d_ptr, &SensorTagBasePrivate::luxDataAvailable,
this, &SensorTagBase::luxDataAvailable);
connect(d_ptr, &SensorTagBasePrivate::tempDataAvailable,
this, &SensorTagBase::tempDataAvailable);
connect(d_ptr, &SensorTagBasePrivate::humidityDataAvailable,
this, &SensorTagBase::humidityDataAvailable);
connect(d_ptr, &SensorTagBasePrivate::pressureDataAvailable,
this, &SensorTagBase::pressureDataAvailable);
connect(d_ptr, &SensorTagBasePrivate::accelDataAvailable,
this, &SensorTagBase::accelDataAvailable);
connect(d_ptr, &SensorTagBasePrivate::gyroDataAvailable,
this, &SensorTagBase::gyroDataAvailable);
connect(d_ptr, &SensorTagBasePrivate::magDataAvailable,
this, &SensorTagBase::magDataAvailable);
}
SensorTagBase::~SensorTagBase()
{
}
void SensorTagBase::start()
{
}
void SensorTagBase::stop()
{
}
quint64 SensorTagBase::produceTimestamp()
{
return QDeadlineTimer::current().deadlineNSecs() / 1000;
}