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 set(qiflora_SRCS
miflora/miflora.cpp
main.cpp main.cpp
) )
qt5_add_resources(RESOURCES resources.qrc) qt5_add_resources(RESOURCES resources.qrc)
add_executable(qiflora ${qiflora_SRCS} ${RESOURCES}) 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}) 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 org.kde.kirigami 2.4 as Kirigami
import QtQuick.Controls 2.0 as Controls import QtQuick.Controls 2.0 as Controls
import QtQuick.Layouts 1.12 as Layouts import QtQuick.Layouts 1.12 as Layouts
import org.eyecreate.qiflora 1.0
Kirigami.ApplicationWindow { Kirigami.ApplicationWindow {
id: root id: root
title: "QiFlora" title: "QiFlora"
pageStack.initialPage: mainPageComponent pageStack.initialPage: mainPageComponent
QiFlora {
id: qiflora
}
Component { Component {
id: mainPageComponent id: mainPageComponent
@@ -17,6 +23,9 @@ Kirigami.ApplicationWindow {
mainAction: Kirigami.Action { mainAction: Kirigami.Action {
iconName: "view-refresh" iconName: "view-refresh"
text: i18n("Query Device") text: i18n("Query Device")
onTriggered: {
//TODO
}
} }
title: "Monitor" title: "Monitor"
@@ -53,7 +62,12 @@ Kirigami.ApplicationWindow {
Kirigami.Heading { Kirigami.Heading {
Layouts.Layout.alignment: Qt.AlignCenter Layouts.Layout.alignment: Qt.AlignCenter
level: 3 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 <QQmlApplicationEngine>
#include <QtQml> #include <QtQml>
#include <QUrl> #include <QUrl>
#include "miflora/miflora.h"
Q_DECL_EXPORT int main(int argc, char *argv[]) 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"); QCoreApplication::setApplicationName("qiflora");
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
qmlRegisterType<MiFlora>("org.eyecreate.qiflora",1,0,"QiFlora");
engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
if (engine.rootObjects().isEmpty()) { 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