20 Commits
v1.0 ... v1.1.2

Author SHA1 Message Date
23fdbd27b6 Update screenshots. 2019-11-15 12:45:34 -05:00
bdebc0833e fix using wrong translation function and bump version. 2019-11-15 12:40:59 -05:00
0ec59fc409 update wording to specify plants. 2019-11-14 11:08:10 -05:00
ad1fdee6c4 update images with latest changes 2019-11-14 11:06:18 -05:00
c9720c9dea update commit 2019-11-14 10:56:25 -05:00
16db502a04 updated version 2019-11-14 10:55:31 -05:00
003a484fc4 Added units of graphs and rounded temperature. 2019-11-14 10:53:42 -05:00
f0d203c6d6 make sure next build is a release build. 2019-11-13 22:26:15 -05:00
9e7bf90831 another flathub build update. 2019-11-13 22:16:44 -05:00
93a71a56e1 Flip order of releases as they are in reverse order of what I expected. 2019-11-13 22:14:56 -05:00
0df4b858d0 Point to new commit for release. 2019-11-13 22:02:42 -05:00
9bad2c73ff Remove uris, since flathub doesn't like them. 2019-11-13 22:00:23 -05:00
60e5c8b0df Update build script with new tag. 2019-11-13 21:47:56 -05:00
90da697f84 Update packaging and use KF5 for about screen. Implement basic notifications for error messages. Use icon for app icon. 2019-11-13 21:43:58 -05:00
9c5c50ef95 Move extra files to packaging. 2019-11-13 16:27:28 -05:00
e8d666d922 Clean up some old test code and make sure graphs have right paramaters to be viewed. 2019-11-13 16:23:51 -05:00
1303626dd8 replace flatpak config with published one. Only builds from git now.
Made large progress on getting history from device.
2019-11-12 23:14:40 -05:00
ed00901a2f Add themed chart layout to gui. Needs backend code. 2019-11-11 15:00:07 -05:00
f6f574875d remove unneeded tag 2019-11-11 11:13:20 -05:00
b35385b306 Quickly add Fahrenheit to temp screen. 2019-11-08 16:05:40 -05:00
16 changed files with 644 additions and 208 deletions

View File

