first commit
This commit is contained in:
commit
9bfa1e11fe
26 changed files with 1751 additions and 0 deletions
82
.gitignore
vendored
Normal file
82
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
*.qbs.user*
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# Directories with generated files
|
||||
.moc/
|
||||
.obj/
|
||||
.pch/
|
||||
.rcc/
|
||||
.uic/
|
||||
/build*/
|
||||
97
CMakeLists.txt
Normal file
97
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(waterLevelSensor VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Quick Network)
|
||||
|
||||
qt_standard_project_setup(REQUIRES 6.9)
|
||||
|
||||
qt_add_executable(appwaterLevelSensor
|
||||
main.cpp
|
||||
images.qrc
|
||||
)
|
||||
|
||||
|
||||
if(ANDROID)
|
||||
qt_add_library(service SHARED
|
||||
servicemain.cpp
|
||||
service.h service.cpp
|
||||
)
|
||||
set_property(TARGET appwaterLevelSensor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
set_property(TARGET service APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
|
||||
|
||||
target_link_libraries(service
|
||||
PRIVATE
|
||||
Qt::Core
|
||||
Qt::CorePrivate
|
||||
Qt::Network
|
||||
Qt::Quick
|
||||
)
|
||||
qt_add_qml_module(appwaterLevelSensor
|
||||
URI waterLevelSensor
|
||||
QML_FILES
|
||||
Main.qml
|
||||
RESOURCES
|
||||
RESOURCES images.qrc
|
||||
SOURCES moving_container.h
|
||||
SOURCES configinterface.h configinterface.cpp
|
||||
SOURCES udplistener.h udplistener.cpp
|
||||
SOURCES service.h
|
||||
)
|
||||
target_link_libraries(appwaterLevelSensor
|
||||
PRIVATE Qt6::Quick Qt6::Network service
|
||||
)
|
||||
|
||||
qt_finalize_executable(appwaterLevelSensor)
|
||||
add_dependencies(appwaterLevelSensor service)
|
||||
|
||||
qt_generate_deploy_app_script(
|
||||
TARGET appwaterLevelSensor
|
||||
OUTPUT_SCRIPT deploy_script
|
||||
NO_UNSUPPORTED_PLATFORM_ERROR
|
||||
)
|
||||
install(SCRIPT ${deploy_script})
|
||||
|
||||
endif()
|
||||
if(NOT ANDROID)
|
||||
qt_add_qml_module(appwaterLevelSensor
|
||||
URI waterLevelSensor
|
||||
QML_FILES
|
||||
Main.qml
|
||||
RESOURCES
|
||||
RESOURCES images.qrc
|
||||
SOURCES udplistener.h udplistener.cpp
|
||||
SOURCES moving_container.h
|
||||
SOURCES configinterface.h configinterface.cpp
|
||||
)
|
||||
|
||||
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
||||
# If you are developing for iOS or macOS you should consider setting an
|
||||
# explicit, fixed bundle identifier manu ally though.
|
||||
set_target_properties(appwaterLevelSensor PROPERTIES
|
||||
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appwaterLevelSensor
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||
MACOSX_BUNDLE TRUE
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(appwaterLevelSensor
|
||||
PRIVATE Qt6::Quick Qt6::Network
|
||||
)
|
||||
|
||||
qt_finalize_executable(appwaterLevelSensor)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS appwaterLevelSensor
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
endif()
|
||||
532
Main.qml
Normal file
532
Main.qml
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
import QtQuick
|
||||
import waterLevelSensor
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
Window {
|
||||
width: 640
|
||||
height: 480
|
||||
visible: true
|
||||
title: qsTr("Hello World")
|
||||
id: root
|
||||
property color accent_color: "#F05FAF5F"
|
||||
property color danger_color: "#F0AF2F1F"
|
||||
property color water_color: "#F00F7FAF"
|
||||
Material.accent: root.accent_color
|
||||
property int font_enlarged_text_size: 20
|
||||
property int font_normal_text_size: 18
|
||||
property double distance: 0
|
||||
property int distance_count: 0
|
||||
// visibility: Window.FullScreen
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
id: background
|
||||
color: "#DF0F0F0F"
|
||||
Rectangle {
|
||||
id: top_bar
|
||||
height: Math.min(root.height / 12, 100)
|
||||
width: root.width
|
||||
anchors {
|
||||
top: parent.top
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
topMargin: 25
|
||||
}
|
||||
color: "#0A0F0F0F"
|
||||
|
||||
Rectangle {
|
||||
id: settings_rect
|
||||
height: top_bar.height - 20
|
||||
width: top_bar.width / 12
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
bottomMargin: 5
|
||||
topMargin: 5
|
||||
leftMargin: 5
|
||||
}
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
|
||||
color: "#0A0F0F0F"
|
||||
border.color: "#AF121212"
|
||||
border.width: 0.5
|
||||
radius: 2
|
||||
Text {
|
||||
id: settings_text
|
||||
text: "⚙"
|
||||
anchors.centerIn: parent
|
||||
color: root.accent_color
|
||||
font.pointSize: root.font_enlarged_text_size
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: settings_button_area
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
property color settings_rect_pressed_prev: "#00000000"
|
||||
property color settings_rect_entered_prev: "#00000000"
|
||||
onEntered: {
|
||||
settings_rect_entered_prev = settings_rect.color
|
||||
settings_rect.color = "#5A1F1F1F"
|
||||
}
|
||||
onExited: {
|
||||
settings_rect.color = settings_rect_entered_prev
|
||||
}
|
||||
onPressed: {
|
||||
settings_rect_pressed_prev = settings_rect.color
|
||||
settings_rect.color = "#5A1F4F1F"
|
||||
settings_popup.open()
|
||||
}
|
||||
onReleased: {
|
||||
settings_rect.color = settings_rect_pressed_prev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Rectangle {
|
||||
property color old_color: "#0F0F0F0F"
|
||||
id: status_indicator
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 20
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
topMargin: 20
|
||||
bottomMargin: 5
|
||||
leftMargin: 5
|
||||
rightMargin: 20
|
||||
}
|
||||
color: "#F0AF2F1F"
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: status_label
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 20
|
||||
right: status_indicator.left
|
||||
rightMargin: 10
|
||||
}
|
||||
horizontalAlignment: Text.AlignRight
|
||||
width: parent.width - status_indicator.width
|
||||
height: parent.height
|
||||
color: root.accent_color
|
||||
font.pointSize: root.font_normal_text_size
|
||||
text: "Disconnected"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: base
|
||||
color: "#0A0F0F0F"
|
||||
width: parent.width
|
||||
height: parent.height - top_bar.height
|
||||
anchors {
|
||||
top: top_bar.bottom
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: settings_popup
|
||||
background: Rectangle {
|
||||
color: "#F0121212"
|
||||
radius: 5
|
||||
border.color: root.accent_color
|
||||
border.width: 1
|
||||
}
|
||||
height: parent.height / 1.5
|
||||
width: parent.width / 1.5
|
||||
anchors.centerIn: Overlay.overlay
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: settings_base
|
||||
anchors.fill: parent
|
||||
color: "#F0121212"
|
||||
|
||||
Text {
|
||||
id: settings_label
|
||||
text: "Settings"
|
||||
width: parent.width
|
||||
anchors {
|
||||
top: parent.top
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: root.accent_color
|
||||
font.pointSize: root.font_enlarged_text_size
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
id: max_thres_label
|
||||
text: "Max Threshold: "
|
||||
anchors {
|
||||
topMargin: 20
|
||||
top: settings_label.bottom
|
||||
left: parent.left
|
||||
}
|
||||
height: max_thres_input.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: root.accent_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: max_thres_input
|
||||
text: config.max
|
||||
anchors {
|
||||
left: max_thres_label.right
|
||||
leftMargin: 5
|
||||
top: settings_label.bottom
|
||||
topMargin: 20
|
||||
}
|
||||
color: root.accent_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
id: min_thres_label
|
||||
text: "Min Threshold: "
|
||||
anchors {
|
||||
topMargin: 20
|
||||
top: max_thres_label.bottom
|
||||
left: parent.left
|
||||
}
|
||||
height: min_thres_input.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: root.accent_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: min_thres_input
|
||||
text: config.min
|
||||
anchors {
|
||||
left: min_thres_label.right
|
||||
leftMargin: 5
|
||||
top: max_thres_label.bottom
|
||||
topMargin: 20
|
||||
}
|
||||
color: root.accent_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
id: alert_thres_label
|
||||
text: "Alert Percentage: "
|
||||
anchors {
|
||||
topMargin: 20
|
||||
top: min_thres_label.bottom
|
||||
left: parent.left
|
||||
}
|
||||
height: alert_thres_input.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: root.accent_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: alert_thres_input
|
||||
text: config.alert
|
||||
anchors {
|
||||
left: alert_thres_label.right
|
||||
leftMargin: 5
|
||||
top: min_thres_label.bottom
|
||||
topMargin: 20
|
||||
}
|
||||
color: root.accent_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
Text {
|
||||
id: settings_error_label
|
||||
property string settings_error_text: ""
|
||||
text: "Error: " + settings_error_text
|
||||
visible: settings_error_text === "" ? false : true
|
||||
anchors {
|
||||
topMargin: 20
|
||||
top: alert_thres_label.bottom
|
||||
left: parent.left
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: root.danger_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: save_config
|
||||
anchors {
|
||||
topMargin: 10
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
color: "#F0121212"
|
||||
width: parent.width / 2.5
|
||||
height: 60
|
||||
border {
|
||||
width: 1
|
||||
color: root.accent_color
|
||||
}
|
||||
radius: 3
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Save"
|
||||
color: root.accent_color
|
||||
font.pointSize: root.font_enlarged_text_size
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
property color settings_rect_pressed_prev: "#00000000"
|
||||
property color settings_rect_entered_prev: "#00000000"
|
||||
onEntered: {
|
||||
settings_rect_entered_prev = save_config.color
|
||||
save_config.color = "#5A1F1F1F"
|
||||
}
|
||||
onExited: {
|
||||
save_config.color = settings_rect_entered_prev
|
||||
}
|
||||
onPressed: {
|
||||
settings_rect_pressed_prev = save_config.color
|
||||
save_config.color = "#5A1F4F1F"
|
||||
let max = parseFloat(min_thres_input.text);
|
||||
let min = parseFloat(min_thres_input.text);
|
||||
let alert = parseFloat(alert_thres_input.text);
|
||||
console.log("Parsed values:", max, min, alert);
|
||||
if(max < min) {
|
||||
settings_error_label.settings_error_text = "Max cannot be less than min";
|
||||
return;
|
||||
}
|
||||
if (alert > 100 || alert < 0) {
|
||||
settings_error_label.settings_error_text = "Alert must be between 0 to 100";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
|
||||
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
|
||||
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
|
||||
console.log("Alert effective reading is: " + alert_effective_reading + " for value: " + alert);
|
||||
listener.setAlert(alert_effective_reading);
|
||||
config.saveConfig(max_thres_input.text, min_thres_input.text, alert_thres_input.text);
|
||||
settings_popup.close();
|
||||
}
|
||||
onReleased: {
|
||||
save_config.color = settings_rect_pressed_prev
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: tank_lid
|
||||
color: "#F0121212"
|
||||
anchors {
|
||||
bottom: tank_base.top
|
||||
horizontalCenter: tank_base.horizontalCenter
|
||||
bottomMargin: -5
|
||||
}
|
||||
radius: 10
|
||||
width: parent.width / 4
|
||||
height: parent.height / 15
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: tank_base
|
||||
color: "#0FFFFFFF"
|
||||
anchors.centerIn: parent
|
||||
width: parent.width / 2.5
|
||||
height: parent.height / 2
|
||||
border.color: "#F0121212"
|
||||
border.width: 5
|
||||
radius: 4
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: water_level
|
||||
color: root.water_color
|
||||
property double height_max: tank_base.height - 10
|
||||
property double current_level: 0
|
||||
property double percentage_filled: 0
|
||||
height: water_level.current_level
|
||||
width: tank_base.width - 10
|
||||
anchors {
|
||||
bottom: tank_base.bottom
|
||||
horizontalCenter: tank_base.horizontalCenter
|
||||
bottomMargin: 5
|
||||
}
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: info_text
|
||||
text: "Current Reading: " + root.distance.toFixed(2) + " cm"
|
||||
anchors {
|
||||
top: tank_base.bottom
|
||||
topMargin: 20
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: root.accent_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
Text {
|
||||
id: info_percent_text
|
||||
text: "Percentage filled: " + water_level.percentage_filled.toFixed(2) + " %"
|
||||
anchors {
|
||||
top: info_text.bottom
|
||||
topMargin: 10
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: root.accent_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
Text {
|
||||
id: error_text
|
||||
property string error_string: ""
|
||||
text: "Error: " + error_string
|
||||
visible: error_string === "" ? false : true;
|
||||
anchors {
|
||||
top: info_percent_text.bottom
|
||||
topMargin: 10
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: root.danger_color
|
||||
font.pixelSize: root.font_normal_text_size
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Timer {
|
||||
id: status_indicator_timer
|
||||
running: false
|
||||
repeat: false
|
||||
triggeredOnStart: false
|
||||
interval: 300
|
||||
onTriggered: {
|
||||
status_indicator.color = status_indicator.old_color;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigInterface {
|
||||
id: config
|
||||
onConfigData: (max, min, alert) => {
|
||||
console.log("Got config data as: ", max, min, alert);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UDPListener {
|
||||
id: listener
|
||||
|
||||
onShowError: (err) => {
|
||||
error_text.error_string = err;
|
||||
}
|
||||
|
||||
onDistanceChanged: {
|
||||
console.log("Distance changed");
|
||||
root.distance = listener.distance;
|
||||
error_text.error_string = "";
|
||||
console.log("Distance count: ", root.distance_count);
|
||||
if(root.distance_count >= 5) {
|
||||
status_label.text = "Recieving values";
|
||||
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
|
||||
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
|
||||
let current_reading = Math.max(0, root.distance - current_min_reading);
|
||||
let height_max_possible = water_level.height_max;
|
||||
let percentage_filled = Math.max(0, ((effective_max_reading - current_reading) / effective_max_reading * 100))
|
||||
let normalized_height = percentage_filled * height_max_possible / 100;
|
||||
let alert = parseFloat(alert_thres_input.text);
|
||||
if(percentage_filled <= alert) {
|
||||
console.log("Water Level is lower than alert");
|
||||
water_level.color = root.danger_color;
|
||||
}
|
||||
else {
|
||||
water_level.color = root.water_color;
|
||||
}
|
||||
|
||||
console.log("Current reading is: " + current_reading)
|
||||
console.log("Normalized height is: " + normalized_height)
|
||||
water_level.current_level = normalized_height;
|
||||
water_level.percentage_filled = percentage_filled;
|
||||
}
|
||||
}
|
||||
onConnected: {
|
||||
error_text.error_string = "";
|
||||
console.log("Connected");
|
||||
status_indicator.color = "#F05F5F0F"
|
||||
status_label.text = "Waiting for more values";
|
||||
|
||||
}
|
||||
onDisconnected: {
|
||||
error_text.error_string = "Disconnected";
|
||||
console.log("Disconnected");
|
||||
status_indicator.color = "#F05F5F0F"
|
||||
status_label.text = "Disconnected";
|
||||
}
|
||||
onDistanceReceived: (distance, time) => {
|
||||
if(root.distance_count >= 1)
|
||||
dataBlink();
|
||||
if(root.distance_count == 0) {
|
||||
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
|
||||
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
|
||||
let alert = parseFloat(alert_thres_input.text);
|
||||
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
|
||||
console.log("Alert effective reading is: " + alert_effective_reading + " for value: " + alert);
|
||||
listener.setAlert(alert_effective_reading);
|
||||
}
|
||||
|
||||
distance_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
onClosing:(close)=> {
|
||||
if(Qt.platform.os === "android"){
|
||||
close.accepted = false
|
||||
} else {
|
||||
close.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
function dataBlink() {
|
||||
let old_color = status_indicator.color;
|
||||
let new_color = "#F04FAF5F";
|
||||
status_indicator.color = new_color;
|
||||
status_indicator.old_color = old_color;
|
||||
status_indicator_timer.start();
|
||||
}
|
||||
}
|
||||
14
NotificationManager.java
Normal file
14
NotificationManager.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package org.qtproject.example.androidnotifier;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.app.NotificationChannel;
|
||||
|
||||
|
||||
public class NotificationManager {
|
||||
|
||||
}
|
||||
BIN
WaterTank1.png
Normal file
BIN
WaterTank1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 319 KiB |
68
android/AndroidManifest.xml
Normal file
68
android/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.qtproject.example.appwaterLevelSensor"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" />
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true" />
|
||||
<application
|
||||
android:name="org.qtproject.qt.android.bindings.QtApplication"
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="appwaterLevelSensor"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupOnly="false">
|
||||
|
||||
<service android:name=".WaterLevelService"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:exported="true">
|
||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="Monitoring water levels via local network UDP packets for real-time sensor alerts." />
|
||||
<meta-data android:name="android.app.lib_name" android:value="service"/>
|
||||
<meta-data android:name="android.app.background_running" android:value="true"/>
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name="org.qtproject.qt.android.bindings.QtActivity"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
|
||||
android:label="appwaterLevelSensor"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="unspecified"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="appwaterLevelSensor" />
|
||||
<meta-data
|
||||
android:name="android.app.arguments"
|
||||
android:value="" />
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.qtprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/qtprovider_paths"/>
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
83
android/build.gradle
Normal file
83
android/build.gradle
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//noinspection AndroidGradlePluginVersion
|
||||
classpath 'com.android.tools.build:gradle:8.8.0'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
apply plugin: qtGradlePluginType
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
//noinspection GradleDependency
|
||||
implementation 'androidx.core:core:1.16.0'
|
||||
}
|
||||
|
||||
android {
|
||||
/*******************************************************
|
||||
* The following variables:
|
||||
* - androidBuildToolsVersion,
|
||||
* - androidCompileSdkVersion
|
||||
* - qtAndroidDir - holds the path to qt android files
|
||||
* needed to build any Qt application
|
||||
* on Android.
|
||||
* - qtGradlePluginType - whether to build an app or a library
|
||||
*
|
||||
* are defined in gradle.properties file. This file is
|
||||
* updated by QtCreator and androiddeployqt tools.
|
||||
* Changing them manually might break the compilation!
|
||||
*******************************************************/
|
||||
|
||||
namespace = androidPackageName
|
||||
compileSdkVersion androidCompileSdkVersion
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
ndkVersion = androidNdkVersion
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
|
||||
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
|
||||
res.srcDirs = [qtAndroidDir + '/res', 'res']
|
||||
resources.srcDirs = ['resources']
|
||||
renderscript.srcDirs = ['src']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.incremental = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError = false
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
// Do not compress Qt binary resources file
|
||||
noCompress 'rcc'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
resConfig "en"
|
||||
minSdkVersion qtMinSdkVersion
|
||||
targetSdkVersion qtTargetSdkVersion
|
||||
ndk.abiFilters = qtTargetAbiList.split(",")
|
||||
}
|
||||
}
|
||||
BIN
android/res/drawable-hdpi/ic_launcher.png
Normal file
BIN
android/res/drawable-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
BIN
android/res/drawable-mdpi/ic_launcher.png
Normal file
BIN
android/res/drawable-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4 KiB |
BIN
android/res/drawable-xhdpi/ic_launcher.png
Normal file
BIN
android/res/drawable-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
android/res/drawable-xxhdpi/ic_launcher.png
Normal file
BIN
android/res/drawable-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
android/res/drawable-xxxhdpi/ic_launcher.png
Normal file
BIN
android/res/drawable-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
|
@ -0,0 +1,17 @@
|
|||
package org.qtproject.example.appwaterLevelSensor;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class WaterLevelReceiver extends BroadcastReceiver {
|
||||
public native static void updateWaterLevel(byte[] data);
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if ("com.example.WATER_LEVEL_UPDATE".equals(intent.getAction())) {
|
||||
byte[] data = intent.getByteArrayExtra("level_data");
|
||||
updateWaterLevel(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
package org.qtproject.example.appwaterLevelSensor;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.app.NotificationChannel;
|
||||
import org.qtproject.qt.android.bindings.QtService;
|
||||
import android.util.Log;
|
||||
import android.content.Intent;
|
||||
import java.lang.Runnable;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.*;
|
||||
import org.qtproject.qt.android.bindings.QtActivity;
|
||||
import android.app.Service;
|
||||
import java.time.Instant;
|
||||
import java.time.Duration;
|
||||
|
||||
public class WaterLevelService extends Service {
|
||||
|
||||
private static float alert_thres_distance = 1000000;
|
||||
|
||||
public class UDPCommandClient implements Runnable {
|
||||
private DatagramSocket socket_cmd_recv;
|
||||
private DatagramSocket socket_cmd_send;
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d("UDP Service", "Command thread initialising");
|
||||
try {
|
||||
socket_cmd_recv = new DatagramSocket(8178, InetAddress.getByName("localhost"));
|
||||
socket_cmd_send = new DatagramSocket(8179);
|
||||
|
||||
Log.d("UDP Service", "Created command thread sockets");
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
byte[] buffer = new byte[1024];
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||
socket_cmd_recv.receive(packet);
|
||||
String str = new String(buffer, StandardCharsets.UTF_8);
|
||||
if(str.startsWith("<SET NAME=ALERT VALUE=")) {
|
||||
str = str.replace("<SET NAME=ALERT VALUE=", "");
|
||||
str = str.replace("=", "");
|
||||
str = str.replace(">", "");
|
||||
alert_thres_distance = Float.parseFloat(str);
|
||||
}
|
||||
Log.d("UDP Service ", "Received command packet " + str);
|
||||
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Log.e("UDP Service", "Socket Error", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class UDPClient implements Runnable {
|
||||
private DatagramSocket socket_recv;
|
||||
private DatagramSocket socket_send;
|
||||
private Instant prevAlertTimestamp;
|
||||
private boolean started = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d("UDP Service", "Thread initialising");
|
||||
android.net.wifi.WifiManager wifi = (android.net.wifi.WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||
android.net.wifi.WifiManager.MulticastLock multicastLock = wifi.createMulticastLock("water_level_lock");
|
||||
android.os.PowerManager pm = (android.os.PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
android.os.PowerManager.WakeLock wakeLock = pm.newWakeLock(android.os.PowerManager.PARTIAL_WAKE_LOCK, "WaterLevel:WakeLock");
|
||||
|
||||
|
||||
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
String alertChannelId = "water_alert_channel";
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
alertChannelId,
|
||||
"Water Level Alerts",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
);
|
||||
manager.createNotificationChannel(channel);
|
||||
|
||||
try {
|
||||
multicastLock.acquire();
|
||||
wakeLock.acquire();
|
||||
socket_recv = new DatagramSocket(8176);
|
||||
socket_send = new DatagramSocket(8177);
|
||||
socket_recv.setBroadcast(true);
|
||||
Log.d("UDP Service", "Created Sockets");
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
byte[] buffer = new byte[4];
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||
socket_recv.receive(packet);
|
||||
float f = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getFloat();
|
||||
Log.d("UDP Service ", "Received packet: " + f);
|
||||
Log.d("UDP Service ", "Threshold is: " + alert_thres_distance);
|
||||
boolean duration_passed = false;
|
||||
if(!started) {
|
||||
duration_passed = true;
|
||||
prevAlertTimestamp = Instant.now();
|
||||
}
|
||||
else {
|
||||
Instant currentTimestamp = Instant.now();
|
||||
Duration duration = Duration.between(prevAlertTimestamp, currentTimestamp);
|
||||
if(duration.toMinutes() > 30) {
|
||||
duration_passed = true;
|
||||
}
|
||||
prevAlertTimestamp = currentTimestamp;
|
||||
}
|
||||
if(f > alert_thres_distance && alert_thres_distance < 999999 && duration_passed) {
|
||||
Notification.Builder builder = new Notification.Builder(WaterLevelService.this, alertChannelId)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setContentTitle("Water Level Sensor")
|
||||
.setContentText("Water Level is below alert threshold")
|
||||
.setPriority(Notification.PRIORITY_HIGH);
|
||||
manager.notify(2, builder.build());
|
||||
Log.d("UDP Service", "Notification sent");
|
||||
}
|
||||
DatagramPacket send_packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("localhost"), 8180);
|
||||
socket_send.send(send_packet);
|
||||
Log.d("UDP Service ", "Sent packet");
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Log.e("UDP Service", "Socket Error", e);
|
||||
} finally {
|
||||
if (multicastLock.isHeld())
|
||||
multicastLock.release();
|
||||
if (wakeLock.isHeld())
|
||||
wakeLock.release();
|
||||
if (socket_recv != null)
|
||||
socket_recv.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startWaterLevelService() {
|
||||
Log.d("UDP Service", "Starting the service from Activity");
|
||||
|
||||
Intent intent = new Intent(this, WaterLevelService.class);
|
||||
|
||||
startForegroundService(intent);
|
||||
}
|
||||
public WaterLevelService() {
|
||||
super();
|
||||
Log.i("UDP Service", "Class is created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.os.IBinder onBind(android.content.Intent intent) { return null; }
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Notification notification = createNotification();
|
||||
startForeground(1, notification);
|
||||
Log.i("UDP Service", "Creating Service");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.i("UDP Service", "Destroying Service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Notification notification = createNotification();
|
||||
startForeground(1, notification);
|
||||
|
||||
Log.d("UDP Service", "Actually starting the thread");
|
||||
|
||||
int ret = super.onStartCommand(intent, flags, startId);
|
||||
|
||||
(new Thread(new UDPClient())).start();
|
||||
(new Thread(new UDPCommandClient())).start();
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
|
||||
private Notification createNotification() {
|
||||
String channelId = "water_level_channel";
|
||||
|
||||
NotificationManager manager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
channelId,
|
||||
"Water Level Service",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
manager.createNotificationChannel(channel);
|
||||
|
||||
return new Notification.Builder(this, channelId)
|
||||
.setContentTitle("Water Level Sensor Running")
|
||||
.setContentText("Monitoring water level...")
|
||||
.setSmallIcon(android.R.drawable.ic_menu_info_details)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static void startQtAndroidService(QtActivity activity) {
|
||||
Log.d("UDP Service", "Starting the service");
|
||||
Intent intent = new Intent(activity, WaterLevelService.class);
|
||||
activity.startForegroundService(intent);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.qtproject.example.appwaterLevelSensor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import org.qtproject.qt.android.bindings.QtActivity;
|
||||
|
||||
public class WaterLevelServiceStarter {
|
||||
|
||||
public static void startService(Context context) {
|
||||
Log.d("UDP Service", "Starting service via QtActivity instance");
|
||||
|
||||
Intent intent = new Intent(context, WaterLevelService.class);
|
||||
|
||||
context.startForegroundService(intent);
|
||||
}
|
||||
}
|
||||
108
configinterface.cpp
Normal file
108
configinterface.cpp
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#include "configinterface.h"
|
||||
|
||||
ConfigInterface::ConfigInterface(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
auto paths = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
qDebug() << "Got writable path as: " << paths;
|
||||
std::filesystem::path dir = paths.toStdString();
|
||||
if(!QDir(dir).exists()) {
|
||||
QDir().mkpath(paths);
|
||||
}
|
||||
std::filesystem::path path = dir / "config.json";
|
||||
QFile config(path);
|
||||
if(config.exists()) {
|
||||
qDebug() << "Loading config file....";
|
||||
bool ok = config.open(QFile::ReadOnly);
|
||||
if(!ok) {
|
||||
qDebug() << config.errorString();
|
||||
return;
|
||||
}
|
||||
auto data = config.readAll();
|
||||
config.close();
|
||||
QJsonObject top = QJsonDocument::fromJson(data).object();
|
||||
QString max, min, alert;
|
||||
max = top["max_threshold"].toString();
|
||||
min = top["min_threshold"].toString();
|
||||
alert = top["alert_threshold"].toString();
|
||||
setMax(max);
|
||||
setMin(min);
|
||||
setAlert(alert);
|
||||
}
|
||||
else {
|
||||
qDebug() << "File does not exist creating one...";
|
||||
QJsonObject top;
|
||||
top["max_threshold"] = "125";
|
||||
top["min_threshold"] = "25";
|
||||
top["alert_threshold"] = "100";
|
||||
QByteArray data = QJsonDocument(top).toJson();
|
||||
bool ok = config.open(QFile::WriteOnly);
|
||||
if(!ok) {
|
||||
qDebug() << config.errorString();
|
||||
return;
|
||||
}
|
||||
config.write(data);
|
||||
config.flush();
|
||||
config.close();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigInterface::saveConfig(QString max, QString min, QString alert)
|
||||
{
|
||||
auto paths = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
qDebug() << "Got writable path as: " << paths;
|
||||
std::filesystem::path path = paths.toStdString();
|
||||
QFile config(path / "config.json");
|
||||
qDebug() << "Writing to config file..." << max << min << alert;
|
||||
QJsonObject top;
|
||||
top["max_threshold"] = max;
|
||||
top["min_threshold"] = min;
|
||||
top["alert_threshold"] = alert;
|
||||
setMax(max);
|
||||
setMin(min);
|
||||
setAlert(alert);
|
||||
QByteArray data = QJsonDocument(top).toJson();
|
||||
config.open(QIODevice::WriteOnly);
|
||||
config.write(data);
|
||||
config.flush();
|
||||
config.close();
|
||||
}
|
||||
|
||||
QString ConfigInterface::alert() const
|
||||
{
|
||||
return m_alert;
|
||||
}
|
||||
|
||||
void ConfigInterface::setAlert(const QString &newAlert)
|
||||
{
|
||||
if (m_alert == newAlert)
|
||||
return;
|
||||
m_alert = newAlert;
|
||||
emit alertChanged();
|
||||
}
|
||||
|
||||
QString ConfigInterface::max() const
|
||||
{
|
||||
return m_max;
|
||||
}
|
||||
|
||||
void ConfigInterface::setMax(const QString &newMax)
|
||||
{
|
||||
if (m_max == newMax)
|
||||
return;
|
||||
m_max = newMax;
|
||||
emit maxChanged();
|
||||
}
|
||||
|
||||
QString ConfigInterface::min() const
|
||||
{
|
||||
return m_min;
|
||||
}
|
||||
|
||||
void ConfigInterface::setMin(const QString &newMin)
|
||||
{
|
||||
if (m_min == newMin)
|
||||
return;
|
||||
m_min = newMin;
|
||||
emit minChanged();
|
||||
}
|
||||
45
configinterface.h
Normal file
45
configinterface.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef CONFIGINTERFACE_H
|
||||
#define CONFIGINTERFACE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonDocument>
|
||||
|
||||
class ConfigInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(QString min READ min WRITE setMin NOTIFY minChanged FINAL)
|
||||
Q_PROPERTY(QString max READ max WRITE setMax NOTIFY maxChanged FINAL)
|
||||
Q_PROPERTY(QString alert READ alert WRITE setAlert NOTIFY alertChanged FINAL)
|
||||
public:
|
||||
explicit ConfigInterface(QObject *parent = nullptr);
|
||||
QString alert() const;
|
||||
void setAlert(const QString &newAlert);
|
||||
|
||||
QString max() const;
|
||||
void setMax(const QString &newMax);
|
||||
|
||||
QString min() const;
|
||||
void setMin(const QString &newMin);
|
||||
|
||||
signals:
|
||||
void configData(QString max, QString min, QString alert);
|
||||
void alertChanged();
|
||||
void maxChanged();
|
||||
void minChanged();
|
||||
|
||||
public slots:
|
||||
void saveConfig(QString max, QString min, QString alert);
|
||||
private:
|
||||
QString m_alert;
|
||||
QString m_max;
|
||||
QString m_min;
|
||||
};
|
||||
|
||||
#endif // CONFIGINTERFACE_H
|
||||
5
images.qrc
Normal file
5
images.qrc
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>WaterTank1.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
18
main.cpp
Normal file
18
main.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
QObject::connect(
|
||||
&engine,
|
||||
&QQmlApplicationEngine::objectCreationFailed,
|
||||
&app,
|
||||
[]() { QCoreApplication::exit(-1); },
|
||||
Qt::QueuedConnection);
|
||||
engine.loadFromModule("waterLevelSensor", "Main");
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue