diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88331dc8a503e84b85230c50bba39ccf062597d6..98fac95532a055b06e405d1fa1c4fc47420b3673 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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}) diff --git a/src/contents/ui/main.qml b/src/contents/ui/main.qml index ed54778edf8949266bd62ca18e383945ff727a7e..f5472548dc82f81fe9f2ade3b2d39f59c1e7d69b 100644 --- a/src/contents/ui/main.qml +++ b/src/contents/ui/main.qml @@ -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" + } } } } diff --git a/src/main.cpp b/src/main.cpp index 3c34d25b44e14368ba8392a8482d28c6682ee255..384a0b4ef5b1dcb7d5c56695027f4c0722f60600 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#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("org.eyecreate.qiflora",1,0,"QiFlora"); engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); if (engine.rootObjects().isEmpty()) { diff --git a/src/miflora/miflora.cpp b/src/miflora/miflora.cpp new file mode 100644 index 0000000000000000000000000000000000000000..840cc45ecd7ad970f7b85d11a24b63e2e3293195 --- /dev/null +++ b/src/miflora/miflora.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 Kevin Whitaker + * + * 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 . + */ + +#include "miflora.h" +#include + +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(&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(&QLowEnergyService::error), this, &MiFlora::logServiceError); + connect(sensorService, &QLowEnergyService::characteristicWritten, this, &MiFlora::serviceCharWritten); + connect(sensorService, &QLowEnergyService::characteristicRead, this, &MiFlora::serviceCharRead); + sensorService->discoverDetails(); + } +} + + + diff --git a/src/miflora/miflora.h b/src/miflora/miflora.h new file mode 100644 index 0000000000000000000000000000000000000000..13b214a1da44941e02a59ff769140486560e34d0 --- /dev/null +++ b/src/miflora/miflora.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 Kevin Whitaker + * + * 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 . + */ + +#ifndef MIFLORA_H +#define MIFLORA_H + +#include +#include +#include +#include +#include +#include + +/** + * 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 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