@@ -1,8 +1,8 @@
project(qiflora) project(qiflora)
cmake_minimum_required(VERSION 2.8.12) cmake_minimum_required(VERSION 2.8.12)
set(KF5_MIN_VERSION "5.18.0") set(KF5_MIN_VERSION "5.44.0")
set(QT_MIN_VERSION "5.5.0") set(QT_MIN_VERSION "5.12.0")
################# Disallow in-source build ################# ################# Disallow in-source build #################
@@ -28,8 +28,9 @@ include(KDECompilerSettings NO_POLICY_SCOPE)
################# Find dependencies ################# ################# Find dependencies #################
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Test Gui Svg QuickControls2 Bluetooth) 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)
find_package(KF5CoreAddons ${KF5_MIN_VERSION} REQUIRED)
################# Enable C++11 features for clang and gcc ################# ################# Enable C++11 features for clang and gcc #################
@@ -38,8 +39,8 @@ set(CMAKE_CXX_STANDARD 11)
################# build and install ################# ################# build and install #################
add_subdirectory(src) add_subdirectory(src)
install(PROGRAMS org.eyecreate.qiflora.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(PROGRAMS packaging/org.eyecreate.qiflora.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES org.eyecreate.qiflora.svg DESTINATION ${KDE_INSTALL_ICONDIR}) install(FILES packaging/org.eyecreate.qiflora.svg DESTINATION ${KDE_INSTALL_ICONDIR})
install(FILES org.eyecreate.qiflora.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES packaging/org.eyecreate.qiflora.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop-application">
<id>org.eyecreate.qiflora</id>
<name>QiFlora</name>
<summary>Mobile friendly application to monitor Mi Flora devices.</summary>
<metadata_license>FSFAP</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<content_rating type="oars-1.1" />
<url type="homepage">https://git.eyecreate.org/eyecreate/qiflora</url>
<developer_name>eyecreate</developer_name>
<description>
<p>Mobile friendly application to monitor Mi Flora devices.</p>
<p>Plants using this device will have thier data logged when app is used and graphed to better monitor plant health.</p>
</description>
<screenshots>
<screenshot type="default">
<image type="source">https://git.eyecreate.org/eyecreate/qiflora/raw/v1.0/packaging/main_window.png</image>
</screenshot>
</screenshots>
<releases>
<release version="1.0" date="2019-11-8" type="stable"/>
</releases>
</component>

View File

@@ -1,120 +0,0 @@
{
"id": "org.eyecreate.qiflora",
"runtime": "org.kde.Platform",
"runtime-version": "5.12",
"sdk": "org.kde.Sdk",
"command": "qiflora",
"tags": ["kde"],
"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": [
{
"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" } ]
},
{
"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"} ]
},
{
"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/"
]
},
{
"name": "qiflora",
"buildsystem": "cmake-ninja",
"builddir": true,
"sources": [ { "type": "dir", "path": ".", "skip": [".git"] } ]
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 129 KiB

BIN
packaging/mobile_window.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop-application">
<id>org.eyecreate.qiflora</id>
<name>QiFlora</name>
<summary>Mobile friendly application to monitor plants using Mi Flora devices.</summary>
<metadata_license>FSFAP</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<content_rating type="oars-1.1" />
<url type="homepage">https://git.eyecreate.org/eyecreate/qiflora</url>
<url type="contact">https://www.eyecreate.org</url>
<developer_name>eyecreate</developer_name>
<description>
<p>Mobile friendly application to monitor Mi Flora devices.</p>
<p>Plants using this device will have thier data logged when app is used and graphed to better monitor plant health.</p>
</description>
<screenshots>
<screenshot type="default">
<image type="source">https://git.eyecreate.org/eyecreate/qiflora/raw/v1.1.2/packaging/main_window.png</image>
</screenshot>
<screenshot>
<image type="source">https://git.eyecreate.org/eyecreate/qiflora/raw/v1.1.2/packaging/mobile_window.png</image>
</screenshot>
</screenshots>
<releases>
<release version="1.1.2" date="2019-11-15" type="stable">
<description>
<p>Fixed code causing text to sometimes not appear.</p>
</description>
</release>
<release version="1.1.1" date="2019-11-14" type="stable">
<description>
<p>Cleaned up graph markings and temperature rounding.</p>
</description>
</release>
<release version="1.1" date="2019-11-13" type="stable">
<description>
<p>This release adds history graph for the last 48 hours.</p>
</description>
</release>
<release version="1.0" date="2019-11-8" type="stable"/>
</releases>
</component>

View File

@@ -1,7 +1,7 @@
[Desktop Entry] [Desktop Entry]
Name=QiFlora Name=QiFlora
Comment=Monitor plants with Mi Flora sensors. Comment=Monitor plants with Mi Flora sensors.
Version=1.0 Version=1.1.2
Exec=qiflora Exec=qiflora
MimeType=application/x-qiflora; MimeType=application/x-qiflora;
Icon=org.eyecreate.qiflora Icon=org.eyecreate.qiflora

View File

@@ -0,0 +1,99 @@
{
"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": [
{
"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": [
{
"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": [
{
"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,
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release"
]
"sources": [
{
"type": "git",
"url": "https://git.eyecreate.org/eyecreate/qiflora.git",
"tag": "v1.1.2",
"commit": "16db502a0460139dcff6a63df90c218e3e4c1d66"
}
]
}
]
}

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -2,10 +2,11 @@
set(qiflora_SRCS set(qiflora_SRCS
miflora/miflora.cpp miflora/miflora.cpp
miflora/bluetoothdevices.cpp miflora/bluetoothdevices.cpp
miflora/florahistory.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 Qt5::Bluetooth) target_link_libraries(qiflora Qt5::Core Qt5::Qml Qt5::Quick Qt5::Svg Qt5::Bluetooth Qt5::Charts KF5::CoreAddons)
install(TARGETS qiflora ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS qiflora ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})

View File

@@ -1,8 +1,9 @@
import QtQuick 2.6 import QtQuick 2.6
import org.kde.kirigami 2.4 as Kirigami import org.kde.kirigami 2.6 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 import QtCharts 2.3 as Charts
import org.eyecreate.qiflora 1.1
Kirigami.ApplicationWindow { Kirigami.ApplicationWindow {
id: root id: root
@@ -11,6 +12,7 @@ Kirigami.ApplicationWindow {
pageStack.initialPage: mainPageComponent pageStack.initialPage: mainPageComponent
contextDrawer: Kirigami.ContextDrawer {}
QiFlora { QiFlora {
id: qiflora id: qiflora
@@ -19,33 +21,99 @@ Kirigami.ApplicationWindow {
Component { Component {
id: mainPageComponent id: mainPageComponent
Kirigami.ScrollablePage { Kirigami.Page {
id: mainPage
mainAction: Kirigami.Action { mainAction: Kirigami.Action {
iconName: "view-refresh" iconName: "view-refresh"
text: i18n("Query Device") text: qsTr("Query Device")
onTriggered: { onTriggered: {
deviceSelect.open(); deviceSelect.open();
} }
} }
contextualActions: [
Kirigami.Action {
iconName: "help-about"
text: qsTr("About")
onTriggered: {
pageStack.replace(aboutPageComponent);
}
}
]
title: "Monitor" title: "Monitor"
leftPadding: 0
rightPadding: 0
Component.onCompleted: { Layouts.ColumnLayout {
monitorTypes.append({"chartType": "temperature", "title": i18n("Temperature"), "icon": "filename-bpm-amarok"}); anchors.fill: parent
monitorTypes.append({"chartType": "moisture", "title": i18n("Moisture"), "icon": "colors-chromablue"}); Kirigami.InlineMessage {
monitorTypes.append({"chartType": "conductivity", "title": i18n("Conductivity"), "icon": "quickopen"}); Layouts.Layout.fillWidth: true
monitorTypes.append({"chartType": "brightness", "title": i18n("Brightness"), "icon": "contrast"}); Layouts.Layout.leftMargin: 10
Layouts.Layout.rightMargin: 10
z: 9997
type: Kirigami.MessageType.Error
id: errorMessage
showCloseButton: true
Connections {
target: qiflora
onErrorHappened: {
errorMessage.text = description;
errorMessage.visible = true;
}
}
} }
Kirigami.CardsListView { Kirigami.CardsListView {
Layouts.Layout.fillWidth: true
Layouts.Layout.fillHeight: true
id: monitorView id: monitorView
model: ListModel { model: ListModel {
id: monitorTypes id: monitorTypes
ListElement{
chartType: "temperature"
title: "Temperature"
icon: "filename-bpm-amarok"
lineColor: "red"
units: "&deg;C"
yMin: -20
yMax: 40
modelCol: 1
}
ListElement{
chartType: "moisture"
title: "Moisture"
icon: "colors-chromablue"
lineColor: "cyan"
units: "%"
yMin: 0
yMax: 75
modelCol: 3
}
ListElement{
chartType: "conductivity"
title: "Conductivity"
icon: "quickopen"
lineColor: "gold"
units: "&#181;S/cm"
yMin: 0
yMax: 6000
modelCol: 4
}
ListElement{
chartType: "brightness"
title: "Brightness"
icon: "contrast"
lineColor: "orange"
units: "lux"
yMin: 0
yMax: 30000
modelCol: 2
}
} }
delegate: Kirigami.Card { delegate: Kirigami.Card {
id: card id: card
banner { banner {
title: model.title title: qsTr(model.title)
titleIcon: model.icon titleIcon: model.icon
titleLevel: 2 titleLevel: 2
} }
@@ -57,13 +125,13 @@ Kirigami.ApplicationWindow {
Kirigami.Heading { Kirigami.Heading {
Layouts.Layout.alignment: Qt.AlignCenter Layouts.Layout.alignment: Qt.AlignCenter
level: 4 level: 4
text: i18n("Last Measured") text: qsTr("Last Measured")
} }
Kirigami.Heading { Kirigami.Heading {
Layouts.Layout.alignment: Qt.AlignCenter Layouts.Layout.alignment: Qt.AlignCenter
level: 3 level: 3
text: { text: {
if(model.chartType == "temperature") qiflora.temperature + "°C" if(model.chartType == "temperature") Math.round(qiflora.temperature*10)/10 + "°C\n" + (Math.round((qiflora.temperature*1.8+32)*10)/10) + "°F"
else if(model.chartType == "moisture") qiflora.moisture + "%" else if(model.chartType == "moisture") qiflora.moisture + "%"
else if(model.chartType == "conductivity") qiflora.conduction + " µS/cm" else if(model.chartType == "conductivity") qiflora.conduction + " µS/cm"
else if(model.chartType == "brightness") qiflora.brightness + " lux" else if(model.chartType == "brightness") qiflora.brightness + " lux"
@@ -71,11 +139,62 @@ Kirigami.ApplicationWindow {
} }
} }
} }
contentItem: Controls.Label { contentItem: Item {
wrapMode: Text.WordWrap implicitWidth: 300
text: i18n("") //TODO: replace with graph? implicitHeight: 200
Charts.ChartView {
id: chart
antialiasing: true
backgroundColor: Kirigami.Theme.buttonBackgroundColor
titleColor: Kirigami.Theme.textColor
legend.visible: false
anchors.fill: parent
Charts.LineSeries {
id: series
axisX: Charts.DateTimeAxis {
id: dateAx
labelsColor: Kirigami.Theme.textColor
format: "MMM d yyyy ha"
Component.onCompleted: {
//On load, change date span to last 24 to remove awkward 1969 empty dates.
var yesterday = new Date();
yesterday.setDate(yesterday.getDate() -1);
dateAx.min = yesterday;
dateAx.max = new Date();
} }
} }
axisY: Charts.ValueAxis {
id: valueAx
labelFormat: "%d "+units
labelsColor: Kirigami.Theme.textColor
min: yMin
max: yMax
}
color: model.lineColor
name: model.title
Charts.VXYModelMapper {
model: qiflora.model
xColumn: 0
yColumn: modelCol
}
}
Connections {
target: qiflora.model
onRowsInserted: {
//Readjust graphs to show date range from earliest item.
dateAx.max = new Date();
dateAx.min = qiflora.model.data(qiflora.model.index(0,0));
}
}
}
}
}
Controls.ScrollBar.vertical: Controls.ScrollBar {}
}
} }
Kirigami.OverlaySheet { Kirigami.OverlaySheet {
@@ -86,15 +205,13 @@ Kirigami.ApplicationWindow {
Rectangle { Rectangle {
height: 10 height: 10
color: "transparent" color: "transparent"
width: parent.width
} }
Kirigami.Heading { Kirigami.Heading {
text: i18n("Select Device to Query") text: qsTr("Select Device to Query")
} }
Rectangle { Rectangle {
height: 10 height: 10
color: "transparent" color: "transparent"
width: parent.width
} }
} }
id: deviceList id: deviceList
@@ -126,4 +243,20 @@ Kirigami.ApplicationWindow {
} }
} }
} }
Component {
id:aboutPageComponent
Kirigami.AboutPage {
id: aboutPage
actions.main: Kirigami.Action {
iconName: "window-close"
text: qsTr("Close")
onTriggered: {
pageStack.clear();
pageStack.push(mainPageComponent);
}
}
aboutData: appAboutData
}
}
} }

View File

@@ -4,18 +4,30 @@
#include <QUrl> #include <QUrl>
#include "miflora/miflora.h" #include "miflora/miflora.h"
#include "miflora/bluetoothdevices.h" #include "miflora/bluetoothdevices.h"
#include <KAboutData>
#include <QIcon>
Q_DECL_EXPORT int main(int argc, char *argv[]) Q_DECL_EXPORT int main(int argc, char *argv[])
{ {
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv); QApplication app(argc, argv);
KAboutData aboutData("org.eyecreate.qiflora", "QiFlora", "1.1.2", "Mobile friendly application to monitor Mi Flora devices.",KAboutLicense::GPL_V3);//TODO:i18n
aboutData.setProductName("qiflora");
aboutData.addAuthor("Kevin Whitaker",QString(),"eyecreate@eyecreate.org","https://www.eyecreate.org");
aboutData.setDesktopFileName("org.eyecreate.qiflora");
QCoreApplication::setOrganizationName("eyecreate"); QCoreApplication::setOrganizationName("eyecreate");
QCoreApplication::setOrganizationDomain("eyecreate.org"); QCoreApplication::setOrganizationDomain("eyecreate.org");
QCoreApplication::setApplicationName("qiflora"); QCoreApplication::setApplicationName(aboutData.productName());
QCoreApplication::setApplicationVersion(aboutData.version());
app.setWindowIcon(QIcon::fromTheme("org.eyecreate.qiflora"));
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
qmlRegisterType<BluetoothDevices>(); qmlRegisterType<BluetoothDevices>();
qmlRegisterType<MiFlora>("org.eyecreate.qiflora",1,0,"QiFlora"); qmlRegisterType<MiFlora>("org.eyecreate.qiflora",1,1,"QiFlora");
engine.rootContext()->setContextProperty(QStringLiteral("appAboutData"), QVariant::fromValue(aboutData));
engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
if (engine.rootObjects().isEmpty()) { if (engine.rootObjects().isEmpty()) {

View File

@@ -0,0 +1,68 @@
/*
* 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 "florahistory.h"
#include <QDebug>
#include <cmath>
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);
} else if(index.column() == 1) {
return QVariant(std::round(tableData[index.row()].temperature*10)/10); //temp in C
} 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 if(index.column() == 5) {
return QVariant((std::round((tableData[index.row()].temperature*1.8+32)*10)/10)); //temp in F
} else {
return QVariant();
}
} else {
return QVariant();
}
}
int FloraHistory::columnCount(const QModelIndex& parent) const
{
return 6;
}
int FloraHistory::rowCount(const QModelIndex& parent) const
{
return tableData.size();
}
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});
endInsertRows();
}
void FloraHistory::clear()
{
tableData.clear();
emit layoutChanged();
}

View File

@@ -0,0 +1,57 @@
/*
* 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 FLORAHISTORY_H
#define FLORAHISTORY_H
#include <qabstractitemmodel.h>
#include <QDateTime>
#include <QList>
/**
* Model class to hold history data from MiFlora device.
*/
class FloraHistory : public QAbstractTableModel
{
Q_OBJECT
public:
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex& parent) const override;
int rowCount(const QModelIndex& parent) 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<flora_data> tableData;
};
#endif // FLORAHISTORY_H

View File

@@ -55,6 +55,11 @@ void MiFlora::updateDataFromDevice ( QString mac )
} }
} }
QAbstractTableModel * MiFlora::getModel()
{
return floraModel;
}
void MiFlora::foundDevice ( const QBluetoothDeviceInfo& device ) void MiFlora::foundDevice ( const QBluetoothDeviceInfo& device )
{ {
if(device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration && device.address().toString().startsWith("C4:7C")) { if(device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration && device.address().toString().startsWith("C4:7C")) {
@@ -72,20 +77,34 @@ QQmlListProperty<BluetoothDevices> MiFlora::getDeviceList()
void MiFlora::logControllerError ( QLowEnergyController::Error err ) void MiFlora::logControllerError ( QLowEnergyController::Error err )
{ {
qDebug() << "Error:" << err; qDebug() << "Error:" << err;
emit errorHappened(currentController->errorString());
} }
void MiFlora::logServiceError(QLowEnergyService::ServiceError err) void MiFlora::logServiceError(QLowEnergyService::ServiceError err)
{ {
qDebug() << "Service Error:" << err; qDebug() << "Service Error:" << err;
emit errorHappened("Possible Read/Write error!");
} }
void MiFlora::serviceStateChanges ( QLowEnergyService::ServiceState state ) void MiFlora::sensorServiceStateChanges ( QLowEnergyService::ServiceState state )
{ {
if(state == QLowEnergyService::ServiceState::ServiceDiscovered) { if(state == QLowEnergyService::ServiceState::ServiceDiscovered) {
for(QLowEnergyCharacteristic chrt : sensorService->characteristics()) { for(QLowEnergyCharacteristic chrt : sensorService->characteristics()) {
if(chrt.uuid().toUInt16() == magicChar) { 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 +153,135 @@ 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 48 numbers(if that many) to represent the last day of data. TODO: look to have amount of time be configurable.
lastHistoryEntry = 0;
if(size > 48) {
currentHistoryEntry = 48;
} 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);
//Write out items to table
floraModel->addData(convTime, temp, bright, moisture, conduct);
}
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 ) void MiFlora::serviceDiscovered ( const QBluetoothUuid& gatt )
{ {
if(gatt == QBluetoothUuid(dataService)) { if(gatt == QBluetoothUuid(dataServiceChar)) {
sensorService = currentController->createServiceObject(gatt); sensorService = currentController->createServiceObject(gatt);
connect(sensorService, &QLowEnergyService::stateChanged, this, &MiFlora::serviceStateChanges); connect(sensorService, &QLowEnergyService::stateChanged, this, &MiFlora::sensorServiceStateChanges);
connect(sensorService, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error), this, &MiFlora::logServiceError); connect(sensorService, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error), this, &MiFlora::logServiceError);
connect(sensorService, &QLowEnergyService::characteristicWritten, this, &MiFlora::serviceCharWritten); connect(sensorService, &QLowEnergyService::characteristicWritten, this, &MiFlora::serviceCharWritten);
connect(sensorService, &QLowEnergyService::characteristicRead, this, &MiFlora::serviceCharRead); connect(sensorService, &QLowEnergyService::characteristicRead, this, &MiFlora::serviceCharRead);
sensorService->discoverDetails(); sensorService->discoverDetails();
} else if(gatt == QBluetoothUuid(historyServiceChar)) {
historyService = currentController->createServiceObject(gatt);
connect(historyService, &QLowEnergyService::stateChanged, this, &MiFlora::historyServiceStateChanges);
connect(historyService, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error), this, &MiFlora::logServiceError);
connect(historyService, &QLowEnergyService::characteristicWritten, this, &MiFlora::historyServiceCharWritten);
connect(historyService, &QLowEnergyService::characteristicRead, this, &MiFlora::historyServiceCharRead);
historyService->discoverDetails();
} }
} }

View File

@@ -26,6 +26,10 @@
#include <QtBluetooth/QLowEnergyCharacteristic> #include <QtBluetooth/QLowEnergyCharacteristic>
#include "bluetoothdevices.h" #include "bluetoothdevices.h"
#include <QQmlListProperty> #include <QQmlListProperty>
#include <QAbstractItemModel>
#include <QDateTime>
#include "florahistory.h"
#include <QXYSeries>
/** /**
* Class using QtBluetooth to find and retrive info from Mi Flora devices. * Class using QtBluetooth to find and retrive info from Mi Flora devices.
@@ -39,17 +43,21 @@ class MiFlora : public QObject
Q_PROPERTY(quint16 conduction NOTIFY conductionChanged MEMBER conduct) Q_PROPERTY(quint16 conduction NOTIFY conductionChanged MEMBER conduct)
Q_PROPERTY(quint8 battery NOTIFY batteryChanged MEMBER battery) Q_PROPERTY(quint8 battery NOTIFY batteryChanged MEMBER battery)
Q_PROPERTY(QQmlListProperty<BluetoothDevices> devices READ getDeviceList NOTIFY newDeviceFound) Q_PROPERTY(QQmlListProperty<BluetoothDevices> devices READ getDeviceList NOTIFY newDeviceFound)
Q_PROPERTY(QAbstractTableModel* model READ getModel NOTIFY modelUpdated)
public: public:
Q_INVOKABLE void startSearch(); Q_INVOKABLE void startSearch();
Q_INVOKABLE void stopSearch(); Q_INVOKABLE void stopSearch();
Q_INVOKABLE void updateDataFromDevice(QString mac); Q_INVOKABLE void updateDataFromDevice(QString mac);
QQmlListProperty<BluetoothDevices> getDeviceList(); QQmlListProperty<BluetoothDevices> getDeviceList();
//TODO:History? QAbstractTableModel* getModel();
signals: signals:
void newDeviceFound(); void newDeviceFound();
void modelUpdated();
void errorHappened(QString description);
void temperatureChanged(float temperature); void temperatureChanged(float temperature);
void brightnessChanged(quint32 brightness); void brightnessChanged(quint32 brightness);
void moistureChanged(quint8 moisture); void moistureChanged(quint8 moisture);
@@ -61,19 +69,33 @@ private:
void serviceDiscovered(const QBluetoothUuid &gatt); void serviceDiscovered(const QBluetoothUuid &gatt);
void logControllerError(QLowEnergyController::Error err); void logControllerError(QLowEnergyController::Error err);
void logServiceError(QLowEnergyService::ServiceError 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 serviceCharWritten(QLowEnergyCharacteristic changedChar, QByteArray value);
void serviceCharRead(QLowEnergyCharacteristic readChar, 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; QBluetoothDeviceDiscoveryAgent *agent;
QList<BluetoothDevices*> devices; QList<BluetoothDevices*> devices;
QLowEnergyController *currentController; QLowEnergyController *currentController;
QLowEnergyService *sensorService; 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 batteryFirmwareChar = 6658;//0x1a02
const quint16 sensorsChar = 6657; //0x1a01 const quint16 sensorsChar = 6657; //0x1a01
const quint16 magicChar = 6656; //0x1a00 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; float temp = 0.0;
quint32 bright = 0; quint32 bright = 0;
@@ -81,6 +103,11 @@ private:
quint16 conduct = 0; quint16 conduct = 0;
quint8 battery = 0; quint8 battery = 0;
QString version; QString version;
qint64 deviceStartTime;
qint16 currentHistoryEntry;
qint16 lastHistoryEntry;
FloraHistory *floraModel = new FloraHistory();
}; };