diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 88d46e97..744ea279 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -39,6 +39,7 @@ PLUGIN_DIRS = \ snapd \ tasmota \ tcpcommander \ + texasinstruments \ udpcommander \ unipi \ unitec \ diff --git a/texasinstruments/deviceplugintexasinstruments.cpp b/texasinstruments/deviceplugintexasinstruments.cpp new file mode 100644 index 00000000..c48f431b --- /dev/null +++ b/texasinstruments/deviceplugintexasinstruments.cpp @@ -0,0 +1,255 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \page texasinstruments.html + \title Texas Instruments + \brief Plugin for Texas Instruments Devices. + + \ingroup plugins + \ingroup nymea-plugins + + This plugin supports devices from Texas Instruments. + + Currently supported devices: + * TI SensorTag (CC2650). + + \chapter TI SensorTag (CC2650) + + Using Bluetooth-LE, the TI SensorTag device class allows nymea to interact with those sensors. All supported + sensors of the device are supported: + * Temperature + * IR Temperature + * Humidity + * Pressure + * Light intensity + * Motion + * Magnetic Objects + Besides reading the sensor values, the buttons, buzzer and LEDs can be read and/or controlled. + + \chapter Plugin defintion + + \quotefile plugins/deviceplugins/multisensor/deviceplugintexasinstruments.json + + \sa {The plugin JSON File} + +*/ + +#include "deviceplugintexasinstruments.h" +#include "plugininfo.h" +#include "sensortag.h" + +#include "hardware/bluetoothlowenergy/bluetoothlowenergymanager.h" +#include "plugintimer.h" + +#include + +DevicePluginTexasInstruments::DevicePluginTexasInstruments(QObject *parent) : DevicePlugin(parent) +{ + +} + +DevicePluginTexasInstruments::~DevicePluginTexasInstruments() +{ + +} + +DeviceManager::DeviceError DevicePluginTexasInstruments::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) +{ + Q_UNUSED(params) + Q_ASSERT_X(deviceClassId == sensorTagDeviceClassId, "DevicePluginTexasInstruments", "Unhandled DeviceClassId!"); + + if (!hardwareManager()->bluetoothLowEnergyManager()->available() || !hardwareManager()->bluetoothLowEnergyManager()->enabled()) { + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + BluetoothDiscoveryReply *reply = hardwareManager()->bluetoothLowEnergyManager()->discoverDevices(); + connect(reply, &BluetoothDiscoveryReply::finished, this, [this, reply](){ + reply->deleteLater(); + + if (reply->error() != BluetoothDiscoveryReply::BluetoothDiscoveryReplyErrorNoError) { + qCWarning(dcTexasInstruments()) << "Bluetooth discovery error:" << reply->error(); + emit devicesDiscovered(sensorTagDeviceClassId, QList()); + return; + } + + QList deviceDescriptors; + foreach (const QBluetoothDeviceInfo &deviceInfo, reply->discoveredDevices()) { + + if (deviceInfo.name().contains("SensorTag")) { + + DeviceDescriptor descriptor(sensorTagDeviceClassId, "Sensor Tag", deviceInfo.address().toString()); + + Devices existingDevice = myDevices().filterByParam(sensorTagDeviceMacParamTypeId, deviceInfo.address().toString()); + if (!existingDevice.isEmpty()) { + descriptor.setDeviceId(existingDevice.first()->id()); + } + + ParamList params; + params.append(Param(sensorTagDeviceMacParamTypeId, deviceInfo.address().toString())); + foreach (Device *existingDevice, myDevices()) { + if (existingDevice->paramValue(sensorTagDeviceMacParamTypeId).toString() == deviceInfo.address().toString()) { + descriptor.setDeviceId(existingDevice->id()); + break; + } + } + descriptor.setParams(params); + deviceDescriptors.append(descriptor); + } + } + + emit devicesDiscovered(sensorTagDeviceClassId, deviceDescriptors); + }); + return DeviceManager::DeviceErrorAsync; +} + +DeviceManager::DeviceSetupStatus DevicePluginTexasInstruments::setupDevice(Device *device) +{ + qCDebug(dcTexasInstruments()) << "Setting up Multi Sensor" << device->name() << device->params(); + + QBluetoothAddress address = QBluetoothAddress(device->paramValue(sensorTagDeviceMacParamTypeId).toString()); + QBluetoothDeviceInfo deviceInfo = QBluetoothDeviceInfo(address, device->name(), 0); + + BluetoothLowEnergyDevice *bluetoothDevice = hardwareManager()->bluetoothLowEnergyManager()->registerDevice(deviceInfo, QLowEnergyController::PublicAddress); + + SensorTag *sensorTag = new SensorTag(device, bluetoothDevice, this); + m_sensorTags.insert(device, sensorTag); + + if (!m_reconnectTimer) { + m_reconnectTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_reconnectTimer, &PluginTimer::timeout, this, [this](){ + foreach (SensorTag *sensorTag, m_sensorTags) { + if (!sensorTag->bluetoothDevice()->connected()) { + sensorTag->bluetoothDevice()->connectDevice(); + } + } + }); + } + + return DeviceManager::DeviceSetupStatusSuccess; +} + +void DevicePluginTexasInstruments::postSetupDevice(Device *device) +{ + // Try to connect right after setup + SensorTag *sensorTag = m_sensorTags.value(device); + + // Configure sensor with state configurations + sensorTag->setTemperatureSensorEnabled(device->stateValue(sensorTagTemperatureSensorEnabledStateTypeId).toBool()); + sensorTag->setHumiditySensorEnabled(device->stateValue(sensorTagHumiditySensorEnabledStateTypeId).toBool()); + sensorTag->setPressureSensorEnabled(device->stateValue(sensorTagPressureSensorEnabledStateTypeId).toBool()); + sensorTag->setOpticalSensorEnabled(device->stateValue(sensorTagOpticalSensorEnabledStateTypeId).toBool()); + sensorTag->setAccelerometerEnabled(device->stateValue(sensorTagAccelerometerEnabledStateTypeId).toBool()); + sensorTag->setGyroscopeEnabled(device->stateValue(sensorTagGyroscopeEnabledStateTypeId).toBool()); + sensorTag->setMagnetometerEnabled(device->stateValue(sensorTagMagnetometerEnabledStateTypeId).toBool()); + sensorTag->setMeasurementPeriod(device->stateValue(sensorTagMeasurementPeriodStateTypeId).toInt()); + sensorTag->setMeasurementPeriodMovement(device->stateValue(sensorTagMeasurementPeriodMovementStateTypeId).toInt()); + + // Connect to the sensor + sensorTag->bluetoothDevice()->connectDevice(); +} + +void DevicePluginTexasInstruments::deviceRemoved(Device *device) +{ + if (!m_sensorTags.contains(device)) { + return; + } + + SensorTag *sensorTag = m_sensorTags.take(device); + hardwareManager()->bluetoothLowEnergyManager()->unregisterDevice(sensorTag->bluetoothDevice()); + sensorTag->deleteLater(); + + if (myDevices().isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer); + m_reconnectTimer = nullptr; + } +} + +DeviceManager::DeviceError DevicePluginTexasInstruments::executeAction(Device *device, const Action &action) +{ + SensorTag *sensorTag = m_sensorTags.value(device); + if (action.actionTypeId() == sensorTagBuzzerActionTypeId) { + sensorTag->setBuzzerPower(action.param(sensorTagBuzzerActionBuzzerParamTypeId).value().toBool()); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagGreenLedActionTypeId) { + sensorTag->setGreenLedPower(action.param(sensorTagGreenLedActionGreenLedParamTypeId).value().toBool()); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagRedLedActionTypeId) { + sensorTag->setRedLedPower(action.param(sensorTagRedLedActionRedLedParamTypeId).value().toBool()); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagBuzzerImpulseActionTypeId) { + sensorTag->buzzerImpulse(); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagTemperatureSensorEnabledActionTypeId) { + bool enabled = action.param(sensorTagTemperatureSensorEnabledActionTemperatureSensorEnabledParamTypeId).value().toBool(); + device->setStateValue(sensorTagTemperatureSensorEnabledStateTypeId, enabled); + sensorTag->setTemperatureSensorEnabled(enabled); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagHumiditySensorEnabledActionTypeId) { + bool enabled = action.param(sensorTagHumiditySensorEnabledActionHumiditySensorEnabledParamTypeId).value().toBool(); + device->setStateValue(sensorTagHumiditySensorEnabledStateTypeId, enabled); + sensorTag->setHumiditySensorEnabled(enabled); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagPressureSensorEnabledActionTypeId) { + bool enabled = action.param(sensorTagPressureSensorEnabledActionPressureSensorEnabledParamTypeId).value().toBool(); + device->setStateValue(sensorTagPressureSensorEnabledStateTypeId, enabled); + sensorTag->setPressureSensorEnabled(enabled); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagOpticalSensorEnabledActionTypeId) { + bool enabled = action.param(sensorTagOpticalSensorEnabledActionOpticalSensorEnabledParamTypeId).value().toBool(); + device->setStateValue(sensorTagOpticalSensorEnabledStateTypeId, enabled); + sensorTag->setOpticalSensorEnabled(enabled); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagAccelerometerEnabledActionTypeId) { + bool enabled = action.param(sensorTagAccelerometerEnabledActionAccelerometerEnabledParamTypeId).value().toBool(); + device->setStateValue(sensorTagAccelerometerEnabledStateTypeId, enabled); + sensorTag->setAccelerometerEnabled(enabled); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagGyroscopeEnabledActionTypeId) { + bool enabled = action.param(sensorTagGyroscopeEnabledActionGyroscopeEnabledParamTypeId).value().toBool(); + device->setStateValue(sensorTagGyroscopeEnabledStateTypeId, enabled); + sensorTag->setGyroscopeEnabled(enabled); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagMagnetometerEnabledActionTypeId) { + bool enabled = action.param(sensorTagMagnetometerEnabledActionMagnetometerEnabledParamTypeId).value().toBool(); + device->setStateValue(sensorTagMagnetometerEnabledStateTypeId, enabled); + sensorTag->setMagnetometerEnabled(enabled); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagMeasurementPeriodActionTypeId) { + int period = action.param(sensorTagMeasurementPeriodActionMeasurementPeriodParamTypeId).value().toInt(); + device->setStateValue(sensorTagMeasurementPeriodStateTypeId, period); + sensorTag->setMeasurementPeriod(period); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagMeasurementPeriodMovementActionTypeId) { + int period = action.param(sensorTagMeasurementPeriodMovementActionMeasurementPeriodMovementParamTypeId).value().toInt(); + device->setStateValue(sensorTagMeasurementPeriodMovementStateTypeId, period); + sensorTag->setMeasurementPeriodMovement(period); + return DeviceManager::DeviceErrorNoError; + } else if (action.actionTypeId() == sensorTagMovementSensitivityActionTypeId) { + int sensitivity = action.param(sensorTagMovementSensitivityActionMovementSensitivityParamTypeId).value().toInt(); + device->setStateValue(sensorTagMovementSensitivityStateTypeId, sensitivity); + sensorTag->setMovementSensitivity(sensitivity); + return DeviceManager::DeviceErrorNoError; + } + + return DeviceManager::DeviceErrorActionTypeNotFound; +} diff --git a/texasinstruments/deviceplugintexasinstruments.h b/texasinstruments/deviceplugintexasinstruments.h new file mode 100644 index 00000000..ce6347f9 --- /dev/null +++ b/texasinstruments/deviceplugintexasinstruments.h @@ -0,0 +1,56 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * 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 DEVICEPLUGINTEXASINSTRUMENTS_H +#define DEVICEPLUGINTEXASINSTRUMENTS_H + +#include + +#include "plugin/deviceplugin.h" + +class SensorTag; + +class PluginTimer; + +class DevicePluginTexasInstruments : public DevicePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "deviceplugintexasinstruments.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginTexasInstruments(QObject *parent = nullptr); + ~DevicePluginTexasInstruments() override; + + DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + void postSetupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + +private: + QHash m_sensorTags; + + PluginTimer *m_reconnectTimer = nullptr; +}; + +#endif // DEVICEPLUGINTEXASINSTRUMENTS_H diff --git a/texasinstruments/deviceplugintexasinstruments.json b/texasinstruments/deviceplugintexasinstruments.json new file mode 100644 index 00000000..f61ef12f --- /dev/null +++ b/texasinstruments/deviceplugintexasinstruments.json @@ -0,0 +1,273 @@ +{ + "displayName": "Texas Instruments", + "name": "TexasInstruments", + "id": "ae550a91-e734-4331-9d71-9f37df0b0fa6", + "vendors": [ + { + "id": "2edf543e-dc2c-4693-bb0c-e76c0d305fad", + "name": "texasInstruments", + "displayName": "Texas Instruments", + "deviceClasses": [ + { + "id": "158a06b6-b27f-4951-957e-6f1e3b44f604", + "name": "sensorTag", + "displayName": "Sensor Tag (CC2650)", + "createMethods": ["discovery"], + "interfaces": ["temperaturesensor", "humiditysensor", "pressuresensor", "lightsensor", "connectable"], + "paramTypes": [ + { + "id": "d51ed68e-c84a-4136-a5b5-be2f95fd5a0f", + "name": "mac", + "displayName": "MAC address", + "type": "QString", + "inputType": "MacAddress" + } + ], + "stateTypes": [ + { + "id": "a9629b11-0f34-47f0-a0f0-f758a6aec2b4", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "8359ada9-df1c-4e60-bb87-9e21d05ee2e2", + "name": "temperature", + "displayName": "temperature", + "displayNameEvent": "temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "id": "c664e9ec-a045-49ba-add1-1642ceba7c4f", + "name": "objectTemperature", + "displayName": "Object temperature", + "displayNameEvent": "Object temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "id": "e83a50ff-96c9-4b6d-889f-f4238353e794", + "name": "humidity", + "displayName": "Humidity", + "displayNameEvent": "Humidity changed", + "type": "double", + "minValue": 0, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": 0 + }, + { + "id": "645633ad-77d4-45b2-8be8-d6ca7a12eb7a", + "name": "pressure", + "displayName": "Barometric pressure", + "displayNameEvent": "Barometric pressure changed", + "type": "double", + "unit": "HectoPascal", + "defaultValue": 0 + }, + { + "id": "6635dce4-2d8d-4608-a836-768c3014f356", + "name": "lightIntensity", + "displayName": "Light intensity", + "displayNameEvent": "Light intensity changed", + "type": "double", + "unit": "Lux", + "defaultValue": 0 + }, + { + "id": "4be5ca26-0565-419d-b18b-2a5a385d2a3d", + "name": "moving", + "displayName": "Moving", + "displayNameEvent": "Moving changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "758d9b39-7390-40f5-8e19-d8b0f4a0a0c6", + "name": "magnetDetected", + "displayName": "Magnet detected", + "displayNameEvent": "Magnet detected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "8995e49e-ca2d-4dd9-a22f-de6c566c2115", + "name": "leftButtonPressed", + "displayName": "Left button pressed", + "displayNameEvent": "Left button pressed changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "ef8eedc5-6a45-4dfb-bb55-ada1a931b20b", + "name": "rightButtonPressed", + "displayName": "Right button pressed", + "displayNameEvent": "Right button pressed changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "8d692132-f950-486f-b3dd-fe1ebc574e1d", + "name": "greenLed", + "displayName": "Green LED", + "displayNameEvent": "Green LED power changed", + "displayNameAction": "Set green LED power", + "type": "bool", + "cached": false, + "defaultValue": false, + "writable": true + }, + { + "id": "bb585725-6393-42de-aea2-ea4f525ad348", + "name": "redLed", + "displayName": "Red LED", + "displayNameEvent": "Red LED power changed", + "displayNameAction": "Set red LED power", + "type": "bool", + "cached": false, + "defaultValue": false, + "writable": true + }, + { + "id": "d522c536-4427-4c53-9980-7820b2649aad", + "name": "buzzer", + "displayName": "Buzzer", + "displayNameEvent": "Buzzer power changed", + "displayNameAction": "Set buzzer power", + "type": "bool", + "cached": false, + "defaultValue": false, + "writable": true + }, + { + "id": "6f0e40a2-0f97-4c1e-8229-757e3c18b345", + "name": "temperatureSensorEnabled", + "displayName": "Temperature sensor enabled", + "displayNameEvent": "Temperature sensor enabled changed", + "displayNameAction": "Set temperature sensor enabled", + "type": "bool", + "defaultValue": true, + "writable": true + }, + { + "id": "29881172-626a-42d5-83b8-3e2e5ca533be", + "name": "humiditySensorEnabled", + "displayName": "Humidity sensor enabled", + "displayNameEvent": "Humidity sensor enabled changed", + "displayNameAction": "Set humidity sensor enabled", + "type": "bool", + "defaultValue": true, + "writable": true + }, + { + "id": "50e5e282-9707-4b31-bb3f-a6ca30a7e1ff", + "name": "pressureSensorEnabled", + "displayName": "Pressure sensor enabled", + "displayNameEvent": "Pressure sensor enabled changed", + "displayNameAction": "Set pressure sensor enabled", + "type": "bool", + "defaultValue": true, + "writable": true + }, + { + "id": "1460a6d6-9fb4-4385-b27b-ee4b7594e454", + "name": "opticalSensorEnabled", + "displayName": "Optical sensor enabled", + "displayNameEvent": "Optical sensor enabled changed", + "displayNameAction": "Set optical sensor enabled", + "type": "bool", + "defaultValue": true, + "writable": true + }, + { + "id": "5786c91a-d94d-461a-8d22-f978dd1438ab", + "name": "accelerometerEnabled", + "displayName": "Accelerometer enabled", + "displayNameEvent": "Accelerometer enabled changed", + "displayNameAction": "Set accelerometer enabled", + "type": "bool", + "defaultValue": true, + "writable": true + }, + { + "id": "94517544-cb97-4816-8993-cb4cf2651a1e", + "name": "gyroscopeEnabled", + "displayName": "Gyroscope enabled", + "displayNameEvent": "Gyroscope enabled changed", + "displayNameAction": "Set gyroscope enabled", + "type": "bool", + "defaultValue": true, + "writable": true + }, + { + "id": "96aae111-b1c1-48a1-9b1f-b56efa546d0d", + "name": "magnetometerEnabled", + "displayName": "Magnetometer enabled", + "displayNameEvent": "Magnetometer enabled changed", + "displayNameAction": "Set magnetometer enabled", + "type": "bool", + "defaultValue": true, + "writable": true + }, + { + "id": "c5308565-5dc9-409e-ae99-577c212c7a92", + "name": "measurementPeriod", + "displayName": "Measurement period for enviromental sensors", + "displayNameEvent": "Measurement period for enviromental sensors changed", + "displayNameAction": "Set measurement period for enviromental sensors", + "type": "int", + "minValue": 10, + "maxValue": 2500, + "defaultValue": 2000, + "writable": true + }, + { + "id": "5237c89c-c21d-4d78-ac99-8be661b40da7", + "name": "measurementPeriodMovement", + "displayName": "Measurement period movement sensor", + "displayNameEvent": "Measurement period movement sensor changed", + "displayNameAction": "Set measurement period movement sensor", + "type": "int", + "minValue": 10, + "maxValue": 2500, + "defaultValue": 300, + "writable": true + }, + { + "id": "a3298d16-eea6-4474-9061-90466e92d476", + "name": "movementSensitivity", + "displayName": "Movement sensitivity", + "displayNameEvent": "Movement sensitivity changed", + "displayNameAction": "Set movement sensitivity", + "type": "int", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 40, + "writable": true + } + ], + "actionTypes": [ + { + "id": "a048ab2e-4f17-4467-a166-a7572156c07e", + "name": "buzzerImpulse", + "displayName": "Buzzer impulse" + } + ] + } + ] + + } + + ] +} diff --git a/texasinstruments/sensordataprocessor.cpp b/texasinstruments/sensordataprocessor.cpp new file mode 100644 index 00000000..fa0cd2ec --- /dev/null +++ b/texasinstruments/sensordataprocessor.cpp @@ -0,0 +1,329 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Simon Stuerz * + * * + * 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 "sensordataprocessor.h" +#include "extern-plugininfo.h" +#include "math.h" + +#include +#include +#include +#include + +SensorDataProcessor::SensorDataProcessor(Device *device, QObject *parent) : + QObject(parent), + m_device(device) +{ + // Create data filters + m_temperatureFilter = new SensorFilter(SensorFilter::TypeLowPass, this); + m_temperatureFilter->setLowPassAlpha(0.1); + m_temperatureFilter->setFilterWindowSize(30); + + m_objectTemperatureFilter = new SensorFilter(SensorFilter::TypeLowPass, this); + m_objectTemperatureFilter->setLowPassAlpha(0.4); + m_objectTemperatureFilter->setFilterWindowSize(20); + + m_humidityFilter = new SensorFilter(SensorFilter::TypeLowPass, this); + m_humidityFilter->setLowPassAlpha(0.1); + m_humidityFilter->setFilterWindowSize(30); + + m_pressureFilter = new SensorFilter(SensorFilter::TypeLowPass, this); + m_pressureFilter->setLowPassAlpha(0.1); + m_pressureFilter->setFilterWindowSize(30); + + m_opticalFilter = new SensorFilter(SensorFilter::TypeLowPass, this); + m_opticalFilter->setLowPassAlpha(0.01); + m_opticalFilter->setFilterWindowSize(10); + + m_accelerometerFilter = new SensorFilter(SensorFilter::TypeLowPass, this); + m_accelerometerFilter->setLowPassAlpha(0.6); + m_accelerometerFilter->setFilterWindowSize(40); + + // Check if the data should be logged + if (m_filterDebug) { + m_logFile = new QFile("/tmp/multisensor.log", this); + if (!m_logFile->open(QIODevice::Append | QIODevice::Text)) { + qCWarning(dcTexasInstruments()) << "Could not open log file" << m_logFile->fileName(); + delete m_logFile; + m_logFile = nullptr; + } + } +} + +SensorDataProcessor::~SensorDataProcessor() +{ + if (m_logFile) { + m_logFile->close(); + } +} + +void SensorDataProcessor::setAccelerometerRange(int accelerometerRange) +{ + m_accelerometerRange = accelerometerRange; +} + +void SensorDataProcessor::setMovementSensitivity(int movementSensitivity) +{ + m_movementSensitivity = movementSensitivity; +} + +double SensorDataProcessor::roundValue(float value) +{ + int tmpValue = static_cast(value * 10); + return static_cast(tmpValue) / 10.0; +} + +bool SensorDataProcessor::testBitUint8(quint8 value, int bitPosition) +{ + return (((value)>> (bitPosition)) & 1); +} + +void SensorDataProcessor::processTemperatureData(const QByteArray &data) +{ + Q_ASSERT(data.count() == 4); + + quint16 rawObjectTemperature = 0; + quint16 rawAmbientTemperature = 0; + + QByteArray payload(data); + QDataStream stream(&payload, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream >> rawObjectTemperature >> rawAmbientTemperature ; + + float scaleFactor = 0.03125; + float objectTemperature = static_cast(rawObjectTemperature) / 4 * scaleFactor; + float ambientTemperature = static_cast(rawAmbientTemperature) / 4 * scaleFactor; + + //qCDebug(dcTexasInstruments()) << "Temperature value" << data.toHex(); + //qCDebug(dcTexasInstruments()) << "Object temperature" << roundValue(objectTemperature) << "°C"; + //qCDebug(dcTexasInstruments()) << "Ambient temperature" << roundValue(ambientTemperature) << "°C"; + + float objectTemperatureFiltered = m_objectTemperatureFilter->filterValue(objectTemperature); + float ambientTemperatureFiltered = m_temperatureFilter->filterValue(ambientTemperature); + + if (m_objectTemperatureFilter->isReady()) { + m_device->setStateValue(sensorTagObjectTemperatureStateTypeId, roundValue(objectTemperatureFiltered)); + } + + // Note: only change the state once the filter has collected enough data + if (m_temperatureFilter->isReady()) { + m_device->setStateValue(sensorTagTemperatureStateTypeId, roundValue(ambientTemperatureFiltered)); + } + +} + +void SensorDataProcessor::processKeyData(const QByteArray &data) +{ + Q_ASSERT(data.count() == 1); + quint8 flags = static_cast(data.at(0)); + setLeftButtonPressed(testBitUint8(flags, 0)); + setRightButtonPressed(testBitUint8(flags, 1)); + setMagnetDetected(testBitUint8(flags, 2)); +} + +void SensorDataProcessor::processHumidityData(const QByteArray &data) +{ + Q_ASSERT(data.count() == 4); + quint16 rawHumidityTemperature = 0; + quint16 rawHumidity = 0; + + QByteArray payload(data); + QDataStream stream(&payload, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream >> rawHumidityTemperature >> rawHumidity ; + + // Note: we don't need the temperature measurement from the humidity sensor + //double humidityTemperature = (rawHumidityTemperature / 65536.0 * 165.0) - 40; + double humidity = rawHumidity / 65536.0 * 100.0; + double humidityFiltered = m_humidityFilter->filterValue(humidity); + + if (m_humidityFilter->isReady()) { + m_device->setStateValue(sensorTagHumidityStateTypeId, roundValue(humidityFiltered)); + } +} + +void SensorDataProcessor::processPressureData(const QByteArray &data) +{ + Q_ASSERT(data.count() == 6); + + QByteArray temperatureData(data.left(3)); + quint32 rawTemperature = static_cast(temperatureData.at(2)); + rawTemperature <<= 8; + rawTemperature |= static_cast(temperatureData.at(1)); + rawTemperature <<= 8; + rawTemperature |= static_cast(temperatureData.at(0)); + + QByteArray pressureData(data.right(3)); + quint32 rawPressure = static_cast(pressureData.at(2)); + rawPressure <<= 8; + rawPressure |= static_cast(pressureData.at(1)); + rawPressure <<= 8; + rawPressure |= static_cast(pressureData.at(0)); + + // Note: we don't need the temperature measurement from the barometic pressure sensor + //qCDebug(dcTexasInstruments()) << "Pressure temperature:" << roundValue(rawTemperature / 100.0) << "°C"; + //qCDebug(dcTexasInstruments()) << "Pressure:" << roundValue(rawPressure / 100.0) << "mBar"; + + double pressureFiltered = m_pressureFilter->filterValue(rawPressure / 100.0); + if (m_pressureFilter->isReady()) { + m_device->setStateValue(sensorTagPressureStateTypeId, roundValue(pressureFiltered)); + } +} + +void SensorDataProcessor::processOpticalData(const QByteArray &data) +{ + Q_ASSERT(data.count() == 2); + + quint16 rawOptical = 0; + QByteArray payload(data); + QDataStream stream(&payload, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream >> rawOptical; + + quint16 lumm = rawOptical & 0x0FFF; + quint16 lume = (rawOptical & 0xF000) >> 12; + + double lux = lumm * (0.01 * pow(2,lume)); + + //qCDebug(dcTexasInstruments()) << "Lux:" << lux; + + double luxFiltered = m_opticalFilter->filterValue(lux); + if (m_opticalFilter->isReady()) { + m_device->setStateValue(sensorTagLightIntensityStateTypeId, qRound(luxFiltered)); + } + + logSensorValue(lux, qRound(luxFiltered)); +} + +void SensorDataProcessor::processMovementData(const QByteArray &data) +{ + //qCDebug(dcTexasInstruments()) << "--> Movement value" << data.toHex(); + + QByteArray payload(data); + QDataStream stream(&payload, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + + qint16 gyroXRaw = 0; qint16 gyroYRaw = 0; qint16 gyroZRaw = 0; + stream >> gyroXRaw >> gyroYRaw >> gyroZRaw; + + qint16 accXRaw = 0; qint16 accYRaw = 0; qint16 accZRaw = 0; + stream >> accXRaw >> accYRaw >> accZRaw; + + qint16 magXRaw = 0; qint16 magYRaw = 0; qint16 magZRaw = 0; + stream >> magXRaw >> magYRaw >> magZRaw; + + + // Calculate rotation [deg/s], Range +- 250 + float gyroX = static_cast(gyroXRaw) / (65536 / 500); + float gyroY = static_cast(gyroYRaw) / (65536 / 500); + float gyroZ = static_cast(gyroZRaw) / (65536 / 500); + + // Calculate acceleration [G], Range +- m_accelerometerRange + float accX = static_cast(accXRaw) / (32768 / static_cast(m_accelerometerRange)); + float accY = static_cast(accYRaw) / (32768 / static_cast(m_accelerometerRange)); + float accZ = static_cast(accZRaw) / (32768 / static_cast(m_accelerometerRange)); + + // Calculate magnetism [uT], Range +- 4900 + float magX = static_cast(magXRaw); + float magY = static_cast(magYRaw); + float magZ = static_cast(magZRaw); + + + //qCDebug(dcTexasInstruments()) << "Accelerometer x:" << accX << " y:" << accY << " z:" << accZ; + //qCDebug(dcTexasInstruments()) << "Gyroscope x:" << gyroX << " y:" << gyroY << " z:" << gyroZ; + //qCDebug(dcTexasInstruments()) << "Magnetometer x:" << magX << " y:" << magY << " z:" << magZ; + + QVector3D accelerometerVector(accX, accY, accZ); + QVector3D gyroscopeVector(gyroX, gyroY, gyroZ); + QVector3D magnetometerVector(magX, magY, magZ); + + Q_UNUSED(gyroscopeVector) + Q_UNUSED(magnetometerVector) + + double filteredVectorLength = m_accelerometerFilter->filterValue(accelerometerVector.length()); + + // Initialize the accelerometer value if no data known yet + if (m_lastAccelerometerVectorLenght == -99999) { + m_lastAccelerometerVectorLenght = filteredVectorLength; + return; + } + + double delta = qAbs(qAbs(m_lastAccelerometerVectorLenght) - qAbs(filteredVectorLength)); + bool motionDetected = (delta >= m_movementSensitivity); + m_device->setStateValue(sensorTagMovingStateTypeId, motionDetected); + m_lastAccelerometerVectorLenght = filteredVectorLength; +} + +void SensorDataProcessor::reset() +{ + m_lastAccelerometerVectorLenght = -99999; + + // Reset data filters + m_temperatureFilter->reset(); + m_objectTemperatureFilter->reset(); + m_humidityFilter->reset(); + m_pressureFilter->reset(); + m_opticalFilter->reset(); + m_accelerometerFilter->reset(); +} + +void SensorDataProcessor::setLeftButtonPressed(bool pressed) +{ + if (m_leftButtonPressed == pressed) + return; + + qCDebug(dcTexasInstruments()) << "Left button" << (pressed ? "pressed" : "released"); + m_leftButtonPressed = pressed; + m_device->setStateValue(sensorTagLeftButtonPressedStateTypeId, m_leftButtonPressed); +} + +void SensorDataProcessor::setRightButtonPressed(bool pressed) +{ + if (m_rightButtonPressed == pressed) + return; + + qCDebug(dcTexasInstruments()) << "Right button" << (pressed ? "pressed" : "released"); + m_rightButtonPressed = pressed; + m_device->setStateValue(sensorTagRightButtonPressedStateTypeId, m_rightButtonPressed); +} + +void SensorDataProcessor::setMagnetDetected(bool detected) +{ + if (m_magnetDetected == detected) + return; + + qCDebug(dcTexasInstruments()) << "Magnet detector" << (detected ? "active" : "inactive"); + m_magnetDetected = detected; + m_device->setStateValue(sensorTagMagnetDetectedStateTypeId, m_magnetDetected); +} + +void SensorDataProcessor::logSensorValue(double originalValue, double filteredValue) +{ + if (!m_filterDebug || !m_logFile) + return; + + QString logLine = QString("%1 %2 %3\n").arg(QDateTime::currentDateTime().toTime_t()).arg(originalValue).arg(filteredValue); + + QTextStream logStream(m_logFile); + logStream.setCodec("UTF-8"); + logStream << logLine; +} diff --git a/texasinstruments/sensordataprocessor.h b/texasinstruments/sensordataprocessor.h new file mode 100644 index 00000000..2a923f12 --- /dev/null +++ b/texasinstruments/sensordataprocessor.h @@ -0,0 +1,93 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Simon Stuerz * + * * + * 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 SENSORDATAPROCESSOR_H +#define SENSORDATAPROCESSOR_H + +#include +#include + +#include "plugin/device.h" +#include "extern-plugininfo.h" + +#include "sensorfilter.h" + +class SensorDataProcessor : public QObject +{ + Q_OBJECT +public: + explicit SensorDataProcessor(Device *device, QObject *parent = nullptr); + ~SensorDataProcessor(); + + void setAccelerometerRange(int accelerometerRange); + void setMovementSensitivity(int movementSensitivity); + + static double roundValue(float value); + static bool testBitUint8(quint8 value, int bitPosition); + + void processTemperatureData(const QByteArray &data); + void processKeyData(const QByteArray &data); + void processHumidityData(const QByteArray &data); + void processPressureData(const QByteArray &data); + void processOpticalData(const QByteArray &data); + void processMovementData(const QByteArray &data); + + void reset(); + +private: + Device *m_device = nullptr; + double m_lastAccelerometerVectorLenght = -99999; + + int m_accelerometerRange = 16; + double m_movementSensitivity = 0.5; + + bool m_leftButtonPressed = false; + bool m_rightButtonPressed = false; + bool m_magnetDetected = false; + + // Log sensor data for debugging filters + // Note: set this to true for enable sensor filter logging + bool m_filterDebug = true; + QFile *m_logFile = nullptr; + + SensorFilter *m_temperatureFilter = nullptr; + SensorFilter *m_objectTemperatureFilter = nullptr; + SensorFilter *m_humidityFilter = nullptr; + SensorFilter *m_pressureFilter = nullptr; + SensorFilter *m_opticalFilter = nullptr; + SensorFilter *m_accelerometerFilter = nullptr; + + // Set methods + void setLeftButtonPressed(bool pressed); + void setRightButtonPressed(bool pressed); + void setMagnetDetected(bool detected); + + void logSensorValue(double originalValue, double filteredValue); + + +signals: + +private slots: + +}; + +#endif // SENSORDATAPROCESSOR_H diff --git a/texasinstruments/sensorfilter.cpp b/texasinstruments/sensorfilter.cpp new file mode 100644 index 00000000..39901089 --- /dev/null +++ b/texasinstruments/sensorfilter.cpp @@ -0,0 +1,178 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Simon Stuerz * + * * + * 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 "sensorfilter.h" + +#include + +SensorFilter::SensorFilter(Type filterType, QObject *parent) : + QObject(parent), + m_filterType(filterType) +{ + +} + +float SensorFilter::filterValue(float value) +{ + float resultValue = value; + switch (m_filterType) { + case TypeLowPass: + resultValue = lowPassFilterValue(value); + break; + case TypeHighPass: + resultValue = highPassFilterValue(value); + break; + case TypeAverage: + resultValue = averageFilterValue(value); + break; + default: + break; + } + + return resultValue; +} + +bool SensorFilter::isReady() const +{ + // Note: filter is ready once 10% of window filled + return m_inputData.size() >= m_filterWindowSize * 0.1; +} + +void SensorFilter::reset() +{ + m_averageSum = 0; + m_inputData.clear(); +} + +SensorFilter::Type SensorFilter::filterType() const +{ + return m_filterType; +} + +QVector SensorFilter::inputData() const +{ + return m_inputData; +} + +QVector SensorFilter::outputData() const +{ + return m_outputData; +} + +uint SensorFilter::windowSize() const +{ + return m_filterWindowSize; +} + +void SensorFilter::setFilterWindowSize(uint windowSize) +{ + Q_ASSERT_X(windowSize > 0, "value out of range", "The filter window size must be bigger than 0"); + m_filterWindowSize = windowSize; +} + +float SensorFilter::lowPassAlpha() const +{ + return m_lowPassAlpha; +} + +void SensorFilter::setLowPassAlpha(float alpha) +{ + Q_ASSERT_X(alpha > 0 && alpha <= 1, "value out of range", "The alpha low pass filter value must be [ 0 < alpha <= 1 ]"); + m_lowPassAlpha = alpha; +} + +float SensorFilter::highPassAlpha() const +{ + return m_highPassAlpha; +} + +void SensorFilter::setHighPassAlpha(float alpha) +{ + Q_ASSERT_X(alpha > 0 && alpha <= 1, "value out of range", "The alpha high pass filter value must be [ 0 < alpha <= 1 ]"); + m_highPassAlpha = alpha; +} + +void SensorFilter::addInputValue(float value) +{ + m_inputData.append(value); + if (static_cast(m_inputData.size()) > m_filterWindowSize) { + m_inputData.removeFirst(); + } +} + +float SensorFilter::lowPassFilterValue(float value) +{ + addInputValue(value); + + // Check if we have enough data for filtering + if (m_inputData.size() < 2) { + return value; + } + + QVector outputData; + outputData.append(m_inputData.at(0)); + for (int i = 1; i < m_inputData.size(); i++) { + // y[i] := y[i-1] + α * (x[i] - y[i-1]) + outputData.append(outputData.at(i - 1) + m_lowPassAlpha * (m_inputData.at(i) - outputData.at(i - 1))); + } + + m_outputData = outputData; + + return m_outputData.last(); +} + +float SensorFilter::highPassFilterValue(float value) +{ + addInputValue(value); + + // Check if we have enough data for filtering + if (m_inputData.size() < 2) { + return value; + } + + QVector outputData; + outputData.append(m_inputData.at(0)); + for (int i = 1; i < m_inputData.size(); i++) { + // y[i] := α * y[i-1] + α * (x[i] - x[i-1]) + outputData.append(m_highPassAlpha * outputData.at(i - 1) + m_highPassAlpha * (m_inputData.at(i) - m_inputData.at(i - 1))); + } + + m_outputData = outputData; + return m_outputData.last(); +} + +float SensorFilter::averageFilterValue(float value) +{ + if (m_inputData.isEmpty()) { + addInputValue(value); + m_averageSum = value; + return value; + } + + if (static_cast(m_inputData.size()) >= m_filterWindowSize) { + m_averageSum -= m_inputData.takeFirst(); + } + + addInputValue(value); + m_averageSum += value; + return m_averageSum / m_inputData.size(); +} diff --git a/texasinstruments/sensorfilter.h b/texasinstruments/sensorfilter.h new file mode 100644 index 00000000..f5ae47e6 --- /dev/null +++ b/texasinstruments/sensorfilter.h @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Simon Stuerz * + * * + * 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 SENSORFILTER_H +#define SENSORFILTER_H + +#include +#include + +class SensorFilter : public QObject +{ + Q_OBJECT +public: + enum Type { + TypeLowPass, + TypeHighPass, + TypeAverage + }; + Q_ENUM(Type) + + explicit SensorFilter(Type filterType, QObject *parent = nullptr); + + float filterValue(float value); + + bool isReady() const; + void reset(); + + Type filterType() const; + + QVector inputData() const; + QVector outputData() const; + + // Filter configuration + uint windowSize() const; + void setFilterWindowSize(uint windowSize = 20); + + float lowPassAlpha() const; + void setLowPassAlpha(float alpha = 0.2f); + + float highPassAlpha() const; + void setHighPassAlpha(float alpha = 0.2f); + +private: + Type m_filterType = TypeLowPass; + uint m_filterWindowSize = 20; + float m_lowPassAlpha = 0.2f; + float m_highPassAlpha = 0.2f; + + float m_averageSum = 0; + + QVector m_inputData; + QVector m_outputData; + + void addInputValue(float value); + + // Filter methods + float lowPassFilterValue(float value); + float highPassFilterValue(float value); + float averageFilterValue(float value); +}; + +#endif // SENSORFILTER_H diff --git a/texasinstruments/sensortag.cpp b/texasinstruments/sensortag.cpp new file mode 100644 index 00000000..b8b1d32e --- /dev/null +++ b/texasinstruments/sensortag.cpp @@ -0,0 +1,912 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Simon Stuerz * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "sensortag.h" +#include "extern-plugininfo.h" +#include "math.h" + +#include +#include +#include + +SensorTag::SensorTag(Device *device, BluetoothLowEnergyDevice *bluetoothDevice, QObject *parent) : + QObject(parent), + m_device(device), + m_bluetoothDevice(bluetoothDevice), + m_dataProcessor(new SensorDataProcessor(m_device, this)) +{ + connect(m_bluetoothDevice, &BluetoothLowEnergyDevice::connectedChanged, this, &SensorTag::onConnectedChanged); + connect(m_bluetoothDevice, &BluetoothLowEnergyDevice::servicesDiscoveryFinished, this, &SensorTag::onServiceDiscoveryFinished); +} + +Device *SensorTag::device() +{ + return m_device; +} + +BluetoothLowEnergyDevice *SensorTag::bluetoothDevice() +{ + return m_bluetoothDevice; +} + +void SensorTag::setTemperatureSensorEnabled(bool enabled) +{ + qCDebug(dcTexasInstruments()) << "Temperature sensor" << (enabled ? "enabled" : "disabled"); + + if (m_temperatureEnabled == enabled) + return; + + m_temperatureEnabled = enabled; + setTemperatureSensorPower(m_temperatureEnabled); +} + +void SensorTag::setHumiditySensorEnabled(bool enabled) +{ + qCDebug(dcTexasInstruments()) << "Humidity sensor" << (enabled ? "enabled" : "disabled"); + + if (m_humidityEnabled == enabled) + return; + + m_humidityEnabled = enabled; + setHumiditySensorPower(m_humidityEnabled); +} + +void SensorTag::setPressureSensorEnabled(bool enabled) +{ + qCDebug(dcTexasInstruments()) << "Pressure sensor" << (enabled ? "enabled" : "disabled"); + + if (m_pressureEnabled == enabled) + return; + + m_pressureEnabled = enabled; + setPressureSensorPower(m_pressureEnabled); +} + +void SensorTag::setOpticalSensorEnabled(bool enabled) +{ + qCDebug(dcTexasInstruments()) << "Optical sensor" << (enabled ? "enabled" : "disabled"); + + if (m_opticalEnabled == enabled) + return; + + m_opticalEnabled = enabled; + setOpticalSensorPower(m_opticalEnabled); +} + +void SensorTag::setAccelerometerEnabled(bool enabled) +{ + qCDebug(dcTexasInstruments()) << "Accelerometer" << (enabled ? "enabled" : "disabled"); + + if (m_accelerometerEnabled == enabled) + return; + + m_accelerometerEnabled = enabled; + configureMovement(); +} + +void SensorTag::setAccelerometerRange(const SensorTag::SensorAccelerometerRange &range) +{ + qCDebug(dcTexasInstruments()) << "Accelerometer" << range; + + if (m_accelerometerRange == range) + return; + + m_accelerometerRange = range; + configureMovement(); +} + +void SensorTag::setGyroscopeEnabled(bool enabled) +{ + qCDebug(dcTexasInstruments()) << "Gyroscope" << (enabled ? "enabled" : "disabled"); + + if (m_gyroscopeEnabled == enabled) + return; + + m_gyroscopeEnabled = enabled; + configureMovement(); +} + +void SensorTag::setMagnetometerEnabled(bool enabled) +{ + qCDebug(dcTexasInstruments()) << "Magnetometer" << (enabled ? "enabled" : "disabled"); + + if (m_magnetometerEnabled == enabled) + return; + + m_magnetometerEnabled = enabled; + configureMovement(); +} + +void SensorTag::setMeasurementPeriod(int period) +{ + qCDebug(dcTexasInstruments()) << "Set sensor measurement period to" << period << "ms"; + + if (period % 10 != 0) { + int adjustedValue = qRound(static_cast(period) / 10.0) * 10; + qCWarning(dcTexasInstruments()) << "Measurement period of sensors" << period << "must be a multiple of 10ms. Adjusting it to" << adjustedValue; + period = adjustedValue; + } + + m_temperaturePeriod = period; + if (m_temperatureService && m_temperaturePeriodCharacteristic.isValid()) + configurePeriod(m_temperatureService, m_temperaturePeriodCharacteristic, m_temperaturePeriod); + + m_humidityPeriod = period; + if (m_humidityService && m_humidityPeriodCharacteristic.isValid()) + configurePeriod(m_humidityService, m_humidityPeriodCharacteristic, m_humidityPeriod); + + m_pressurePeriod = period; + if (m_pressureService && m_pressurePeriodCharacteristic.isValid()) + configurePeriod(m_pressureService, m_pressurePeriodCharacteristic, m_pressurePeriod); + + m_opticalPeriod = period; + if (m_opticalService && m_opticalPeriodCharacteristic.isValid()) + configurePeriod(m_opticalService, m_opticalPeriodCharacteristic, m_opticalPeriod); + +} + +void SensorTag::setMeasurementPeriodMovement(int period) +{ + qCDebug(dcTexasInstruments()) << "Set movement sensor measurement period to" << period << "ms"; + + if (period % 10 != 0) { + int adjustedValue = qRound(static_cast(period) / 10.0) * 10; + qCWarning(dcTexasInstruments()) << "Measurement period of movement sensor" << period << "must be a multiple of 10ms. Adjusting it to" << adjustedValue; + period = adjustedValue; + } + + m_movementPeriod = period; + if (m_movementService && m_movementPeriodCharacteristic.isValid()) + configurePeriod(m_movementService, m_movementPeriodCharacteristic, m_movementPeriod); + +} + +void SensorTag::setMovementSensitivity(int percentage) +{ + m_movementSensitivity = static_cast(percentage) / 100.0; +} + +void SensorTag::setGreenLedPower(bool power) +{ + m_greenLedEnabled = power; + qCDebug(dcTexasInstruments()) << "Green LED" << (power ? "enabled" : "disabled"); + configureIo(); + m_device->setStateValue(sensorTagGreenLedStateTypeId, m_greenLedEnabled); +} + +void SensorTag::setRedLedPower(bool power) +{ + m_redLedEnabled = power; + qCDebug(dcTexasInstruments()) << "Red LED" << (power ? "enabled" : "disabled"); + configureIo(); + m_device->setStateValue(sensorTagRedLedStateTypeId, m_redLedEnabled); +} + +void SensorTag::setBuzzerPower(bool power) +{ + m_buzzerEnabled = power; + qCDebug(dcTexasInstruments()) << "Buzzer" << (power ? "enabled" : "disabled"); + configureIo(); + m_device->setStateValue(sensorTagBuzzerStateTypeId, m_buzzerEnabled); +} + +void SensorTag::buzzerImpulse() +{ + qCDebug(dcTexasInstruments()) << "Buzzer impulse"; + setBuzzerPower(true); + QTimer::singleShot(1000, this, &SensorTag::onBuzzerImpulseTimeout); +} + +void SensorTag::configurePeriod(QLowEnergyService *serice, const QLowEnergyCharacteristic &characteristic, int measurementPeriod) +{ + Q_ASSERT(measurementPeriod % 10 == 0); + QByteArray payload; + QDataStream stream(&payload, QIODevice::WriteOnly); + stream << static_cast(measurementPeriod / 10); + + qCDebug(dcTexasInstruments()) << "Configure period to" << measurementPeriod << payload.toHex(); + serice->writeCharacteristic(characteristic, payload); +} + +void SensorTag::configureMovement() +{ + if (!m_movementService || !m_movementConfigurationCharacteristic.isValid()) + return; + + quint16 configuration = 0; + if (m_gyroscopeEnabled) { + configuration |= (1 << 0); // enable x-axis + configuration |= (1 << 1); // enable y-axis + configuration |= (1 << 2); // enable z-axis + } + + if (m_accelerometerEnabled) { + configuration |= (1 << 3); // enable x-axis + configuration |= (1 << 4); // enable y-axis + configuration |= (1 << 5); // enable z-axis + } + + if (m_magnetometerEnabled) { + configuration |= (1 << 6); // enable all axis + } + + // Always enable wake on movement in order to save energy + configuration |= (1 << 8); // enable + + // Accelerometer range 2 Bit ( 0 = 2G, 1 = 4G, 2 = 8G, 3 = 16G) + switch (m_accelerometerRange) { + case SensorAccelerometerRange2G: + // Bit 9 = 0 + // Bit 10 = 0 + break; + case SensorAccelerometerRange4G: + configuration |= (1 << 11); + // Bit 12 = 0 + break; + case SensorAccelerometerRange8G: + // Bit 13 = 0 + configuration |= (1 << 14); + break; + case SensorAccelerometerRange16G: + configuration |= (1 << 15); + configuration |= (1 << 16); + break; + default: + break; + } + + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << configuration; + + qCDebug(dcTexasInstruments()) << "Configure movement sensor" << data.toHex(); + m_movementService->writeCharacteristic(m_movementConfigurationCharacteristic, data); +} + +void SensorTag::configureSensorMode(const SensorTag::SensorMode &mode) +{ + if (!m_ioService || !m_ioDataCharacteristic.isValid()) + return; + + qCDebug(dcTexasInstruments()) << "Setting" << mode; + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(mode); + + m_ioService->writeCharacteristic(m_ioConfigurationCharacteristic, data); +} + +void SensorTag::configureIo() +{ + if (!m_ioService || !m_ioDataCharacteristic.isValid()) + return; + + // Write value to IO + quint8 configuration = 0; + if (m_redLedEnabled) + configuration |= (1 << 0); // Red LED + + if (m_greenLedEnabled) + configuration |= (1 << 1); // Green LED + + if (m_buzzerEnabled) + configuration |= (1 << 2); // Buzzer + + QByteArray payload; + QDataStream stream(&payload, QIODevice::WriteOnly); + stream << configuration; + + m_ioService->writeCharacteristic(m_ioDataCharacteristic, payload); +} + +void SensorTag::setTemperatureSensorPower(bool power) +{ + if (!m_temperatureService || !m_temperatureConfigurationCharacteristic.isValid()) + return; + + QByteArray payload = (power ? QByteArray::fromHex("01") : QByteArray::fromHex("00")); + m_temperatureService->writeCharacteristic(m_temperatureConfigurationCharacteristic, payload); +} + +void SensorTag::setHumiditySensorPower(bool power) +{ + if (!m_humidityService || !m_humidityConfigurationCharacteristic.isValid()) + return; + + QByteArray payload = (power ? QByteArray::fromHex("01") : QByteArray::fromHex("00")); + m_humidityService->writeCharacteristic(m_humidityConfigurationCharacteristic, payload); +} + +void SensorTag::setPressureSensorPower(bool power) +{ + if (!m_pressureService || !m_pressureConfigurationCharacteristic.isValid()) + return; + + QByteArray payload = (power ? QByteArray::fromHex("01") : QByteArray::fromHex("00")); + m_pressureService->writeCharacteristic(m_pressureConfigurationCharacteristic, payload); +} + +void SensorTag::setOpticalSensorPower(bool power) +{ + if (!m_opticalService || !m_opticalConfigurationCharacteristic.isValid()) + return; + + QByteArray payload = (power ? QByteArray::fromHex("01") : QByteArray::fromHex("00")); + m_opticalService->writeCharacteristic(m_opticalConfigurationCharacteristic, payload); +} + + +void SensorTag::onConnectedChanged(const bool &connected) +{ + qCDebug(dcTexasInstruments()) << "Sensor" << m_bluetoothDevice->name() << m_bluetoothDevice->address().toString() << (connected ? "connected" : "disconnected"); + m_device->setStateValue(sensorTagConnectedStateTypeId, connected); + + if (!connected) { + // Clean up services + m_temperatureService->deleteLater(); + m_humidityService->deleteLater(); + m_pressureService->deleteLater(); + m_opticalService->deleteLater(); + m_keyService->deleteLater(); + m_movementService->deleteLater(); + m_ioService->deleteLater(); + + m_temperatureService = nullptr; + m_humidityService = nullptr; + m_pressureService = nullptr; + m_opticalService = nullptr; + m_keyService = nullptr; + m_movementService = nullptr; + m_ioService = nullptr; + + m_dataProcessor->reset(); + } +} + +void SensorTag::onServiceDiscoveryFinished() +{ + foreach (const QBluetoothUuid serviceUuid, m_bluetoothDevice->serviceUuids()) { + qCDebug(dcTexasInstruments()) << "-->" << serviceUuid; + } + + if (!m_bluetoothDevice->serviceUuids().contains(temperatureServiceUuid)) { + qCWarning(dcTexasInstruments()) << "Could not find temperature service"; + m_bluetoothDevice->disconnectDevice(); + return; + } + + if (!m_bluetoothDevice->serviceUuids().contains(humidityServiceUuid)) { + qCWarning(dcTexasInstruments()) << "Could not find humidity service"; + m_bluetoothDevice->disconnectDevice(); + return; + } + + if (!m_bluetoothDevice->serviceUuids().contains(pressureServiceUuid)) { + qCWarning(dcTexasInstruments()) << "Could not find pressure service"; + m_bluetoothDevice->disconnectDevice(); + return; + } + + if (!m_bluetoothDevice->serviceUuids().contains(opticalServiceUuid)) { + qCWarning(dcTexasInstruments()) << "Could not find optical service"; + m_bluetoothDevice->disconnectDevice(); + return; + } + + if (!m_bluetoothDevice->serviceUuids().contains(keyServiceUuid)) { + qCWarning(dcTexasInstruments()) << "Could not find key service"; + m_bluetoothDevice->disconnectDevice(); + return; + } + + if (!m_bluetoothDevice->serviceUuids().contains(movementServiceUuid)) { + qCWarning(dcTexasInstruments()) << "Could not find movement service"; + m_bluetoothDevice->disconnectDevice(); + return; + } + + if (!m_bluetoothDevice->serviceUuids().contains(ioServiceUuid)) { + qCWarning(dcTexasInstruments()) << "Could not find IO service"; + m_bluetoothDevice->disconnectDevice(); + return; + } + + // IR Temperature + if (!m_temperatureService) { + m_temperatureService = m_bluetoothDevice->controller()->createServiceObject(temperatureServiceUuid, this); + if (!m_temperatureService) { + qCWarning(dcTexasInstruments()) << "Could not create temperature service."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + connect(m_temperatureService, &QLowEnergyService::stateChanged, this, &SensorTag::onTemperatureServiceStateChanged); + connect(m_temperatureService, &QLowEnergyService::characteristicChanged, this, &SensorTag::onTemperatureServiceCharacteristicChanged); + + if (m_temperatureService->state() == QLowEnergyService::DiscoveryRequired) { + m_temperatureService->discoverDetails(); + } + } + + // Humidity + if (!m_humidityService) { + m_humidityService = m_bluetoothDevice->controller()->createServiceObject(humidityServiceUuid, this); + if (!m_humidityService) { + qCWarning(dcTexasInstruments()) << "Could not create humidity service."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + connect(m_humidityService, &QLowEnergyService::stateChanged, this, &SensorTag::onHumidityServiceStateChanged); + connect(m_humidityService, &QLowEnergyService::characteristicChanged, this, &SensorTag::onHumidityServiceCharacteristicChanged); + if (m_humidityService->state() == QLowEnergyService::DiscoveryRequired) { + m_humidityService->discoverDetails(); + } + } + + // Pressure + if (!m_pressureService) { + m_pressureService = m_bluetoothDevice->controller()->createServiceObject(pressureServiceUuid, this); + if (!m_pressureService) { + qCWarning(dcTexasInstruments()) << "Could not create pressure service."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + connect(m_pressureService, &QLowEnergyService::stateChanged, this, &SensorTag::onPressureServiceStateChanged); + connect(m_pressureService, &QLowEnergyService::characteristicChanged, this, &SensorTag::onPressureServiceCharacteristicChanged); + if (m_pressureService->state() == QLowEnergyService::DiscoveryRequired) { + m_pressureService->discoverDetails(); + } + } + + + // Optical + if (!m_opticalService) { + m_opticalService = m_bluetoothDevice->controller()->createServiceObject(opticalServiceUuid, this); + if (!m_opticalService) { + qCWarning(dcTexasInstruments()) << "Could not create optical service."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + connect(m_opticalService, &QLowEnergyService::stateChanged, this, &SensorTag::onOpticalServiceStateChanged); + connect(m_opticalService, &QLowEnergyService::characteristicChanged, this, &SensorTag::onOpticalServiceCharacteristicChanged); + + if (m_opticalService->state() == QLowEnergyService::DiscoveryRequired) { + m_opticalService->discoverDetails(); + } + } + + // Key + if (!m_keyService) { + m_keyService = m_bluetoothDevice->controller()->createServiceObject(keyServiceUuid, this); + if (!m_keyService) { + qCWarning(dcTexasInstruments()) << "Could not create key service."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + connect(m_keyService, &QLowEnergyService::stateChanged, this, &SensorTag::onKeyServiceStateChanged); + connect(m_keyService, &QLowEnergyService::characteristicChanged, this, &SensorTag::onKeyServiceCharacteristicChanged); + + if (m_keyService->state() == QLowEnergyService::DiscoveryRequired) { + m_keyService->discoverDetails(); + } + } + + // Movement + if (!m_movementService) { + m_movementService = m_bluetoothDevice->controller()->createServiceObject(movementServiceUuid, this); + if (!m_movementService) { + qCWarning(dcTexasInstruments()) << "Could not create movement service."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + connect(m_movementService, &QLowEnergyService::stateChanged, this, &SensorTag::onMovementServiceStateChanged); + connect(m_movementService, &QLowEnergyService::characteristicChanged, this, &SensorTag::onMovementServiceCharacteristicChanged); + + if (m_movementService->state() == QLowEnergyService::DiscoveryRequired) { + m_movementService->discoverDetails(); + } + } + + // IO + if (!m_ioService) { + m_ioService = m_bluetoothDevice->controller()->createServiceObject(ioServiceUuid, this); + if (!m_ioService) { + qCWarning(dcTexasInstruments()) << "Could not create IO service."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + connect(m_ioService, &QLowEnergyService::stateChanged, this, &SensorTag::onIoServiceStateChanged); + connect(m_ioService, &QLowEnergyService::characteristicChanged, this, &SensorTag::onIoServiceCharacteristicChanged); + + if (m_ioService->state() == QLowEnergyService::DiscoveryRequired) { + m_ioService->discoverDetails(); + } + } +} + +void SensorTag::onBuzzerImpulseTimeout() +{ + setBuzzerPower(false); +} + +void SensorTag::onTemperatureServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcTexasInstruments()) << "Temperature sensor service discovered."; + + foreach (const QLowEnergyCharacteristic &characteristic, m_temperatureService->characteristics()) { + qCDebug(dcTexasInstruments()) << " -->" << characteristic.name() << characteristic.uuid().toString() << characteristic.value(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcTexasInstruments()) << " -->" << descriptor.name() << descriptor.uuid().toString() << descriptor.value(); + } + } + + // Data characteristic + m_temperatureDataCharacteristic = m_temperatureService->characteristic(temperatureDataCharacteristicUuid); + if (!m_temperatureDataCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid temperature data characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_temperatureDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_temperatureService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + + // Config characteristic + m_temperatureConfigurationCharacteristic = m_temperatureService->characteristic(temperatureConfigurationCharacteristicUuid); + if (!m_temperatureConfigurationCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid temperature configuration characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + // Period characteristic + m_temperaturePeriodCharacteristic = m_temperatureService->characteristic(temperaturePeriodCharacteristicUuid); + if (!m_temperaturePeriodCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid temperature period characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + configurePeriod(m_temperatureService, m_temperaturePeriodCharacteristic, m_temperaturePeriod); + + // Enable/disable measuring + setTemperatureSensorPower(m_temperatureEnabled); +} + +void SensorTag::onTemperatureServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic == m_temperatureDataCharacteristic) { + m_dataProcessor->processTemperatureData(value); + } +} + +void SensorTag::onHumidityServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcTexasInstruments()) << "Humidity sensor service discovered."; + + foreach (const QLowEnergyCharacteristic &characteristic, m_humidityService->characteristics()) { + qCDebug(dcTexasInstruments()) << " -->" << characteristic.name() << characteristic.uuid().toString() << characteristic.value(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcTexasInstruments()) << " -->" << descriptor.name() << descriptor.uuid().toString() << descriptor.value(); + } + } + + // Data characteristic + m_humidityDataCharacteristic = m_humidityService->characteristic(humidityDataCharacteristicUuid); + if (!m_humidityDataCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid humidity data characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_humidityDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_humidityService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + // Config characteristic + m_humidityConfigurationCharacteristic = m_humidityService->characteristic(humidityConfigurationCharacteristicUuid); + if (!m_humidityConfigurationCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid humidity configuration characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + // Period characteristic + m_humidityPeriodCharacteristic = m_humidityService->characteristic(humidityPeriodCharacteristicUuid); + if (!m_humidityPeriodCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid humidity period characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + configurePeriod(m_humidityService, m_humidityPeriodCharacteristic, m_humidityPeriod); + + // Enable measuring + m_humidityService->writeCharacteristic(m_humidityConfigurationCharacteristic, QByteArray::fromHex("01")); +} + +void SensorTag::onHumidityServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic == m_humidityDataCharacteristic) { + m_dataProcessor->processHumidityData(value); + } +} + +void SensorTag::onPressureServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcTexasInstruments()) << "Pressure sensor service discovered."; + + foreach (const QLowEnergyCharacteristic &characteristic, m_pressureService->characteristics()) { + qCDebug(dcTexasInstruments()) << " -->" << characteristic.name() << characteristic.uuid().toString() << characteristic.value(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcTexasInstruments()) << " -->" << descriptor.name() << descriptor.uuid().toString() << descriptor.value(); + } + } + + // Data characteristic + m_pressureDataCharacteristic = m_pressureService->characteristic(pressureDataCharacteristicUuid); + if (!m_pressureDataCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid pressure data characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_pressureDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_pressureService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + // Config characteristic + m_pressureConfigurationCharacteristic = m_pressureService->characteristic(pressureConfigurationCharacteristicUuid); + if (!m_pressureConfigurationCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid pressure configuration characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + // Period characteristic + m_pressurePeriodCharacteristic = m_pressureService->characteristic(pressurePeriodCharacteristicUuid); + if (!m_pressurePeriodCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid pressure period characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + configurePeriod(m_pressureService, m_pressurePeriodCharacteristic, m_pressurePeriod); + + // Enable measuring + m_pressureService->writeCharacteristic(m_pressureConfigurationCharacteristic, QByteArray::fromHex("01")); +} + +void SensorTag::onPressureServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic == m_pressureDataCharacteristic) { + m_dataProcessor->processPressureData(value); + } +} + +void SensorTag::onOpticalServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcTexasInstruments()) << "Optical sensor service discovered."; + + foreach (const QLowEnergyCharacteristic &characteristic, m_pressureService->characteristics()) { + qCDebug(dcTexasInstruments()) << " -->" << characteristic.name() << characteristic.uuid().toString() << characteristic.value(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcTexasInstruments()) << " -->" << descriptor.name() << descriptor.uuid().toString() << descriptor.value(); + } + } + + // Data characteristic + m_opticalDataCharacteristic = m_opticalService->characteristic(opticalDataCharacteristicUuid); + if (!m_opticalDataCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid optical data characteristic."; + m_bluetoothDevice->disconnectDevice(); + return; + } + + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_opticalDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_opticalService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + // Config characteristic + m_opticalConfigurationCharacteristic = m_opticalService->characteristic(opticalConfigurationCharacteristicUuid); + if (!m_opticalConfigurationCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid optical configuration characteristic."; + m_bluetoothDevice->disconnectDevice(); + } + + // Period characteristic + m_opticalPeriodCharacteristic = m_opticalService->characteristic(opticalPeriodCharacteristicUuid); + if (!m_opticalPeriodCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid optical period characteristic."; + m_bluetoothDevice->disconnectDevice(); + } + + // Set measurement period + configurePeriod(m_opticalService, m_opticalPeriodCharacteristic, m_opticalPeriod); + + // Enable measuring + m_opticalService->writeCharacteristic(m_opticalConfigurationCharacteristic, QByteArray::fromHex("01")); +} + +void SensorTag::onOpticalServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic == m_opticalDataCharacteristic) { + m_dataProcessor->processOpticalData(value); + } +} + +void SensorTag::onKeyServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcTexasInstruments()) << "Key service discovered."; + foreach (const QLowEnergyCharacteristic &characteristic, m_keyService->characteristics()) { + qCDebug(dcTexasInstruments()) << " -->" << characteristic.name() << characteristic.uuid().toString() << characteristic.value(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcTexasInstruments()) << " -->" << descriptor.name() << descriptor.uuid().toString() << descriptor.value(); + } + } + + // Data characteristic + m_keyDataCharacteristic = m_keyService->characteristic(keyDataCharacteristicUuid); + if (!m_keyDataCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid button data characteristic."; + m_bluetoothDevice->disconnectDevice(); + } + + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_keyDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_keyService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); +} + +void SensorTag::onKeyServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic == m_keyDataCharacteristic) { + m_dataProcessor->processKeyData(value); + } +} + +void SensorTag::onMovementServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcTexasInstruments()) << "Movement sensor service discovered."; + + foreach (const QLowEnergyCharacteristic &characteristic, m_pressureService->characteristics()) { + qCDebug(dcTexasInstruments()) << " -->" << characteristic.name() << characteristic.uuid().toString() << characteristic.value(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcTexasInstruments()) << " -->" << descriptor.name() << descriptor.uuid().toString() << descriptor.value(); + } + } + + // Data characteristic + m_movementDataCharacteristic = m_movementService->characteristic(movementDataCharacteristicUuid); + if (!m_movementDataCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid movement data characteristic."; + m_bluetoothDevice->disconnectDevice(); + } + + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_movementDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_movementService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + // Config characteristic + m_movementConfigurationCharacteristic = m_movementService->characteristic(movementConfigurationCharacteristicUuid); + if (!m_movementConfigurationCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid movement configuration characteristic."; + m_bluetoothDevice->disconnectDevice(); + } + + // Period characteristic + m_movementPeriodCharacteristic = m_movementService->characteristic(movementPeriodCharacteristicUuid); + if (!m_movementPeriodCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid movement period characteristic."; + m_bluetoothDevice->disconnectDevice(); + } + + // Set measurement period + configurePeriod(m_movementService, m_movementPeriodCharacteristic, m_movementPeriod); + configureMovement(); + + // Enable measuring + m_movementService->writeCharacteristic(m_movementConfigurationCharacteristic, QByteArray::fromHex("01")); +} + +void SensorTag::onMovementServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic == m_movementDataCharacteristic) { + m_dataProcessor->processMovementData(value); + } +} + +void SensorTag::onIoServiceStateChanged(const QLowEnergyService::ServiceState &state) +{ + // Only continue if discovered + if (state != QLowEnergyService::ServiceDiscovered) + return; + + qCDebug(dcTexasInstruments()) << "IO service discovered."; + + foreach (const QLowEnergyCharacteristic &characteristic, m_pressureService->characteristics()) { + qCDebug(dcTexasInstruments()) << " -->" << characteristic.name() << characteristic.uuid().toString() << characteristic.value(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcTexasInstruments()) << " -->" << descriptor.name() << descriptor.uuid().toString() << descriptor.value(); + } + } + + // Data characteristic + m_ioDataCharacteristic = m_ioService->characteristic(ioDataCharacteristicUuid); + if (!m_ioDataCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid IO data characteristic."; + m_bluetoothDevice->disconnectDevice(); + } + + // Enable notifications + QLowEnergyDescriptor notificationDescriptor = m_ioDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + m_ioService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); + + // Config characteristic + m_ioConfigurationCharacteristic = m_ioService->characteristic(ioConfigurationCharacteristicUuid); + if (!m_ioConfigurationCharacteristic.isValid()) { + qCWarning(dcTexasInstruments()) << "Invalid IO configuration characteristic."; + m_bluetoothDevice->disconnectDevice(); + } + + configureIo(); + configureSensorMode(SensorModeRemote); + configureIo(); +} + +void SensorTag::onIoServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + qCDebug(dcTexasInstruments()) << characteristic.uuid().toString() << value.toHex(); +} diff --git a/texasinstruments/sensortag.h b/texasinstruments/sensortag.h new file mode 100644 index 00000000..57037337 --- /dev/null +++ b/texasinstruments/sensortag.h @@ -0,0 +1,228 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Simon Stuerz * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SENSORTAG_H +#define SENSORTAG_H + +#include + +#include "plugin/device.h" +#include "extern-plugininfo.h" +#include "sensordataprocessor.h" + +#include "hardware/bluetoothlowenergy/bluetoothlowenergydevice.h" + +// http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User's_Guide + +static QBluetoothUuid temperatureServiceUuid = QBluetoothUuid(QUuid("f000aa00-0451-4000-b000-000000000000")); +static QBluetoothUuid temperatureDataCharacteristicUuid = QBluetoothUuid(QUuid("f000aa01-0451-4000-b000-000000000000")); +static QBluetoothUuid temperatureConfigurationCharacteristicUuid = QBluetoothUuid(QUuid("f000aa02-0451-4000-b000-000000000000")); +static QBluetoothUuid temperaturePeriodCharacteristicUuid = QBluetoothUuid(QUuid("f000aa03-0451-4000-b000-000000000000")); + +static QBluetoothUuid humidityServiceUuid = QBluetoothUuid(QUuid("f000aa20-0451-4000-b000-000000000000")); +static QBluetoothUuid humidityDataCharacteristicUuid = QBluetoothUuid(QUuid("f000aa21-0451-4000-b000-000000000000")); +static QBluetoothUuid humidityConfigurationCharacteristicUuid = QBluetoothUuid(QUuid("f000aa22-0451-4000-b000-000000000000")); +static QBluetoothUuid humidityPeriodCharacteristicUuid = QBluetoothUuid(QUuid("f000aa23-0451-4000-b000-000000000000")); + +static QBluetoothUuid pressureServiceUuid = QBluetoothUuid(QUuid("f000aa40-0451-4000-b000-000000000000")); +static QBluetoothUuid pressureDataCharacteristicUuid = QBluetoothUuid(QUuid("f000aa41-0451-4000-b000-000000000000")); +static QBluetoothUuid pressureConfigurationCharacteristicUuid = QBluetoothUuid(QUuid("f000aa42-0451-4000-b000-000000000000")); +static QBluetoothUuid pressurePeriodCharacteristicUuid = QBluetoothUuid(QUuid("f000aa44-0451-4000-b000-000000000000")); + +static QBluetoothUuid opticalServiceUuid = QBluetoothUuid(QUuid("f000aa70-0451-4000-b000-000000000000")); +static QBluetoothUuid opticalDataCharacteristicUuid = QBluetoothUuid(QUuid("f000aa71-0451-4000-b000-000000000000")); +static QBluetoothUuid opticalConfigurationCharacteristicUuid = QBluetoothUuid(QUuid("f000aa72-0451-4000-b000-000000000000")); +static QBluetoothUuid opticalPeriodCharacteristicUuid = QBluetoothUuid(QUuid("f000aa73-0451-4000-b000-000000000000")); + +static QBluetoothUuid keyServiceUuid = QBluetoothUuid(QUuid("0000ffe0-0000-1000-8000-00805f9b34fb")); +static QBluetoothUuid keyDataCharacteristicUuid = QBluetoothUuid(QUuid("0000ffe1-0000-1000-8000-00805f9b34fb")); + +static QBluetoothUuid ioServiceUuid = QBluetoothUuid(QUuid("f000aa64-0451-4000-b000-000000000000")); +static QBluetoothUuid ioDataCharacteristicUuid = QBluetoothUuid(QUuid("f000aa65-0451-4000-b000-000000000000")); +static QBluetoothUuid ioConfigurationCharacteristicUuid = QBluetoothUuid(QUuid("f000aa66-0451-4000-b000-000000000000")); + +static QBluetoothUuid movementServiceUuid = QBluetoothUuid(QUuid("f000aa80-0451-4000-b000-000000000000")); +static QBluetoothUuid movementDataCharacteristicUuid = QBluetoothUuid(QUuid("f000aa81-0451-4000-b000-000000000000")); +static QBluetoothUuid movementConfigurationCharacteristicUuid = QBluetoothUuid(QUuid("f000aa82-0451-4000-b000-000000000000")); +static QBluetoothUuid movementPeriodCharacteristicUuid = QBluetoothUuid(QUuid("f000aa83-0451-4000-b000-000000000000")); + + +class SensorTag : public QObject +{ + Q_OBJECT +public: + enum SensorAccelerometerRange { + SensorAccelerometerRange2G = 2, + SensorAccelerometerRange4G = 4, + SensorAccelerometerRange8G = 8, + SensorAccelerometerRange16G = 16 + }; + Q_ENUM(SensorAccelerometerRange) + + enum SensorMode { + SensorModeLocal = 0, + SensorModeRemote = 1, + SensorModeTest = 2 + }; + Q_ENUM(SensorMode) + + explicit SensorTag(Device *device, BluetoothLowEnergyDevice *bluetoothDevice, QObject *parent = nullptr); + + Device *device(); + BluetoothLowEnergyDevice *bluetoothDevice(); + + // Configurations + void setTemperatureSensorEnabled(bool enabled); + void setHumiditySensorEnabled(bool enabled); + void setPressureSensorEnabled(bool enabled); + void setOpticalSensorEnabled(bool enabled); + void setAccelerometerEnabled(bool enabled); + void setGyroscopeEnabled(bool enabled); + void setMagnetometerEnabled(bool enabled); + + void setAccelerometerRange(const SensorAccelerometerRange &range); + void setMeasurementPeriod(int period); + void setMeasurementPeriodMovement(int period); + void setMovementSensitivity(int percentage); + + // Actions + void setGreenLedPower(bool power); + void setRedLedPower(bool power); + void setBuzzerPower(bool power); + void buzzerImpulse(); + +private: + Device *m_device; + BluetoothLowEnergyDevice *m_bluetoothDevice; + + // Services + QLowEnergyService *m_temperatureService = nullptr; + QLowEnergyService *m_humidityService = nullptr; + QLowEnergyService *m_pressureService = nullptr; + QLowEnergyService *m_opticalService = nullptr; + QLowEnergyService *m_keyService = nullptr; + QLowEnergyService *m_movementService = nullptr; + QLowEnergyService *m_ioService = nullptr; + + // Characteristics + QLowEnergyCharacteristic m_temperatureDataCharacteristic; + QLowEnergyCharacteristic m_temperatureConfigurationCharacteristic; + QLowEnergyCharacteristic m_temperaturePeriodCharacteristic; + + QLowEnergyCharacteristic m_humidityDataCharacteristic; + QLowEnergyCharacteristic m_humidityConfigurationCharacteristic; + QLowEnergyCharacteristic m_humidityPeriodCharacteristic; + + QLowEnergyCharacteristic m_pressureDataCharacteristic; + QLowEnergyCharacteristic m_pressureConfigurationCharacteristic; + QLowEnergyCharacteristic m_pressurePeriodCharacteristic; + + QLowEnergyCharacteristic m_opticalDataCharacteristic; + QLowEnergyCharacteristic m_opticalConfigurationCharacteristic; + QLowEnergyCharacteristic m_opticalPeriodCharacteristic; + + QLowEnergyCharacteristic m_keyDataCharacteristic; + + QLowEnergyCharacteristic m_movementDataCharacteristic; + QLowEnergyCharacteristic m_movementConfigurationCharacteristic; + QLowEnergyCharacteristic m_movementPeriodCharacteristic; + + QLowEnergyCharacteristic m_ioDataCharacteristic; + QLowEnergyCharacteristic m_ioConfigurationCharacteristic; + + // Measure periods + int m_temperaturePeriod = 2500; + int m_humidityPeriod = 2500; + int m_pressurePeriod = 2500; + int m_opticalPeriod = 2500; + int m_movementPeriod = 500; + double m_movementSensitivity = 0.5; + SensorAccelerometerRange m_accelerometerRange = SensorAccelerometerRange16G; + + // States + bool m_greenLedEnabled = false; + bool m_redLedEnabled = false; + bool m_buzzerEnabled = false; + + // Plugin configs + bool m_temperatureEnabled = true; + bool m_humidityEnabled = true; + bool m_pressureEnabled = true; + bool m_opticalEnabled = true; + bool m_accelerometerEnabled = true; + bool m_gyroscopeEnabled = false; + bool m_magnetometerEnabled = false; + + SensorDataProcessor *m_dataProcessor = nullptr; + + // Configuration methods + void configurePeriod(QLowEnergyService *serice, const QLowEnergyCharacteristic &characteristic, int measurementPeriod); + void configureMovement(); + void configureSensorMode(const SensorMode &mode); + void configureIo(); + + void setTemperatureSensorPower(bool power); + void setHumiditySensorPower(bool power); + void setPressureSensorPower(bool power); + void setOpticalSensorPower(bool power); + +signals: + void leftButtonPressedChanged(bool pressed); + void rightButtonPressedChanged(bool pressed); + void magnetDetectedChanged(bool detected); + +private slots: + void onConnectedChanged(const bool &connected); + void onServiceDiscoveryFinished(); + + void onBuzzerImpulseTimeout(); + + // Temperature sensor service + void onTemperatureServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onTemperatureServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + + // Humidity sensor service + void onHumidityServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onHumidityServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + + // Pressure sensor service + void onPressureServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onPressureServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + + // Optical sensor service + void onOpticalServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onOpticalServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + + // Button service + void onKeyServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onKeyServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + + // Movement service + void onMovementServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onMovementServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + + // IO service + void onIoServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onIoServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); +}; + +#endif // SENSORTAG_H diff --git a/texasinstruments/texasinstruments.pro b/texasinstruments/texasinstruments.pro new file mode 100644 index 00000000..5ba16b80 --- /dev/null +++ b/texasinstruments/texasinstruments.pro @@ -0,0 +1,19 @@ +include(../plugins.pri) + +QT += bluetooth + +TARGET = $$qtLibraryTarget(nymea_deviceplugintexasinstruments) + +HEADERS += \ + deviceplugintexasinstruments.h \ + sensortag.h \ + sensordataprocessor.h \ + sensorfilter.h + +SOURCES += \ + deviceplugintexasinstruments.cpp \ + sensortag.cpp \ + sensordataprocessor.cpp \ + sensorfilter.cpp + + diff --git a/texasinstruments/translations/ae550a91-e734-4331-9d71-9f37df0b0fa6-en_US.ts b/texasinstruments/translations/ae550a91-e734-4331-9d71-9f37df0b0fa6-en_US.ts new file mode 100644 index 00000000..f7f66d85 --- /dev/null +++ b/texasinstruments/translations/ae550a91-e734-4331-9d71-9f37df0b0fa6-en_US.ts @@ -0,0 +1,4 @@ + + + + \ No newline at end of file