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