Link bluetooth.

Implement class that talks to BLE miflora devices.
Have UI show current values from new class.
This commit is contained in:
2019-11-07 19:14:58 -05:00
parent 0f63b81cb7
commit 62bf803c71
5 changed files with 246 additions and 3 deletions

View File

@@ -1,8 +1,10 @@
set(qiflora_SRCS
miflora/miflora.cpp
main.cpp
)
qt5_add_resources(RESOURCES resources.qrc)
add_executable(qiflora ${qiflora_SRCS} ${RESOURCES})
target_link_libraries(qiflora Qt5::Core Qt5::Qml Qt5::Quick Qt5::Svg)
target_link_libraries(qiflora Qt5::Core Qt5::Qml Qt5::Quick Qt5::Svg Qt5::Bluetooth)
install(TARGETS qiflora ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})

View File

@@ -2,13 +2,19 @@ import QtQuick 2.6
import org.kde.kirigami 2.4 as Kirigami
import QtQuick.Controls 2.0 as Controls
import QtQuick.Layouts 1.12 as Layouts
import org.eyecreate.qiflora 1.0
Kirigami.ApplicationWindow {
id: root
title: "QiFlora"
pageStack.initialPage: mainPageComponent
QiFlora {
id: qiflora
}
Component {
id: mainPageComponent
@@ -17,6 +23,9 @@ Kirigami.ApplicationWindow {
mainAction: Kirigami.Action {
iconName: "view-refresh"
text: i18n("Query Device")
onTriggered: {
//TODO
}
}
title: "Monitor"
@@ -53,7 +62,12 @@ Kirigami.ApplicationWindow {
Kirigami.Heading {
Layouts.Layout.alignment: Qt.AlignCenter
level: 3
text: "40" //TODO: replace
text: {
if(model.chartType == "temperature") qiflora.temperature + "°C"
else if(model.chartType == "moisture") qiflora.moisture + "%"
else if(model.chartType == "conductivity") qiflora.conduction + " µS/cm"
else if(model.chartType == "brightness") qiflora.brightness + " lux"
}
}
}
}

View File

@@ -2,6 +2,7 @@
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QUrl>
#include "miflora/miflora.h"
Q_DECL_EXPORT int main(int argc, char *argv[])
{
@@ -12,7 +13,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
QCoreApplication::setApplicationName("qiflora");
QQmlApplicationEngine engine;
qmlRegisterType<MiFlora>("org.eyecreate.qiflora",1,0,"QiFlora");
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
if (engine.rootObjects().isEmpty()) {

143
src/miflora/miflora.cpp Normal file
View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2019 Kevin Whitaker <eyecreate@eyecreate.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "miflora.h"
#include <QDataStream>
void MiFlora::startSearch()
{
qDebug() << "Starting search...";
agent = new QBluetoothDeviceDiscoveryAgent();
agent->setLowEnergyDiscoveryTimeout(5000);
devices.clear();
connect(agent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &MiFlora::foundDevice);
agent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
}
void MiFlora::stopSearch()
{
if(agent != NULL) {
agent->stop();
agent->disconnect();
delete agent;
}
}
void MiFlora::updateDataFromDevice ( QString mac )
{
for(QBluetoothDeviceInfo info: devices) {
if(info.address().toString() == mac) {
qDebug() << "Discovering on:" + info.name();
currentController = QLowEnergyController::createCentral(info);
currentController->setRemoteAddressType(QLowEnergyController::PublicAddress);
connect(currentController, &QLowEnergyController::serviceDiscovered, this, &MiFlora::serviceDiscovered);
connect(currentController, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error), this, &MiFlora::logControllerError);
connect(currentController, &QLowEnergyController::connected, this, [this]{
currentController->discoverServices();
});
currentController->connectToDevice();
}
}
}
void MiFlora::foundDevice ( const QBluetoothDeviceInfo& device )
{
if(device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration && device.address().toString().startsWith("C4:7C")) {
devices.append(device);
emit newDeviceFound(device.name(), device.address().toString());
}
}
void MiFlora::logControllerError ( QLowEnergyController::Error err )
{
qDebug() << "Error:" << err;
}
void MiFlora::logServiceError(QLowEnergyService::ServiceError err)
{
qDebug() << "Service Error:" << err;
}
void MiFlora::serviceStateChanges ( QLowEnergyService::ServiceState state )
{
if(state == QLowEnergyService::ServiceState::ServiceDiscovered) {
for(QLowEnergyCharacteristic chrt : sensorService->characteristics()) {
if(chrt.uuid().toUInt16() == magicChar) {
sensorService->writeCharacteristic(chrt, QByteArray::fromHex("a01f"));
}
}
}
}
void MiFlora::serviceCharWritten(QLowEnergyCharacteristic changedChar, QByteArray value)
{
if(changedChar.uuid().toUInt16() == magicChar) {
//Used to only check sensor data after writting magic bytes.
for(QLowEnergyCharacteristic chrt : sensorService->characteristics()) {
if(chrt.uuid().toUInt16() == batteryFirmwareChar) {
sensorService->readCharacteristic(chrt);
}
if(chrt.uuid().toUInt16() == sensorsChar) {
sensorService->readCharacteristic(chrt);
}
}
}
}
void MiFlora::serviceCharRead(QLowEnergyCharacteristic readChar, QByteArray value)
{
if(readChar.uuid().toUInt16() == batteryFirmwareChar) {
QDataStream parser(value);
parser >> battery;
parser.skipRawData(1);
QByteArray version_buf(5, Qt::Uninitialized);
parser.readRawData(version_buf.data(), 5);
version = QString(version_buf);
emit batteryChanged(battery);
qDebug() << "Firmware: " << version;
} else if(readChar.uuid().toUInt16() == sensorsChar) {
quint16 origTemp;
QDataStream parser(value);
parser.setByteOrder(QDataStream::LittleEndian);
parser >> origTemp;
temp = origTemp/ 10.0; //original value in 0.1 C
parser.skipRawData(1);
parser >> bright;
parser >> moisture;
parser >> conduct;
emit brightnessChanged(bright);
emit temperatureChanged(temp);
emit moistureChanged(moisture);
emit conductionChanged(conduct);
}
}
void MiFlora::serviceDiscovered ( const QBluetoothUuid& gatt )
{
if(gatt == QBluetoothUuid(dataService)) {
sensorService = currentController->createServiceObject(gatt);
connect(sensorService, &QLowEnergyService::stateChanged, this, &MiFlora::serviceStateChanges);
connect(sensorService, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error), this, &MiFlora::logServiceError);
connect(sensorService, &QLowEnergyService::characteristicWritten, this, &MiFlora::serviceCharWritten);
connect(sensorService, &QLowEnergyService::characteristicRead, this, &MiFlora::serviceCharRead);
sensorService->discoverDetails();
}
}

83
src/miflora/miflora.h Normal file
View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2019 Kevin Whitaker <eyecreate@eyecreate.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MIFLORA_H
#define MIFLORA_H
#include <QObject>
#include <QtBluetooth/QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/QBluetoothDeviceInfo>
#include <QtBluetooth/QLowEnergyController>
#include <QtBluetooth/QLowEnergyService>
#include <QtBluetooth/QLowEnergyCharacteristic>
/**
* Class using QtBluetooth to find and retrive info from Mi Flora devices.
*/
class MiFlora : public QObject
{
Q_OBJECT
Q_PROPERTY(float temperature NOTIFY temperatureChanged MEMBER temp)
Q_PROPERTY(quint32 brightness NOTIFY brightnessChanged MEMBER bright)
Q_PROPERTY(quint8 moisture NOTIFY moistureChanged MEMBER moisture)
Q_PROPERTY(quint16 conduction NOTIFY conductionChanged MEMBER conduct)
Q_PROPERTY(quint8 battery NOTIFY batteryChanged MEMBER battery)
public:
Q_INVOKABLE void startSearch();
Q_INVOKABLE void stopSearch();
Q_INVOKABLE void updateDataFromDevice(QString mac);
//TODO:History?
signals:
void newDeviceFound(QString name, QString address);
void temperatureChanged(float temperature);
void brightnessChanged(quint32 brightness);
void moistureChanged(quint8 moisture);
void conductionChanged(quint16 conduction);
void batteryChanged(quint8 battery);
private:
void foundDevice(const QBluetoothDeviceInfo &device);
void serviceDiscovered(const QBluetoothUuid &gatt);
void logControllerError(QLowEnergyController::Error err);
void logServiceError(QLowEnergyService::ServiceError err);
void serviceStateChanges(QLowEnergyService::ServiceState state);
void serviceCharWritten(QLowEnergyCharacteristic changedChar, QByteArray value);
void serviceCharRead(QLowEnergyCharacteristic readChar, QByteArray value);
QBluetoothDeviceDiscoveryAgent *agent;
QList<QBluetoothDeviceInfo> devices;
QLowEnergyController *currentController;
QLowEnergyService *sensorService;
const quint16 dataService = 4612; //0x1204
const quint16 batteryFirmwareChar = 6658;//0x1a02
const quint16 sensorsChar = 6657; //0x1a01
const quint16 magicChar = 6656; //0x1a00
float temp = 0.0;
quint32 bright = 0;
quint8 moisture = 0;
quint16 conduct = 0;
quint8 battery = 0;
QString version;
};
#endif // MIFLORA_H