diff --git a/CMakeLists.txt b/CMakeLists.txt index 50d9fe36fc3c1756c53946f71173fdbfc8f1a16a..434c521419e3e48a9bef39105d6be2418458568e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ include(KDECompilerSettings NO_POLICY_SCOPE) ################# Find dependencies ################# find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Test Gui Svg QuickControls2 Bluetooth Charts) -find_package(KF5Kirigami2 ${KF5_MIN_VERSION}) +find_package(KF5Kirigami2 ${KF5_MIN_VERSION} REQUIRED) ################# Enable C++11 features for clang and gcc ################# diff --git a/org.eyecreate.qiflora.json b/org.eyecreate.qiflora.json index daf59dc7dbc780f727c58313199aa16b6882fe10..4985978a376851b8be93a24c9aaa3b4d0610a1c4 100644 --- a/org.eyecreate.qiflora.json +++ b/org.eyecreate.qiflora.json @@ -1,119 +1,96 @@ { - "id": "org.eyecreate.qiflora", - "runtime": "org.kde.Platform", - "runtime-version": "5.12", - "sdk": "org.kde.Sdk", - "command": "qiflora", - "finish-args": [ - "--share=ipc", - "--allow=bluetooth", - "--system-talk-name=org.bluez", - "--share=network", - "--socket=x11", - "--socket=wayland", - "--device=dri", - "--filesystem=home", - "--talk-name=org.freedesktop.Notifications" - ], - "separate-locales": false, - - "modules": [ + "id": "org.eyecreate.qiflora", + "runtime": "org.kde.Platform", + "command": "qiflora", + "finish-args": [ + "--share=ipc", + "--allow=bluetooth", + "runtime-version": "5.13", + "sdk": "org.kde.Sdk", + "--system-talk-name=org.bluez", + "--share=network", + "--socket=x11", + "--socket=wayland", + "--device=dri", + "--filesystem=home", + "--talk-name=org.freedesktop.Notifications" + ], + "separate-locales": false, + "modules": [ + { + "name": "ical", + "cleanup": [ + "/lib/cmake" + ], + "buildsystem": "cmake-ninja", + "config-opts": [ + "-DCMAKE_BUILD_TYPE=RelWithDebInfo", + "-DCMAKE_INSTALL_LIBDIR=/app/lib", + "-DBUILD_SHARED_LIBS=ON" + ], + "sources": [ { - "name": "udev", - "rm-configure": true, - "config-opts": [ - "--disable-hwdb", - "--disable-logging", - "--disable-introspection", - "--disable-keymap", - "--disable-mtd_probe", - "--with-systemdsystemunitdir=/app/lib/systemd/" - ], - "cleanup": [ - "/include", - "/etc", - "/libexec", - "/sbin", - "/lib/pkgconfig", - "/lib/systemd", - "/man", - "/share/aclocal", - "/share/doc", - "/share/gtk-doc", - "/share/man", - "/share/pkgconfig", - "*.la", - "*.a" - ], - "sources": [ - { - "type": "archive", - "url": "https://www.kernel.org/pub/linux/utils/kernel/hotplug/udev-175.tar.bz2", - "sha256": "4c7937fe5a1521316ea571188745b9a00a9fdf314228cffc53a7ba9e5968b7ab" - }, - { - "type": "patch", - "path": "sysmacros.patch" - }, - { - "type": "script", - "dest-filename": "autogen.sh", - "commands": [ - "autoreconf -vfi" - ] - } - ], - "post-install": [ - "sed -i 's|${exec_prefix}|/app|g' /app/share/pkgconfig/udev.pc" - ] - }, - { - "name": "ical", - "cleanup": [ - "/lib/cmake" - ], - "buildsystem": "cmake-ninja", - "config-opts": [ - "-DCMAKE_BUILD_TYPE=RelWithDebInfo", - "-DCMAKE_INSTALL_LIBDIR=/app/lib", - "-DBUILD_SHARED_LIBS=ON" - ], - "sources": [ { "type": "archive", "url": "https://github.com/libical/libical/archive/v3.0.5.tar.gz", "sha256": "483acbf7fee66ca071c2ff8183e46b6f2b3a89e1e866eadf4870eaaa281c8db1" } ] - }, + "type": "archive", + "url": "https://github.com/libical/libical/archive/v3.0.5.tar.gz", + "sha256": "483acbf7fee66ca071c2ff8183e46b6f2b3a89e1e866eadf4870eaaa281c8db1" + } + ] + }, + { + "name": "bluez", + "config-opts": [ + "--disable-datafiles", + "--disable-systemd", + "--enable-library", + "--prefix=/app", + "--sysconfdir=/app/etc", + "--disable-udev" + ], + "sources": [ { - "name": "bluez", - "config-opts": [ - "--disable-datafiles", - "--disable-systemd", - "--enable-library", - "--prefix=/app", - "--sysconfdir=/app/etc" - ], - "sources": [ { "type": "archive", "url": "https://mirrors.edge.kernel.org/pub/linux/bluetooth/bluez-5.50.tar.xz", "sha256": "5ffcaae18bbb6155f1591be8c24898dc12f062075a40b538b745bfd477481911"} ] - }, + "type": "archive", + "url": "https://mirrors.edge.kernel.org/pub/linux/bluetooth/bluez-5.52.tar.xz", + "sha256": "f7144ce2039202cfac18ccb52426efea11c98e4f6e1bb8041bcb994b8378560a" + } + ] + }, + { + "name": "qtconnectivity", + "buildsystem": "simple", + "cleanup-platform": [ + "/bin", + "/mkspecs" + ], + "sources": [ { - "name": "qtconnectivity", - "buildsystem": "simple", - "cleanup-platform": [ - "/bin", - "/mkspecs" - ], - "sources": [ { "type": "git", "url": "https://github.com/qt/qtconnectivity", "branch": "5.12.4", "commit": "f6be1f73a810514335ab3d27e1d05825a36b06af" } ], - "build-commands": [ - "qmake", - "make -j $FLATPAK_BUILDER_N_JOBS", - "cp -r -n bin /app", - "cp -r -n include /app", - "cp -r -n lib /app", - "mkdir -p /app/src/bluetooth", - "cp -r src/bluetooth /app/src/" - ] - }, + "type": "git", + "url": "https://github.com/qt/qtconnectivity", + "branch": "5.13.2", + "commit": "f6be1f73a810514335ab3d27e1d05825a36b06af" + } + ], + "build-commands": [ + "qmake", + "make -j $FLATPAK_BUILDER_N_JOBS", + "cp -r -n bin /app", + "cp -r -n include /app", + "cp -r -n lib /app", + "mkdir -p /app/src/bluetooth", + "cp -r src/bluetooth /app/src/" + ] + }, + { + "name": "qiflora", + "buildsystem": "cmake-ninja", + "builddir": true, + "sources": [ { - "name": "qiflora", - "buildsystem": "cmake-ninja", - "builddir": true, - "sources": [ { "type": "dir", "path": ".", "skip": [".git"] } ] + "type": "git", + "url": "https://git.eyecreate.org/eyecreate/qiflora.git", + "tag": "v1.0", + "commit": "bcdf831d0c64f84642f53a4f8376a26df412b819" } - ] + ] + } + ] } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 224e44151d1d0317b2f31d6e6ece111567f616a6..3a6b988838a99da639372fa0e0c68a512898095b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ set(qiflora_SRCS miflora/miflora.cpp miflora/bluetoothdevices.cpp + miflora/florahistory.cpp main.cpp ) diff --git a/src/contents/ui/main.qml b/src/contents/ui/main.qml index 038605c2c8ea910a8581ce52a1862151338cbd76..6b3b7fa13f3f4e5c263d0945b7cacecd21dba40a 100644 --- a/src/contents/ui/main.qml +++ b/src/contents/ui/main.qml @@ -31,10 +31,10 @@ Kirigami.ApplicationWindow { title: "Monitor" Component.onCompleted: { - monitorTypes.append({"chartType": "temperature", "title": i18n("Temperature"), "icon": "filename-bpm-amarok", "lineColor": "red"}); - monitorTypes.append({"chartType": "moisture", "title": i18n("Moisture"), "icon": "colors-chromablue", "lineColor": "cyan"}); - monitorTypes.append({"chartType": "conductivity", "title": i18n("Conductivity"), "icon": "quickopen", "lineColor": "green"}); - monitorTypes.append({"chartType": "brightness", "title": i18n("Brightness"), "icon": "contrast", "lineColor": "yellow"}); + monitorTypes.append({"chartType": "temperature", "title": i18n("Temperature"), "icon": "filename-bpm-amarok", "lineColor": "red", "modelCol": 1}); + monitorTypes.append({"chartType": "moisture", "title": i18n("Moisture"), "icon": "colors-chromablue", "lineColor": "cyan", "modelCol": 3}); + monitorTypes.append({"chartType": "conductivity", "title": i18n("Conductivity"), "icon": "quickopen", "lineColor": "yellow", "modelCol": 4}); + monitorTypes.append({"chartType": "brightness", "title": i18n("Brightness"), "icon": "contrast", "lineColor": "orange", "modelCol": 2}); } Kirigami.CardsListView { @@ -82,17 +82,24 @@ Kirigami.ApplicationWindow { legend.visible: false anchors.fill: parent Charts.LineSeries { - axisX: Charts.ValueAxis { + axisX: Charts.DateTimeAxis { labelsColor: Kirigami.Theme.textColor - titleText: i18n("Hours") + format: "MMM d yyyy ha" } axisY: Charts.ValueAxis { labelsColor: Kirigami.Theme.textColor + min: 0 + max: 50 } color: model.lineColor name: model.title - Charts.XYPoint{x: 1; y: 1} - Charts.XYPoint{x: 2; y: 2} + + Charts.VXYModelMapper { + model: qiflora.model + xColumn: 0 + yColumn: 1 + + } } } } @@ -107,7 +114,6 @@ Kirigami.ApplicationWindow { Rectangle { height: 10 color: "transparent" - width: parent.width } Kirigami.Heading { text: i18n("Select Device to Query") @@ -115,7 +121,6 @@ Kirigami.ApplicationWindow { Rectangle { height: 10 color: "transparent" - width: parent.width } } id: deviceList diff --git a/src/main.cpp b/src/main.cpp index 9544a8ace9df8426a00f17f006c79614746e5138..bb86898201983567899aec3a9e518db898e31337 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("eyecreate"); QCoreApplication::setOrganizationDomain("eyecreate.org"); QCoreApplication::setApplicationName("qiflora"); + QQmlApplicationEngine engine; qmlRegisterType(); diff --git a/src/miflora/florahistory.cpp b/src/miflora/florahistory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8032e6c35fb47efba411848ca1e9a85605d82ad4 --- /dev/null +++ b/src/miflora/florahistory.cpp @@ -0,0 +1,93 @@ +/* + * 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 "florahistory.h" + +QVariant FloraHistory::data(const QModelIndex& index, int role) const +{ + if(role == Qt::DisplayRole && tableData.size()-1 >= index.row()) { + if(index.column() == 0) { + return QVariant(tableData[index.row()].time.toMSecsSinceEpoch()); + } else if(index.column() == 1) { + return QVariant(tableData[index.row()].temperature); + } else if(index.column() == 2) { + return QVariant(tableData[index.row()].brightness); + } else if(index.column() == 3) { + return QVariant(tableData[index.row()].moisture); + } else if(index.column() == 4) { + return QVariant(tableData[index.row()].conductivity); + } else { + return QVariant(); + } + } else { + return QVariant(); + } +} + +int FloraHistory::columnCount(const QModelIndex& parent) const +{ + return 5; +} + +int FloraHistory::rowCount(const QModelIndex& parent) const +{ + //return tableData.size(); + return 23; +} + +QVariant FloraHistory::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(role == Qt::DisplayRole) { + if(section == 0) { + return QVariant("Time"); + } else if(section == 1) { + return QVariant("Temperature"); + } else if(section == 2) { + return QVariant("Brightness"); + } else if(section == 3) { + return QVariant("Moisture"); + } else if(section == 4) { + return QVariant("Conductivity"); + } else { + return QVariant(); + } + } else { + return QVariant(); + } +} + +Qt::ItemFlags FloraHistory::flags(const QModelIndex& index) const +{ + return QAbstractTableModel::flags(index); +} + +void FloraHistory::addData(QDateTime time, float temperature, quint32 brightness, quint8 moisture, quint16 conductivity) +{ + //beginInsertRows(QModelIndex(),tableData.size(),tableData.size()); + tableData.append(flora_data{time, temperature, brightness, moisture, conductivity}); + //emit dataChanged(createIndex(0,0), createIndex(tableData.size()-1,4)); + //endInsertRows(); + emit dataChanged(createIndex(0,0),createIndex(23,4)); +} + +void FloraHistory::clear() +{ + tableData.clear(); + emit layoutChanged(); +} + + diff --git a/src/miflora/florahistory.h b/src/miflora/florahistory.h new file mode 100644 index 0000000000000000000000000000000000000000..1fc2883999b46014ab5c99cdf5acdc9ecab77dc6 --- /dev/null +++ b/src/miflora/florahistory.h @@ -0,0 +1,61 @@ +/* + * 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 FLORAHISTORY_H +#define FLORAHISTORY_H + +#include +#include +#include + +/** + * Model class to hold history data from MiFlora device. + */ +class FloraHistory : public QAbstractTableModel +{ + Q_OBJECT + +public: + + QVariant data(const QModelIndex& index, int role) const override; + + int columnCount(const QModelIndex& parent) const override; + + int rowCount(const QModelIndex& parent) const override; + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + Qt::ItemFlags flags(const QModelIndex & index) const override; + + void clear(); + + void addData(QDateTime time, float temperature, quint32 brightness, quint8 moisture, quint16 conductivity); + +private: + struct flora_data { + QDateTime time; + float temperature; + quint32 brightness; + quint8 moisture; + quint16 conductivity; + + }; + QList tableData; + +}; + +#endif // FLORAHISTORY_H diff --git a/src/miflora/miflora.cpp b/src/miflora/miflora.cpp index 0edeb27313cdbc654d7ffa43d4ef618a885002f7..b09295e598a0f2081bb35242ac5e0db0e0dfc0ce 100644 --- a/src/miflora/miflora.cpp +++ b/src/miflora/miflora.cpp @@ -55,6 +55,11 @@ void MiFlora::updateDataFromDevice ( QString mac ) } } +QAbstractTableModel * MiFlora::getModel() +{ + return floraModel; +} + void MiFlora::foundDevice ( const QBluetoothDeviceInfo& device ) { if(device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration && device.address().toString().startsWith("C4:7C")) { @@ -80,12 +85,24 @@ void MiFlora::logServiceError(QLowEnergyService::ServiceError err) } -void MiFlora::serviceStateChanges ( QLowEnergyService::ServiceState state ) +void MiFlora::sensorServiceStateChanges ( QLowEnergyService::ServiceState state ) { if(state == QLowEnergyService::ServiceState::ServiceDiscovered) { for(QLowEnergyCharacteristic chrt : sensorService->characteristics()) { if(chrt.uuid().toUInt16() == magicChar) { - sensorService->writeCharacteristic(chrt, QByteArray::fromHex("a01f")); + sensorService->writeCharacteristic(chrt, magicBytes); + } + } + } +} + +void MiFlora::historyServiceStateChanges ( QLowEnergyService::ServiceState state ) +{ + if(state == QLowEnergyService::ServiceState::ServiceDiscovered) { + for(QLowEnergyCharacteristic chrt : historyService->characteristics()) { + if(chrt.uuid().toUInt16() == historyTimeChar) { + qDebug() << "Asking for device's view on time."; + historyService->readCharacteristic(chrt); } } } @@ -134,15 +151,137 @@ void MiFlora::serviceCharRead(QLowEnergyCharacteristic readChar, QByteArray valu } } +QLowEnergyCharacteristic MiFlora::getCharFromValue(QLowEnergyService *service, quint16 characteristic) +{ + for(QLowEnergyCharacteristic chrt : service->characteristics()) { + if(chrt.uuid().toUInt16() == characteristic) { + return chrt; + } + } + return QLowEnergyCharacteristic(); +} + +void MiFlora::historyServiceCharRead(QLowEnergyCharacteristic readChar, QByteArray value) +{ + if(readChar.uuid().toUInt16() == historyReaderChar) { + QDataStream parser(value); + parser.setByteOrder(QDataStream::LittleEndian); + quint16 size; + parser >> size; + //change connect signals/slots to perform history read task. + disconnect(historyService, &QLowEnergyService::characteristicRead, this, &MiFlora::historyServiceCharRead); + connect(historyService, &QLowEnergyService::characteristicRead, this, &MiFlora::historyServiceCharReadData); + disconnect(historyService, &QLowEnergyService::characteristicWritten, this, &MiFlora::historyServiceCharWritten); + connect(historyService, &QLowEnergyService::characteristicWritten, this, &MiFlora::historyServiceCharWrittenData); + //Take size given and start by giving the last 24 numbers(if that many) to represent the last day of data. TODO: look to have amount of time be configurable. + lastHistoryEntry = 0; + if(size > 24) { + currentHistoryEntry = 24; + } else { + currentHistoryEntry = size; + } + floraModel->clear(); + //Ask for first history value + QByteArray historyIndex(QByteArray::fromHex("a1")); + QDataStream historyParser(&historyIndex, QIODevice::ReadWrite); + historyParser.setByteOrder(QDataStream::LittleEndian); + historyParser.skipRawData(1); + historyParser << currentHistoryEntry; + historyService->writeCharacteristic(getCharFromValue(historyService, historyControllerChar), historyIndex); + } else if(readChar.uuid().toUInt16() == historyTimeChar) { + //Determine when device started counting by comparing system and device time. + quint32 time; + QDataStream parser(value); + parser.setByteOrder(QDataStream::LittleEndian); + parser >> time; + qint64 systemTime = QDateTime::currentSecsSinceEpoch(); + deviceStartTime = systemTime - time; + //Now we start the history reading process. + qDebug() << "Writing history init."; + historyService->writeCharacteristic(getCharFromValue(historyService, historyControllerChar), historyReadInit); + } +} + +void MiFlora::historyServiceCharReadData(QLowEnergyCharacteristic readChar, QByteArray value) +{ + //We are in a read loop. Read data and determine if we should stop asking for history. + if(value == QByteArray::fromHex("ffffffffffffffffffffffffffffffff") || value == QByteArray::fromHex("00000000000000000000000000000000") || value == QByteArray::fromHex("aabbccddeeff99887766554433221110")) { + qDebug() << "invalid history response:" << value; + } else { + quint16 origTemp; + quint32 time; + quint32 bright; + quint8 moisture; + quint16 conduct; + float temp; + QDataStream parser(value); + parser.setByteOrder(QDataStream::LittleEndian); + parser >> time; + parser >> origTemp; + temp = origTemp/ 10.0; //original value in 0.1 C + parser.skipRawData(1); + parser >> bright; + parser >> moisture; + parser >> conduct; + QDateTime convTime; + convTime.setSecsSinceEpoch(deviceStartTime+time); + qDebug() << convTime.toString(Qt::ISODate) << temp << bright << moisture << conduct; + //Write out items to table + floraModel->addData(convTime, temp, bright, moisture, conduct); + //emit historyUpdated(); + } + + if(lastHistoryEntry == currentHistoryEntry) { + disconnect(historyService, &QLowEnergyService::characteristicRead, this, &MiFlora::historyServiceCharReadData); + connect(historyService, &QLowEnergyService::characteristicRead, this, &MiFlora::historyServiceCharRead); + disconnect(historyService, &QLowEnergyService::characteristicWritten, this, &MiFlora::historyServiceCharWrittenData); + connect(historyService, &QLowEnergyService::characteristicWritten, this, &MiFlora::historyServiceCharWritten); + } else { + //Get next history entry + currentHistoryEntry -= 1; + QByteArray historyIndex(QByteArray::fromHex("a1")); + QDataStream parser(&historyIndex, QIODevice::ReadWrite); + parser.setByteOrder(QDataStream::LittleEndian); + parser.skipRawData(1); + parser << currentHistoryEntry; + historyService->writeCharacteristic(getCharFromValue(historyService, historyControllerChar), historyIndex); + } +} + +void MiFlora::historyServiceCharWrittenData(QLowEnergyCharacteristic changedChar, QByteArray value) +{ + //We are in a read loop. Ask to read from history what should have just been requested by a write. + historyService->readCharacteristic(getCharFromValue(historyService, historyReaderChar)); +} + + +void MiFlora::historyServiceCharWritten(QLowEnergyCharacteristic changedChar, QByteArray value) +{ + if(changedChar.uuid().toUInt16() == historyControllerChar) { + for(QLowEnergyCharacteristic chrt : historyService->characteristics()) { + if(chrt.uuid().toUInt16() == historyReaderChar) { + historyService->readCharacteristic(chrt); + } + } + } +} + void MiFlora::serviceDiscovered ( const QBluetoothUuid& gatt ) { - if(gatt == QBluetoothUuid(dataService)) { + if(gatt == QBluetoothUuid(dataServiceChar)) { sensorService = currentController->createServiceObject(gatt); - connect(sensorService, &QLowEnergyService::stateChanged, this, &MiFlora::serviceStateChanges); + connect(sensorService, &QLowEnergyService::stateChanged, this, &MiFlora::sensorServiceStateChanges); 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(); + } else if(gatt == QBluetoothUuid(historyServiceChar)) { + historyService = currentController->createServiceObject(gatt); + connect(historyService, &QLowEnergyService::stateChanged, this, &MiFlora::historyServiceStateChanges); + connect(historyService, static_cast(&QLowEnergyService::error), this, &MiFlora::logServiceError); + connect(historyService, &QLowEnergyService::characteristicWritten, this, &MiFlora::historyServiceCharWritten); + connect(historyService, &QLowEnergyService::characteristicRead, this, &MiFlora::historyServiceCharRead); + historyService->discoverDetails(); } } diff --git a/src/miflora/miflora.h b/src/miflora/miflora.h index d0a23dbc1622ab7d7cb4caf34d090d1e391eb4c2..55f3718d88a9da10a1c3d1732a4cfd795bc346db 100644 --- a/src/miflora/miflora.h +++ b/src/miflora/miflora.h @@ -26,6 +26,10 @@ #include #include "bluetoothdevices.h" #include +#include +#include +#include +#include "florahistory.h" /** * Class using QtBluetooth to find and retrive info from Mi Flora devices. @@ -39,6 +43,7 @@ class MiFlora : public QObject Q_PROPERTY(quint16 conduction NOTIFY conductionChanged MEMBER conduct) Q_PROPERTY(quint8 battery NOTIFY batteryChanged MEMBER battery) Q_PROPERTY(QQmlListProperty devices READ getDeviceList NOTIFY newDeviceFound) + Q_PROPERTY(QAbstractTableModel* model READ getModel NOTIFY historyUpdated) public: Q_INVOKABLE void startSearch(); @@ -46,10 +51,11 @@ public: Q_INVOKABLE void updateDataFromDevice(QString mac); QQmlListProperty getDeviceList(); - //TODO:History? + QAbstractTableModel* getModel(); signals: void newDeviceFound(); + void historyUpdated(); void temperatureChanged(float temperature); void brightnessChanged(quint32 brightness); void moistureChanged(quint8 moisture); @@ -61,19 +67,33 @@ private: void serviceDiscovered(const QBluetoothUuid &gatt); void logControllerError(QLowEnergyController::Error err); void logServiceError(QLowEnergyService::ServiceError err); - void serviceStateChanges(QLowEnergyService::ServiceState state); + void sensorServiceStateChanges(QLowEnergyService::ServiceState state); + void historyServiceStateChanges(QLowEnergyService::ServiceState state); void serviceCharWritten(QLowEnergyCharacteristic changedChar, QByteArray value); void serviceCharRead(QLowEnergyCharacteristic readChar, QByteArray value); + void historyServiceCharWritten(QLowEnergyCharacteristic changedChar, QByteArray value); + void historyServiceCharWrittenData(QLowEnergyCharacteristic changedChar, QByteArray value); + void historyServiceCharRead(QLowEnergyCharacteristic readChar, QByteArray value); + void historyServiceCharReadData(QLowEnergyCharacteristic readChar, QByteArray value); + QLowEnergyCharacteristic getCharFromValue(QLowEnergyService *service, quint16 characteristic); QBluetoothDeviceDiscoveryAgent *agent; QList devices; QLowEnergyController *currentController; QLowEnergyService *sensorService; + QLowEnergyService *historyService; - const quint16 dataService = 4612; //0x1204 + const quint16 dataServiceChar = 4612; //0x1204 + const quint16 historyServiceChar = 4614; //0x1206 const quint16 batteryFirmwareChar = 6658;//0x1a02 const quint16 sensorsChar = 6657; //0x1a01 const quint16 magicChar = 6656; //0x1a00 + const quint16 historyControllerChar = 6672; //0x1a10 + const quint16 historyReaderChar = 6673; //0x1a11 + const quint16 historyTimeChar = 6674; //0x1a12 + const QByteArray magicBytes = QByteArray::fromHex("a01f"); + const QByteArray historyReadInit = QByteArray::fromHex("a00000"); + float temp = 0.0; quint32 bright = 0; @@ -81,6 +101,11 @@ private: quint16 conduct = 0; quint8 battery = 0; QString version; + qint64 deviceStartTime; + qint16 currentHistoryEntry; + qint16 lastHistoryEntry; + + FloraHistory *floraModel = new FloraHistory(); };