diff --git a/.gitignore b/.gitignore
index c9e89eb..aa3808c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,82 @@
-build/*
-.qtcreator/*
+# 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*/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 957c9c2..2b80b0d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,69 +1,111 @@
cmake_minimum_required(VERSION 3.16)
-project(WaterLevelRewrite VERSION 0.1 LANGUAGES CXX)
+project(waterLevelSensor VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTORCC ON)
-find_package(Qt6 REQUIRED COMPONENTS Gui Core Quick Multimedia)
+find_package(Qt6 REQUIRED COMPONENTS Quick Network CorePrivate)
-qt_standard_project_setup(REQUIRES 6.10)
+qt_standard_project_setup(REQUIRES 6.11)
-if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/importedcontent/CMakeLists.txt")
- add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/importedcontent)
-endif()
-
-qt_add_executable(appWaterLevelRewrite
+qt_add_executable(appwaterLevelSensor
main.cpp
+ images.qrc
)
-qt_add_qml_module(appWaterLevelRewrite
- URI WaterLevelRewrite
+
+if(ANDROID)
+
+include(FetchContent)
+FetchContent_Declare(
+ android_openssl
+ DOWNLOAD_EXTRACT_TIMESTAMP true
+ URL https://github.com/KDAB/android_openssl/archive/refs/heads/master.zip
+)
+FetchContent_MakeAvailable(android_openssl)
+include(${android_openssl_SOURCE_DIR}/android_openssl.cmake)
+
+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::Network
+ Qt::CorePrivate
+ Qt::Quick
+)
+qt_add_qml_module(appwaterLevelSensor
+ URI waterLevelSensor
QML_FILES
Main.qml
- WaterTank.qml
- SettingsBarIcon.qml
- SettingsPopup.qml
- ConnectButton.qml
- SOURCES
- configinterface.h
- configinterface.cpp
- requestworker.h
- requestworker.cpp
- wifihelper.h
- wifihelper.cpp
- RESOURCES
- media.qrc
- QML_FILES CustomCheckBox.qml
- QML_FILES RequestResponsePopup.qml
- QML_FILES ColorInput.qml
+ RESOURCES
+ RESOURCES images.qrc
+ SOURCES moving_container.h
+ SOURCES configinterface.h configinterface.cpp
+ SOURCES udplistener.h udplistener.cpp
+ SOURCES service.h
+ RESOURCES README.md
+)
+add_android_openssl_libraries(service appwaterLevelSensor)
+target_link_libraries(appwaterLevelSensor
+ PRIVATE Qt6::Quick Qt6::Network service
)
-if(APPLE)
- set_target_properties(appWaterLevelRewrite PROPERTIES
- # MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appWaterLevelRewrite
- MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
- MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
- MACOSX_BUNDLE TRUE
- )
+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(WIN32)
- set_target_properties(appWaterLevelRewrite PROPERTIES
- WIN32_EXECUTABLE TRUE
- )
-endif()
-
-set_target_properties(appWaterLevelRewrite PROPERTIES
- QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android"
+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
)
-target_link_libraries(appWaterLevelRewrite
- PRIVATE Qt6::Quick Qt6::Multimedia Qt6::Gui Qt6::Core
+# 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
)
-install(TARGETS appWaterLevelRewrite
+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()
diff --git a/ColorInput.qml b/ColorInput.qml
deleted file mode 100644
index a4cebe7..0000000
--- a/ColorInput.qml
+++ /dev/null
@@ -1,21 +0,0 @@
-import QtQuick
-import QtQuick.Effects
-import QtQuick.Controls
-import QtQuick.Controls.Material
-
-TextField {
- id: color_text
- text: "F05FAF5F"
- property alias color_text: base.color
- Rectangle {
- id: base
- anchors {
- right: parent.right
- rightMargin: 10
- verticalCenter: parent.verticalCenter
- }
- width: 10
- height: parent.height / 2
- color: "#" + color_text.text
- }
-}
diff --git a/ConnectButton.qml b/ConnectButton.qml
deleted file mode 100644
index dd058bc..0000000
--- a/ConnectButton.qml
+++ /dev/null
@@ -1,63 +0,0 @@
-import QtQuick
-import QtQuick.Effects
-import QtQuick.Controls
-import QtQuick.Controls.Material
-
-Rectangle {
- id: root
- property color accent_color: "#00000000"
- property color font_color: "#00000000"
- property int font_enlarged_text_size: 20
- property int font_normal_text_size: 16
- property string text: "Connect"
- property string text2: "Stop"
- property int mode: 0
- signal connect;
- layer.enabled: true
- layer.effect: MultiEffect {
- shadowEnabled: true
- }
-
- Text {
- anchors.centerIn: parent
- font.pointSize: root.font_enlarged_text_size
- color: root.font_color
- text: mode ? root.text2 : root.text
- horizontalAlignment: Text.AlignHCenter
- }
-
- radius: 5
- border {
- color: root.accent_color
- width: 0.5
- }
- 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 = root.color
- root.color = "#5A1F1F1F"
- }
- onExited: {
- root.color = settings_rect_entered_prev
- }
- onPressed: {
- settings_rect_pressed_prev = root.color
- root.color = "#5A1F4F1F"
- root.connect();
- switchMode();
- }
- onReleased: {
- root.color = settings_rect_pressed_prev
- }
- }
- function switchMode() {
- if(root.mode === 0)
- root.mode = 1
- else
- root.mode = 0;
- }
-}
diff --git a/CustomCheckBox.qml b/CustomCheckBox.qml
deleted file mode 100644
index 0cdbb38..0000000
--- a/CustomCheckBox.qml
+++ /dev/null
@@ -1,17 +0,0 @@
-import QtQuick
-import QtQuick.Controls.Basic
-
-CheckBox {
- id: control
- property color font_color: "#0000FF00"
- property int font_point_size: 22
- font.pointSize: font_point_size
- checked: true
- contentItem: Text {
- text: control.text
- font: control.font
- color: control.font_color
- verticalAlignment: Text.AlignVCenter
- leftPadding: control.indicator.width + control.spacing
- }
-}
diff --git a/Main.qml b/Main.qml
index 28693c5..e7844b9 100644
--- a/Main.qml
+++ b/Main.qml
@@ -1,59 +1,71 @@
import QtQuick
-import WaterLevelRewrite
+import waterLevelSensor
import QtQuick.Effects
import QtQuick.Controls
import QtQuick.Controls.Material
Window {
- width: 960
- height: 1400
+ width: 640
+ height: 1080
visible: true
- title: qsTr("Water Level Rewrite")
+ title: qsTr("Hello World")
id: root
property color accent_color: "#F05FAF5F"
property color dark_accent_color: "#FF5F7F5F"
property color danger_color: "#F0AF2F1F"
property color water_color: "#F00F7FAF"
- property color water_color_above_alert: "#F00FAF0F"
- property color background_color: "#1F1F1F"
Material.accent: root.accent_color
property int font_enlarged_text_size: 20
property int font_normal_text_size: 16
+ property double distance: 0
+ property int distance_count: 0
property int distance_count_thres: 3
property string mode: "http"
- property string status: "Initializing"
- property string connection_text_string: "Reading WiFi Status..."
- property string alarm_status_text: ""
- property double water_tank_height_factor: 1.3
- property double water_tank_width_factor: 2
- property var root_config: ({})
- property var base_config: {
- "max": 0,
- "min": 0,
- "alert": 0,
- "interval": 0,
- "url": "none",
- "water_tank_width": 2,
- "water_tank_height": 1.3,
- "rate_of_fill_enable": true,
- "current_reading_enable": true,
- "percentage_enable": true,
- "filling_status_enable": true,
- "expected_to_fill_enable": true,
- "accent_color": "F05FAF5F",
- "dark_accent_color": "FF5F7F5F",
- "danger_color": "F0AF2F1F",
- "water_color": "F00F7FAF",
- "water_color_above_alert": "F00FAF0F",
- "background_color": "1F1F1F",
- "font_normal_text_size": 16,
- "font_enlarged_text_size": 20
+ // visibility: Window.FullScreen
+ Component.onCompleted: {
+ loading_popup.open();
+ }
+
+
+ Popup {
+ id: loading_popup
+ background: Rectangle {
+ color: "#B0121212"
+ radius: 5
+ border.color: root.accent_color
+ border.width: 1
+ }
+ height: parent.height / 10
+ width: parent.width / 1.1
+ anchors.centerIn: Overlay.overlay
+ closePolicy: Popup.NoAutoClose
+ BusyIndicator {
+ id: loading_indicator
+ running: true
+ anchors {
+ left: parent.left
+ verticalCenter: parent.verticalCenter
+ }
+ }
+ Text {
+ id: loading_text
+ text: "Loading..."
+ anchors {
+ leftMargin: 20
+ left: loading_indicator.right
+ verticalCenter: parent.verticalCenter
+ }
+ verticalAlignment: Text.AlignVCenter
+ color: root.accent_color
+ font.pointSize: root.font_enlarged_text_size
+ }
+
}
Rectangle {
anchors.fill: parent
- color: root.background_color
- id: root_rect
+ id: background
+ color: "#DF0F0F0F"
Rectangle {
id: top_bar
height: Math.min(root.height / 12, 100)
@@ -64,321 +76,729 @@ Window {
topMargin: 25
}
color: "#0A0F0F0F"
- SettingsBarIcon {
- id: settings_icon
+
+ Rectangle {
+ id: settings_rect
height: top_bar.height - 20
- width: height
+ width: top_bar.width / 12
anchors {
top: parent.top
- right: parent.right
+ left: parent.left
bottomMargin: 5
topMargin: 5
leftMargin: 5
}
+ Behavior on color {
+ ColorAnimation {
+ duration: 200
+ }
+ }
+
color: "#0A0F0F0F"
- radius: 100
- accent_color: root.accent_color
- onOpen: {
- settings_popup.open()
+ 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
+ }
}
}
- SettingsBarIcon {
- height: top_bar.height - 20
- width: height
+
+
+ Rectangle {
+ property color old_color: "#0F0F0F0F"
+ id: status_indicator
+ width: 20
+ height: 20
+ radius: 20
anchors {
top: parent.top
- left: parent.left
+ right: parent.right
+ topMargin: 20
bottomMargin: 5
- topMargin: 10
leftMargin: 5
+ rightMargin: 20
}
- text: "⇅"
- color: "#0A0F0F0F"
- radius: 100
- accent_color: root.accent_color
- onOpen: {
- request_response_popup.open()
+ color: "#F0AF2F1F"
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ shadowEnabled: true
}
}
- }
-
- RequestResponsePopup {
- id: request_response_popup
- accent_color: root.accent_color
- font_enlarged_text_size: 20
-
- }
-
- SettingsPopup {
- id: settings_popup
- accent_color: root.accent_color
- background_color: root.background_color
- font_enlarged_text_size: root.font_enlarged_text_size
- font_normal_text_size: root.font_normal_text_size
- function setConfig(new_config: var) {
- settings_popup.config.max = new_config.max
- settings_popup.config.min = new_config.min
- settings_popup.config.alert = new_config.alert
- settings_popup.config.url = new_config.url
+ 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"
}
- onSaveConfig: (config) => {
- console.log("Saving config: ", config)
- config_interface.saveConfig(config);
- }
- onDeleteConfig: () => {
- config_interface.deleteConfig();
- }
- }
-
- Text {
- id: connection_text
- anchors {
- top: top_bar.bottom
- topMargin: 20
- horizontalCenter: parent.horizontalCenter
- }
- text: root.connection_text_string
- color: root.accent_color
- font.pointSize: root.font_normal_text_size
- }
-
- WaterTank {
- id: tank
- anchors {
- top: connection_text.bottom
- horizontalCenter: parent.horizontalCenter
- topMargin: 40
- }
- height: (root_rect.height) / root.water_tank_height_factor
- width: root_rect.width / root.water_tank_width_factor
- base_color: root.background_color
- distance: 0
- tank_lid_color: "#F0121212"
- tank_base_color: "#0FFFFFFF"
- water_level_color: root.water_color
- tank_base_border_color: "#F0121212"
- water_color_above_alert: root.water_color_above_alert
- min_thres_input: 0
- max_thres_input: 0
- text_color: root.accent_color
- font_normal_text_size: root.font_normal_text_size
- font_enlarged_text_size: root.font_enlarged_text_size
- }
-
-
- Text {
- id: alarm_status
- text: root.alarm_status_text
- visible: (root.alarm_status_text.length > 0 ) ? true : false
- anchors {
- bottom: status.top
- bottomMargin: 10
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- color: root.accent_color
- font.pointSize: root.font_normal_text_size
- }
-
- Text {
- id: status
- text: root.status
- anchors {
- bottom: button_base.top
- bottomMargin: 20
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- color: root.accent_color
- font.pointSize: root.font_normal_text_size
}
Rectangle {
- id: button_base
- color: "#00000000"
- width: parent.width / 1.5
- height: parent.height / 20
+ id: base
+ color: "#0A0F0F0F"
+ width: parent.width
+ height: parent.height - top_bar.height
anchors {
- horizontalCenter: parent.horizontalCenter
+ top: top_bar.bottom
bottom: parent.bottom
- bottomMargin: 30
+ horizontalCenter: parent.horizontalCenter
}
- ConnectButton {
- id: connect_button
- width: parent.width / 2.1
- height: parent.height
- font_color: root.accent_color
- font_enlarged_text_size: root.font_enlarged_text_size
- font_normal_text_size: root.font_normal_text_size
- color: root.background_color
- accent_color: root.accent_color
- anchors {
- left: parent.left
- leftMargin: 5
- bottom: parent.bottom
- bottomMargin: 5
+
+ Popup {
+ id: settings_popup
+ background: Rectangle {
+ color: "#F0121212"
+ radius: 5
+ border.color: root.accent_color
+ border.width: 1
}
- onConnect: {
- if(connect_button.mode === 0)
- {
- requester.start();
- root.status = "Waiting to Send.";
+ height: Math.max(parent.height / 1.2, contentHeight)
+ width: parent.width / 1.1
+ 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
}
- else {
- root.status = "Stopped.";
- requester.stop();
+
+
+ Text {
+ id: max_thres_label
+ text: "Max Threshold: "
+ anchors {
+ topMargin: 20
+ top: settings_label.bottom
+ left: parent.left
+ verticalCenter: max_thres_input.verticalCenter
+ }
+ height: max_thres_input.height
+ verticalAlignment: Text.AlignVCenter
+ color: root.accent_color
+ font.pointSize: root.font_normal_text_size
+ }
+
+ TextField {
+ id: max_thres_input
+ text: config.max
+ anchors {
+ right: parent.right
+ rightMargin: 5
+ top: settings_label.bottom
+ topMargin: 20
+ }
+ width: parent.width / 2
+ color: root.accent_color
+ font.pointSize: root.font_normal_text_size
+ }
+
+
+ Text {
+ id: min_thres_label
+ text: "Min Threshold: "
+ anchors {
+ topMargin: 20
+ top: max_thres_label.bottom
+ left: parent.left
+ verticalCenter: min_thres_input.verticalCenter
+ }
+ height: min_thres_input.height
+ verticalAlignment: Text.AlignVCenter
+ color: root.accent_color
+ font.pointSize: root.font_normal_text_size
+ }
+
+ TextField {
+ id: min_thres_input
+ text: config.min
+ anchors {
+ right: parent.right
+ rightMargin: 5
+ top: max_thres_label.bottom
+ topMargin: 20
+ }
+ width: parent.width / 2
+ color: root.accent_color
+ font.pointSize: root.font_normal_text_size
+ }
+
+
+ Text {
+ id: alert_thres_label
+ text: "Alert Percentage: "
+ anchors {
+ topMargin: 20
+ top: min_thres_label.bottom
+ left: parent.left
+ verticalCenter: alert_thres_input.verticalCenter
+ }
+ height: alert_thres_input.height
+ verticalAlignment: Text.AlignVCenter
+ color: root.accent_color
+ font.pointSize: root.font_normal_text_size
+ }
+
+ TextField {
+ id: alert_thres_input
+ text: config.alert
+ anchors {
+ right: parent.right
+ rightMargin: 5
+ top: min_thres_label.bottom
+ topMargin: 20
+ }
+ width: parent.width / 2
+ color: root.accent_color
+ font.pointSize: root.font_normal_text_size
+ }
+ CheckBox {
+ id: flip_bits_box
+ text: "Flip Bits"
+ checked: config.flipBits
+ anchors {
+ topMargin: 20
+ top: alert_thres_label.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+ contentItem: Text {
+ text: flip_bits_box.text
+ font.pointSize: root.font_normal_text_size
+ color: root.accent_color
+ leftPadding: flip_bits_box.indicator.width + flip_bits_box.spacing
+ }
+ }
+
+ Text {
+ id: settings_mode_text
+ anchors {
+ topMargin: 20
+ top: flip_bits_box.bottom
+ left: parent.left
+ }
+ font.pointSize: root.font_normal_text_size
+ color: root.accent_color
+ text: "Mode: "
+ }
+ Row {
+ id: mode_row
+ anchors {
+ left: settings_mode_text.right
+ right: parent.right
+ verticalCenter: settings_mode_text.verticalCenter
+ leftMargin: 10
+ }
+ RadioButton {
+ id: udp_radio_button
+ checked: config.mode === "udp" ? true : false
+ text: "UDP"
+ font.pointSize: root.font_normal_text_size
+ contentItem: Text {
+ text: udp_radio_button.text
+ font: udp_radio_button.font
+ opacity: enabled ? 1.0 : 0.3
+ color: udp_radio_button.down ? root.dark_accent_color : root.accent_color
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: udp_radio_button.indicator.width + udp_radio_button.spacing
+ }
+ onCheckedChanged: {
+ if(checked) {
+ root.mode = "udp";
+ }
+ }
+ }
+ RadioButton {
+ id: http_radio_button
+ checked: config.mode === "http" ? true : false
+ text: "HTTP"
+ font.pointSize: root.font_normal_text_size
+ contentItem: Text {
+ text: http_radio_button.text
+ font: http_radio_button.font
+ opacity: enabled ? 1.0 : 0.3
+ color: http_radio_button.down ? root.dark_accent_color : root.accent_color
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: http_radio_button.indicator.width + http_radio_button.spacing
+ }
+ onCheckedChanged: {
+ if(checked) {
+ root.mode = "http";
+ }
+ }
+ }
+ }
+ Text {
+ id: http_url_label
+ text: "Url: "
+ anchors {
+ topMargin: 20
+ top: settings_mode_text.bottom
+ left: parent.left
+ verticalCenter: http_url_input.verticalCenter
+ }
+ height: http_url_input.height
+ verticalAlignment: Text.AlignVCenter
+ color: root.accent_color
+ font.pointSize: root.font_normal_text_size
+ }
+
+ TextField {
+ id: http_url_input
+ text: config.url
+ anchors {
+ right: parent.right
+ rightMargin: 20
+ leftMargin: 20
+ top: mode_row.bottom
+ topMargin: 20
+ }
+ enabled: root.mode === "http" ? true : false
+ width: Math.max(parent.width / 2, parent.width - http_url_label.width - anchors.leftMargin)
+ color: root.accent_color
+ font.pointSize: root.font_normal_text_size
+ cursorPosition: 0
+ }
+
+ 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: http_url_label.bottom
+ bottom: reset_averages.top
+ bottomMargin: 10
+ left: parent.left
+ horizontalCenter: parent.horizontalCenter
+ }
+ horizontalAlignment: Text.AlignHCenter
+ color: root.danger_color
+ font.pixelSize: root.font_normal_text_size
+ }
+
+ Rectangle {
+ id: reset_averages
+ anchors {
+ topMargin: 10
+ bottom: save_config.top
+ bottomMargin: 10
+ horizontalCenter: parent.horizontalCenter
+ }
+ color: "#F0121212"
+ width: parent.width / 1.3
+ height: 60
+ border {
+ width: 1
+ color: root.accent_color
+ }
+ radius: 3
+
+ Text {
+ anchors.centerIn: parent
+ text: "Reset Averages"
+ 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 = reset_averages.color
+ reset_averages.color = "#5A1F1F1F"
+ }
+ onExited: {
+ reset_averages.color = settings_rect_entered_prev
+ }
+ onPressed: {
+ settings_rect_pressed_prev = reset_averages.color
+ reset_averages.color = "#5A1F4F1F"
+ listener.resetAverages();
+ }
+ onReleased: {
+ reset_averages.color = settings_rect_pressed_prev
+ }
+ }
+
+ }
+
+ Rectangle {
+ id: save_config
+ anchors {
+ topMargin: 10
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+ color: "#F0121212"
+ width: parent.width / 1.3
+ 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);
+ let flipBits = flip_bits_box.checked;
+ let mode = root.mode;
+ let url = http_url_input.text;
+ console.log("Parsed values:", max, min, alert, flip_bits_box.checked);
+ 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.setConfig(alert_effective_reading, flipBits, mode, url);
+ config.saveConfig(max_thres_input.text, min_thres_input.text, alert_thres_input.text, flip_bits_box.checked, mode, url);
+ settings_popup.close();
+ }
+ onReleased: {
+ save_config.color = settings_rect_pressed_prev
+ }
+ }
+
}
}
}
- ConnectButton {
- id: start_alarm
- width: parent.width / 2.1
- height: parent.height
- font_color: root.accent_color
- font_enlarged_text_size: root.font_enlarged_text_size
- font_normal_text_size: root.font_normal_text_size
- color: root.background_color
- accent_color: root.accent_color
- text: "Start Alarm"
- text2: "Stop Alarm"
+ Rectangle {
+ id: tank_lid
+ color: "#F0121212"
anchors {
- left: connect_button.right
- leftMargin: 10
- bottom: parent.bottom
+ 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
+ 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
}
- onConnect: {
- if(start_alarm.mode === 0)
- {
- requester.startAlarm();
- root.alarm_status_text = "Started Alarm; Waiting for Trigger: " + root.root_config.alert
+ Behavior on color {
+ ColorAnimation {
+ duration: 200
}
- else {
- requester.stopAlarm();
- root.alarm_status_text = "";
+ }
+ 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: last_updated_text
+ property string last_updated: ""
+ text: "Last Updated On Server On: " + last_updated
+ anchors {
+ top: info_percent_text.bottom
+ topMargin: 10
+ horizontalCenter: parent.horizontalCenter
+ }
+ horizontalAlignment: Text.AlignHCenter
+ color: root.accent_color
+ font.pixelSize: root.font_normal_text_size
+ visible: config.mode === "http" ? true : false
+ }
+
+ Text {
+ id: error_text
+ property string error_string: ""
+ text: "Error: " + error_string
+ visible: error_string === "" ? false : true;
+ anchors {
+ top: last_updated_text.bottom
+ topMargin: 10
+ horizontalCenter: parent.horizontalCenter
+ }
+ horizontalAlignment: Text.AlignHCenter
+ color: root.danger_color
+ font.pixelSize: root.font_normal_text_size
+ }
+
}
-
-
}
- RequestWorker {
- id: requester
- onStatusChanged: () => {
- root.status = requester.status;
- }
- onNetworkReply: (water_level, fill_rate, expected, data) => {
- tank.setDistance(water_level);
- tank.rate_of_fill = fill_rate;
- tank.expected_to_fill = expected;
- status.color = root.accent_color;
- request_response_popup.request_response_string = data;
- }
- onRequestFailed: (data) => {
- status.color = root.danger_color;
- request_response_popup.request_response_string = data;
- }
- onConnectionText: (text, error) => {
- root.connection_text_string = text
- if(!error) {
- connection_text.color = root.accent_color
- }
- else {
- connection_text.color = root.danger_color
- }
+ Timer {
+ id: status_indicator_timer
+ running: false
+ repeat: false
+ triggeredOnStart: false
+ interval: 300
+ onTriggered: {
+ status_indicator.color = status_indicator.old_color;
}
}
ConfigInterface {
- id: config_interface
- fileName: "config.json"
- onConfig: (config) => {
- settings_popup.config = config;
- console.log("Loading initial config");
- setConfig(config);
- if(root.status === "Initializing") {
- root.status = "Press Connect.";
- }
+ id: config
+ onConfigData: (max, min, alert, mode, url) => {
+ console.log("Got config data as: ", max, min, alert, mode, url);
}
- onConfigDeleted: () => {
- let config = base_config;
- settings_popup.config = config;
- console.log("Loading initial config");
- setConfig(config);
- if(root.status === "Initializing") {
- root.status = "Press Connect.";
- }
- }
-
- function setConfig(config) {
- tank.setConfig(config);
- requester.setConfig(config);
- root.root_config = config;
- if(config.water_tank_height === undefined) {
- config.water_tank_height = 2
- }
- if(config.water_tank_width === undefined) {
- config.water_tank_width = 2.2
- }
- if(config.rate_of_fill_enable === undefined) {
- config.rate_of_fill_enable = true
- }
- if(config.current_reading_enable === undefined) {
- config.current_reading_enable = true
- }
- if(config.percentage_enable === undefined) {
- config.percentage_enable = true
- }
- if(config.filling_status_enable === undefined) {
- config.filling_status_enable = true
- }
- if(config.expected_to_fill_enable === undefined) {
- config.expected_to_fill_enable = true
- }
- if(config.font_enlarged_text_size === undefined) {
- config.font_enlarged_text_size = 20
- }
- if(config.font_normal_text_size === undefined) {
- config.font_normal_text_size = 16
- }
- if(config.accent_color === undefined) {
- config.accent_color = "F05FAF5F"
- }
- if(config.danger_color === undefined) {
- config.danger_color = "F0AF2F1F"
- }
- if(config.dark_accent_color === undefined) {
- config.dark_accent_color = "FF5F7F5F"
- }
- if(config.water_color === undefined) {
- config.water_color = "F00F7FAF"
- }
- if(config.water_color_above_alert === undefined) {
- config.water_color_above_alert = "F00FAF0F"
- }
- if(config.background_color === undefined) {
- config.background_color = "1F1F1F"
- }
- root.water_tank_height_factor = config.water_tank_height
- root.water_tank_width_factor = config.water_tank_width
- root.accent_color = "#" + config.accent_color
- root.danger_color = "#" + config.danger_color
- root.dark_accent_color = "#" + config.dark_accent_color
- root.water_color = "#" + config.water_color
- root.water_color_above_alert = "#" + config.water_color_above_alert
- root.background_color = "#" + config.background_color
- root.font_enlarged_text_size = config.font_enlarged_text_size
- root.font_normal_text_size = config.font_normal_text_size
-
+ onModeChanged: {
+ root.mode = mode;
}
}
- onClosing: {
- requester.stopAlarm();
+ Timer {
+ property int count: 0
+ id: init_timer
+ running: true
+ repeat: true
+ interval: 1000
+ onTriggered: {
+ if(count === 0) {
+ listener.startService();
+ count++;
+ return;
+ }
+ else {
+ console.log("Timer triggered saving config, hope everything is loaded by now");
+ 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("Setting initial configs: ", alert_effective_reading, config.flipBits , config.mode, config.url);
+ listener.setConfig(alert_effective_reading, config.flipBits , config.mode, config.url);
+ http_url_input.cursorPosition = 0;
+ init_timer.repeat = false;
+ init_timer.stop();
+ }
+ }
+
+ }
+
+ UDPListener {
+ id: listener
+
+ onShowError: (err) => {
+ error_text.error_string = err;
+ }
+
+ onLastUpdatedChanged: (update, warn) => {
+ console.log("QML Last Updated: ", update);
+ if(!warn)
+ {
+ last_updated_text.last_updated = update;
+ last_updated_text.color = root.accent_color;
+ }
+ else
+ {
+ last_updated_text.last_updated = update;
+ last_updated_text.color = root.danger_color;
+ }
+ }
+
+ onDistanceChanged: {
+ console.log("Distance changed");
+ root.distance = listener.distance;
+ error_text.error_string = "";
+ console.log("Distance count: ", root.distance_count);
+ if(root.distance_count >= root.distance_count_thres) {
+ if(loading_popup.opened) {
+ loading_popup.close();
+ }
+ 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;
+ }
+ if(root.distance_count > 1000) {
+ root.distance_count = root.distance_count_thres;
+ }
+ 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.setConfig(alert_effective_reading, flip_bits_box.checked, root.mode, http_url_input.text);
+ }
+
+ distance_count += 1;
+ }
+ }
+
+ onClosing: (close)=> {
+ if(Qt.platform.os === "android"){
+ // close.accepted = false
+ // listener.moveAppToBackground();
+ // console.log("Moving to backgound");
+ close.accepted = true;
+ } else {
+ close.accepted = true
+ }
+ }
+
+ function dataBlink() {
+ if(status_indicator_timer.running) {
+ status_indicator_timer.stop();
+ }
+ let old_color = "#F05F5F0F";
+ let new_color = "#F04FAF5F";
+ status_indicator.color = new_color;
+ status_indicator.old_color = old_color;
+ status_indicator_timer.start();
}
}
diff --git a/NotificationManager.java b/NotificationManager.java
new file mode 100644
index 0000000..5656db5
--- /dev/null
+++ b/NotificationManager.java
@@ -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 {
+
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7198d51
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# Water Level Sensor
+
+Builds using QtCreator, use Qt Version 6.9.
+Builds for android and desktop.
+On Android Creates a service from service.cpp and android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java.
+Changes in AndroidManifest.xml are important.
+On Desktop just waits for values from a UDP Broadcast on local network with port 8176 and shows those values.
+On Android the service does that, and creates an alert notification if the value in broadcast is less than alert threshold.
+
+
+
+Create a new socket in Worker that will instantly start listening.
+In the service launch the threads and immediately send client hello to the worker socket.
+On client hello send command data to the android service.
+On quit send server bye to android service.
+On server bye deinit the sockets that send data to cpp server.
+On next launch, the command and client threads can be reinitialized from the StartService java class.
\ No newline at end of file
diff --git a/RequestResponsePopup.qml b/RequestResponsePopup.qml
deleted file mode 100644
index 468bcda..0000000
--- a/RequestResponsePopup.qml
+++ /dev/null
@@ -1,30 +0,0 @@
-import QtQuick
-import QtQuick.Effects
-import QtQuick.Controls
-import QtQuick.Controls.Material
-
-Popup {
- id: request_response_popup
- property color accent_color: "#00000000"
- property int font_enlarged_text_size: 20
- property int font_normal_text_size: 16
- property string request_response_string: ""
- background: Rectangle {
- color: "#F0121212"
- radius: 5
- border.color: request_response_popup.accent_color
- border.width: 1
- }
- height: Math.max(parent.height / 1.6, contentHeight)
- width: parent.width / 1.2
- anchors.centerIn: Overlay.overlay
- ScrollView {
- id: view
- anchors.fill: parent
- TextArea {
- text: request_response_string
- font.pointSize: font_normal_text_size
- color: request_response_popup.accent_color
- }
- }
-}
diff --git a/SettingsBarIcon.qml b/SettingsBarIcon.qml
deleted file mode 100644
index 35072b7..0000000
--- a/SettingsBarIcon.qml
+++ /dev/null
@@ -1,49 +0,0 @@
-import QtQuick
-import QtQuick.Effects
-import QtQuick.Controls
-import QtQuick.Controls.Material
-
-Rectangle {
- id: settings_rect
- signal open;
- property color accent_color: "#00000000"
- property int font_enlarged_text_size: 20
- property string text: "⚙"
- Behavior on color {
- ColorAnimation {
- duration: 200
- }
- }
- Text {
- id: settings_text
- text: settings_rect.text
- anchors.centerIn: parent
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- color: settings_rect.accent_color
- font.pointSize: settings_rect.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_rect.open();
- }
- onReleased: {
- settings_rect.color = settings_rect_pressed_prev
- }
- }
-}
diff --git a/SettingsPopup.qml b/SettingsPopup.qml
deleted file mode 100644
index 6a97c2b..0000000
--- a/SettingsPopup.qml
+++ /dev/null
@@ -1,594 +0,0 @@
-import QtQuick
-import QtQuick.Effects
-import QtQuick.Controls
-import QtQuick.Controls.Material
-
-Popup {
- id: settings_popup
- signal saveConfig(config: var)
- signal deleteConfig()
- property color accent_color: "#00000000"
- property int font_enlarged_text_size: 20
- property int font_normal_text_size: 16
- property color background_color: "#00000000"
- property var config: {
- "max": 0,
- "min": 0,
- "alert": 0,
- "interval": 0,
- "url": "none",
- "water_tank_width": 2,
- "water_tank_height": 1.3,
- "rate_of_fill_enable": true,
- "current_reading_enable": true,
- "percentage_enable": true,
- "filling_status_enable": true,
- "expected_to_fill_enable": true,
- "accent_color": "F05FAF5F",
- "dark_accent_color": "FF5F7F5F",
- "danger_color": "F0AF2F1F",
- "water_color": "F00F7FAF",
- "water_color_above_alert": "F00FAF0F",
- "background_color": "1F1F1F",
- "font_normal_text_size": 16,
- "font_enlarged_text_size": 20
- }
-
- background: Rectangle {
- color: "#F0121212"
- radius: 5
- border.color: settings_popup.accent_color
- border.width: 1
- }
- height: Math.max(parent.height / 1.6, contentHeight)
- width: parent.width / 1.2
- anchors.centerIn: Overlay.overlay
- Rectangle {
- id: settings_base
- anchors.fill: parent
- color: "#F0121212"
-
- ScrollView {
- id: rect_base_scroll_view
- anchors {
- top: parent.top
- horizontalCenter: parent.horizontalCenter
- bottom: save_config.top
- bottomMargin: 10
- }
- width: parent.width
- clip: true
-
- // Use contentWidth and contentHeight to define the scrollable area
- contentWidth: availableWidth
- contentHeight: settings_column.height
-
- Column {
- id: settings_column
- width: rect_base_scroll_view.width
- spacing: 20
-
- Text {
- id: settings_label
- text: "Settings"
- width: parent.width
- horizontalAlignment: Text.AlignHCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_enlarged_text_size
- }
-
- // Max Threshold
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Max Threshold: "
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: max_thres_input.verticalCenter
- }
-
- }
- TextField {
- id: max_thres_input
- text: config.max
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Min Threshold
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Min Threshold: "
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: min_thres_input.verticalCenter
- }
-
- }
- TextField {
- id: min_thres_input
- text: config.min
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Alert Threshold
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Alert Threshold: "
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: alert_thres_input.verticalCenter
- }
-
- }
- TextField {
- id: alert_thres_input
- text: config.alert
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Update Interval
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Update Interval: "
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: interval_input.verticalCenter
- }
- }
- TextField {
- id: interval_input
- text: config.interval
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // URL
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Url: "
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: http_url_input.verticalCenter
- }
-
- }
- TextField {
- id: http_url_input
- text: config.url
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- cursorPosition: 0
- }
- }
-
- // Tank Image Width
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Tank Image Width"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: water_tank_width_input.verticalCenter
- }
-
- }
- TextField {
- id: water_tank_width_input
- text: String(config.water_tank_width)
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- cursorPosition: 0
- }
- }
-
- // Tank Image Height
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Tank Image Height"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: water_tank_height_input.verticalCenter
- }
- }
- TextField {
- id: water_tank_height_input
- text: String(config.water_tank_height)
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- cursorPosition: 0
- }
- }
-
- // Accent Color
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Accent Color"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: accent_color_input.verticalCenter
- }
- }
- ColorInput {
- id: accent_color_input
- text: config.accent_color
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Dark Accent Color
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Dark Accent Color"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: dark_accent_color_input.verticalCenter
- }
- }
- ColorInput {
- id: dark_accent_color_input
- text: config.dark_accent_color
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Danger Color
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Danger Text Color"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: danger_color_input.verticalCenter
- }
- }
- ColorInput {
- id: danger_color_input
- text: config.danger_color
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Water Color
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Water Color"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: water_color_input.verticalCenter
- }
- }
- ColorInput {
- id: water_color_input
- text: config.water_color
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Alert Water Color
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Alert Water Color"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: water_color_above_alert_input.verticalCenter
- }
- }
- ColorInput {
- id: water_color_above_alert_input
- text: config.water_color_above_alert
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Background Color
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Backgound Color"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: background_color_input.verticalCenter
- }
- }
- ColorInput {
- id: background_color_input
- text: config.background_color
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
- }
-
- // Normal Font Size
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Font Size"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: font_normal_text_size_input.verticalCenter
- }
- }
- TextField {
- id: font_normal_text_size_input
- text: String(config.font_normal_text_size)
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- cursorPosition: 0
- }
- }
-
- // Header Font Size
- Row {
- width: parent.width
- spacing: 10
- Text {
- text: "Header Font Size"
- width: parent.width * 0.4
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- anchors {
- verticalCenter: font_enlarged_text_size_input.verticalCenter
- }
- }
- TextField {
- id: font_enlarged_text_size_input
- text: String(config.font_enlarged_text_size)
- width: parent.width * 0.55
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- cursorPosition: 0
- }
- }
-
- // CheckBox
- CustomCheckBox {
- id: current_reading_enable
- checked: config.current_reading_enable
- font_color: settings_popup.accent_color
- font_point_size: settings_popup.font_normal_text_size
- text: "Enable Current Reading"
- leftPadding: 0
- }
-
- // CheckBox
- CustomCheckBox {
- id: percentage_enable
- checked: config.percentage_enable
- font_color: settings_popup.accent_color
- font_point_size: settings_popup.font_normal_text_size
- text: "Enable Percentage Reading"
- leftPadding: 0
- }
-
- // CheckBox
- CustomCheckBox {
- id: filling_status_enable
- checked: config.filling_status_enable
- font_color: settings_popup.accent_color
- font_point_size: settings_popup.font_normal_text_size
- text: "Enable Filling Status"
- leftPadding: 0
- }
-
- // CheckBox
- CustomCheckBox {
- id: rate_of_fill_enable
- checked: config.rate_of_fill_enable
- font_color: settings_popup.accent_color
- font_point_size: settings_popup.font_normal_text_size
- text: "Enable Text Rate of Fill"
- leftPadding: 0
- }
-
- // CheckBox
- CustomCheckBox {
- id: expected_to_fill_enable
- checked: config.expected_to_fill_enable
- font_color: settings_popup.accent_color
- font_point_size: settings_popup.font_normal_text_size
- text: "Enable Expected to fill"
- leftPadding: 0
- }
-
- ConnectButton {
- id: delete_config_button
- width: parent.width / 1.5
- height: 80
- anchors {
- horizontalCenter: parent.horizontalCenter
- }
-
- font_color: settings_popup.accent_color
- font_enlarged_text_size: settings_popup.font_enlarged_text_size
- font_normal_text_size: settings_popup.font_normal_text_size
- color: settings_popup.background_color
- accent_color: settings_popup.accent_color
- text: "Delete/Reset Config"
- text2: text
- onConnect: {
- settings_popup.deleteConfig();
- }
- }
- }
- }
- Rectangle {
- id: save_config
- anchors {
- topMargin: 10
- bottom: parent.bottom
- horizontalCenter: parent.horizontalCenter
- }
- color: "#F0121212"
- width: parent.width / 1.3
- height: 60
- border {
- width: 1
- color: settings_popup.accent_color
- }
- radius: 3
-
- Text {
- anchors.centerIn: parent
- text: "Save"
- color: settings_popup.accent_color
- font.pointSize: settings_popup.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(max_thres_input.text);
- let min = parseFloat(min_thres_input.text);
- let alert = parseFloat(alert_thres_input.text);
- let url = http_url_input.text;
- let interval = parseInt(interval_input.text);
- let image_width = parseFloat(water_tank_width_input.text);
- let image_height = parseFloat(water_tank_height_input.text);
-
- let current_reading = current_reading_enable.checked;
- let percentage_reading = percentage_enable.checked
- let filling_status = filling_status_enable.checked;
- let rate_of_fill = rate_of_fill_enable.checked;
- let expected_to_fill = expected_to_fill_enable.checked;
-
- let normal_font_size = parseInt(font_normal_text_size_input.text);
- let enlarged_font_size = parseInt(font_enlarged_text_size_input.text);
-
- let accent_color = accent_color_input.text;
- let dark_accent_color = dark_accent_color_input.text;
- let danger_color = danger_color_input.text;
- let water_color = water_color_input.text;
- let water_color_above_alert = water_color_above_alert_input.text;
- let background_color = background_color_input.text;
-
- var new_config = {"max": max, "min": min, "alert": alert, "url": url,
- "interval": interval,
- "water_tank_height": image_height,
- "water_tank_width": image_width,
- "current_reading_enable": current_reading,
- "percentage_enable": percentage_reading,
- "filling_status_enable": filling_status,
- "rate_of_fill_enable": rate_of_fill,
- "expected_to_fill_enable": expected_to_fill,
- "font_normal_text_size": normal_font_size,
- "font_enlarged_text_size": enlarged_font_size,
- "accent_color": accent_color,
- "dark_accent_color": dark_accent_color,
- "danger_color": danger_color,
- "water_color": water_color,
- "water_color_above_alert": water_color_above_alert,
- "background_color": background_color
- }
- settings_popup.saveConfig(new_config)
- settings_popup.close();
- }
- onReleased: {
- save_config.color = settings_rect_pressed_prev
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/WaterTank.qml b/WaterTank.qml
deleted file mode 100644
index 633fac8..0000000
--- a/WaterTank.qml
+++ /dev/null
@@ -1,204 +0,0 @@
-import QtQuick
-import QtQuick.Effects
-import QtQuick.Controls
-import QtQuick.Controls.Material
-
-Item {
- id: root
- property color base_color: "#000F0F0F"
- property color tank_lid_color: "#F0121212"
- property color tank_base_color: "#00FFFFFF"
- property color water_level_color: "#F00F7FAF"
- property color water_color_above_alert: "#F00FAF0F"
- property color tank_base_border_color: "#F0121212"
- property color text_color: "#F05FAF5F"
- property double height_max: tank_base.height - 10
- property int font_normal_text_size: 0
- property int font_enlarged_text_size: 0
- property double current_level: 0
- property double percentage_filled: 0
- property int distance: 0
- property double min_thres_input: 0
- property double max_thres_input: 0
- property double rate_of_fill: 0
- property double expected_to_fill: 0
- property double alert_threshold: 0
- property bool enable_current_reading: true
- property bool enable_percentage_reading: true
- property bool enable_filling_status: true
- property bool enable_rate_of_fill: true
- property bool enable_expected_to_fill: true
-
- Rectangle {
- id: base
- color: root.base_color
- anchors.fill: parent
- Rectangle {
- id: tank_lid
- color: root.tank_lid_color
- anchors {
- top: parent.top
- horizontalCenter: tank_base.horizontalCenter
- bottomMargin: -5
- }
- radius: 10
- width: tank_base.width / 3
- height: tank_base.height / 15
- }
-
- Rectangle {
- id: tank_base
- color: root.tank_base_color
- anchors {
- top: tank_lid.bottom
- horizontalCenter: parent.horizontalCenter
- }
- width: parent.width
- height: parent.height * 0.55
- border.color: root.tank_base_border_color
- border.width: 5
- radius: 4
- }
-
- Rectangle {
- id: water_level
- color: root.water_level_color
- height: root.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
- }
- }
- }
-
- ScrollView {
- id: rect_base_scroll_view
- anchors {
- margins: 5
- top: tank_base.bottom
- bottom: parent.bottom
- horizontalCenter: parent.horizontalCenter
- }
- width: parent.width
-
- contentWidth: parent.width
- contentHeight: settings_column.implicitHeight
- Column {
- id: settings_column
- spacing: 10
- width: rect_base_scroll_view.width
- Text {
- id: water_level_reading
- text: "Current Reading: " + (root.distance / 10)+ " cm"
- font.pointSize: root.font_normal_text_size
- color: root.text_color
- visible: enable_current_reading
- anchors {
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- }
-
- Text {
- id: water_level_percentage
- text: "Percentage: " + root.percentage_filled.toFixed(2) + " %"
- font.pointSize: root.font_normal_text_size
- visible: enable_percentage_reading
- color: root.text_color
- anchors {
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- }
-
- Text {
- id: fill_status
- text: "Filling status: " + (( root.rate_of_fill > 0)? "Filling" : "Discharging")
- font.pointSize: root.font_normal_text_size
- color: root.text_color
- visible: enable_filling_status
- anchors {
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- }
- Text {
- id: rate_of_fill_label
- text: "Rate of fill: " + root.rate_of_fill + " cm/s"
- font.pointSize: root.font_normal_text_size
- color: root.text_color
- visible: enable_rate_of_fill
- anchors {
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- }
- Text {
- id: expected_to_fill_label
- text: "Expected to fill in: " + root.expected_to_fill + " s"
- font.pointSize: root.font_normal_text_size
- color: root.text_color
- visible: (root.rate_of_fill > 0 && enable_expected_to_fill === true) ? true : false
- anchors {
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- }
- }
- }
-
- }
- function setConfig(config: var) {
- min_thres_input = config.min;
- max_thres_input = config.max;
- alert_threshold = config.alert;
- enable_current_reading = config.current_reading_enable;
- enable_percentage_reading = config.percentage_enable;
- enable_filling_status = config.filling_status_enable;
- enable_rate_of_fill = config.rate_of_fill_enable;
- enable_expected_to_fill = config.expected_to_fill_enable;
-
- }
-
- function setDistance(distance) {
- root.distance = distance;
- let distance_f = distance / 10;
- console.log("Distance changed to: ", distance_f);
- let current_min_reading = Math.max(0, min_thres_input);
- let effective_max_reading = Math.max(0, max_thres_input) - current_min_reading;
- let current_reading = Math.max(0, distance_f - current_min_reading);
- console.log("Current reading is: " + current_reading)
- console.log("Current min reading is: " + current_min_reading)
- console.log("Effective max reading is: " + effective_max_reading)
-
- let height_max_possible = root.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;
- console.log("Current reading is: " + current_reading)
- console.log("Normalized height is: " + normalized_height)
- console.log("Percentage filled is: " + percentage_filled)
- console.log("Alert threshold is: " + root.alert_threshold)
- console.log("Is level above alert threshold: " + (distance_f <= root.alert_threshold))
- root.current_level = normalized_height;
- root.percentage_filled = percentage_filled;
-
- if(distance_f <= root.alert_threshold) {
- water_level.color = water_color_above_alert
- }
- else {
- water_level.color = water_level_color
- }
- }
-}
-
diff --git a/WaterTank1.png b/WaterTank1.png
new file mode 100644
index 0000000..24ef3c1
Binary files /dev/null and b/WaterTank1.png differ
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 2598885..ead6548 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -1,18 +1,18 @@
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -38,11 +49,9 @@
-
-
+ android:value="appwaterLevelSensor" />
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..ea6c193
--- /dev/null
+++ b/android/build.gradle
@@ -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(",")
+ }
+}
diff --git a/android/res/drawable-hdpi/ic_launcher.png b/android/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..7d0f630
Binary files /dev/null and b/android/res/drawable-hdpi/ic_launcher.png differ
diff --git a/android/res/drawable-mdpi/ic_launcher.png b/android/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..9260e4e
Binary files /dev/null and b/android/res/drawable-mdpi/ic_launcher.png differ
diff --git a/android/res/drawable-xhdpi/ic_launcher.png b/android/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..c3e5ba9
Binary files /dev/null and b/android/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/android/res/drawable-xxhdpi/ic_launcher.png b/android/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..1257bb6
Binary files /dev/null and b/android/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/android/res/drawable-xxxhdpi/ic_launcher.png b/android/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4ed8a75
Binary files /dev/null and b/android/res/drawable-xxxhdpi/ic_launcher.png differ
diff --git a/android/src/org/qtproject/example/appwaterLevelSensor/.WaterLevelService.java.kate-swp b/android/src/org/qtproject/example/appwaterLevelSensor/.WaterLevelService.java.kate-swp
new file mode 100644
index 0000000..aac66dc
Binary files /dev/null and b/android/src/org/qtproject/example/appwaterLevelSensor/.WaterLevelService.java.kate-swp differ
diff --git a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelReceiver.java b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelReceiver.java
new file mode 100644
index 0000000..60b004c
--- /dev/null
+++ b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelReceiver.java
@@ -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);
+ }
+ }
+}
diff --git a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java
new file mode 100644
index 0000000..cc765d0
--- /dev/null
+++ b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java
@@ -0,0 +1,536 @@
+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;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONArray;
+import android.widget.Toast;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+
+public class WaterLevelService extends Service {
+
+ private static float alert_thres_distance = 1000000;
+ private static boolean bits_flipped = false;
+ private static String mode = "";
+ private static String url = "";
+ private static Thread udpThread;
+ private static Thread cmdThread;
+ private static boolean resetBinds = false;
+ private final IBinder binder = new LocalBinder();
+ final Messenger mMessenger = new Messenger(new IncomingHandler());
+ public static final int MSG_RESTART_THREADS = 1;
+
+
+ class IncomingHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RESTART_THREADS:
+ Log.d("UDP Service MainThread", "Received IPC request to restart threads");
+ restartThreads();
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+
+ public synchronized void restartThreads() {
+ Log.d("UDP Service MainThread", "In synchronized thread stopper.");
+ udpThread.interrupt();
+ cmdThread.interrupt();
+ try {
+ udpThread.join();
+ cmdThread.join();
+ } catch (InterruptedException e) {
+ Log.d("UDP Service MainThread", "Thread joining was interrupted because of: " + e);
+ }
+
+ new Thread(new
+ Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Move your socket and thread starting logic here
+ if (udpThread == null || !udpThread.isAlive()) {
+ udpThread = new Thread(new UDPClient());
+ udpThread.start();
+ }
+ if (cmdThread == null || !cmdThread.isAlive()) {
+ cmdThread = new Thread(new UDPCommandClient());
+ cmdThread.start();
+ }
+ } catch (Exception e) {
+ Log.e("UDP Service MainThread", "Restarting the threads", e);
+ }
+ }
+ }).start();
+ Log.d("UDP Service MainThread", "Actually restarting the thread");
+ }
+
+ public static synchronized boolean checkBound() {
+ return resetBinds;
+ }
+
+ public static synchronized void setBound(boolean value) {
+ resetBinds = value;
+ }
+
+ public class LocalBinder extends android.os.Binder {
+ WaterLevelService getService() {
+ return WaterLevelService.this;
+ }
+ }
+
+ public class UDPCommandClient implements Runnable {
+ private DatagramSocket socket_cmd_recv;
+ private DatagramSocket socket_cmd_send;
+ private boolean received_cmd_first = false;
+
+ @Override
+ public void run() {
+ Log.d("UDP Service CommandThread", "Command thread initialising");
+ try {
+ socket_cmd_recv = new DatagramSocket(null);
+ socket_cmd_send = new DatagramSocket(null);
+ socket_cmd_recv.setSoTimeout(4000);
+ socket_cmd_recv.setReuseAddress(true);
+ socket_cmd_send.setReuseAddress(true);
+ socket_cmd_recv.bind(new InetSocketAddress(8178));
+ socket_cmd_send.bind(new InetSocketAddress(8187));
+ boolean not_send_set = true;
+ Log.d("UDP Service CommandThread", "Created command thread sockets");
+ while (!Thread.currentThread().isInterrupted()) {
+ if(socket_cmd_recv.isClosed()) {
+ socket_cmd_recv.bind(new InetSocketAddress(8178));
+ }
+ if(socket_cmd_send.isClosed()) {
+ socket_cmd_send.bind(new InetSocketAddress(8187));
+ }
+
+ if(not_send_set) {
+ String s_data = new String("");
+ byte[] buff_s = s_data.getBytes(StandardCharsets.UTF_8);
+ DatagramPacket packet = new DatagramPacket(buff_s, buff_s.length, InetAddress.getByName("127.0.0.1"), 8186);
+ try {
+ socket_cmd_send.connect(InetAddress.getByName("127.0.0.1"), 8186);
+ socket_cmd_send.send(packet);
+ } catch(Exception e) {
+ Log.d("UDP Service CommandThread", "Failed to send data to client due to: " + e);
+ }
+ Log.d("UDP Service CommandThread", "Sent set message to client");
+ }
+
+ byte[] buffer = new byte[1024];
+ DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
+ try {
+ socket_cmd_recv.receive(packet);
+ } catch(java.net.SocketTimeoutException e) {
+ continue;
+ }
+ String str = new String(buffer, StandardCharsets.UTF_8);
+ Log.d("UDP Service CommandThread", "Received command packet " + str);
+ not_send_set = false;
+ if(str.startsWith("", "");
+ alert_thres_distance = Float.parseFloat(str);
+ {
+ if(!checkBound())
+ {
+ setBound(true);
+ Log.d("UDP Service CommandThread", "Set Bound to True");
+ }
+ else {
+ Log.d("UDP Service CommandThread", "Set Bound is False");
+ }
+
+ }
+ }
+ else if(str.startsWith("", "");
+ Log.d("UDP Service CommandThread", "Flipping " + str);
+ if(str.toLowerCase() == "false") {
+ bits_flipped = false;
+ }
+ else if(str.toLowerCase() == "true") {
+ bits_flipped = true;
+ }
+ }
+ else if(str.startsWith("", "");
+ Log.d("UDP Service CommandThread", "Mode " + str);
+ mode = str.toLowerCase();
+ }
+ else if(str.startsWith("", "");
+ Log.d("UDP Service CommandThread", "Url " + str);
+ url = str.toLowerCase();
+ }
+ }
+ }
+ catch (IOException e) {
+ // showToast("IOException: " + e);
+ Log.e("UDP Service CommandThread", "IOException Error" + e);
+ }
+ }
+
+ }
+
+ public class UDPClient implements Runnable {
+ private DatagramSocket socket_recv;
+ private DatagramSocket socket_send;
+ private Instant prevAlertTimestamp;
+ private InetAddress address;
+ private boolean started = false;
+
+ @Override
+ public void run() {
+ Log.d("UDP Service ClientThread", "Client 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.net.wifi.WifiManager.WifiLock wifiLock = wifi.createWifiLock(android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF, "WaterLevel:WifiLock");
+ 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();
+ wifiLock.acquire();
+ socket_recv = new DatagramSocket(null);
+ socket_send = new DatagramSocket(null);
+ socket_recv.setBroadcast(true);
+ socket_recv.setSoTimeout(4000);
+ socket_recv.setReuseAddress(true);
+ socket_send.setReuseAddress(true);
+ socket_send.bind(new InetSocketAddress(8177));
+ socket_recv.bind(new InetSocketAddress(8176));
+ Log.d("UDP Service ClientThread", "Created Sockets");
+ int count = 0;
+ while (!Thread.currentThread().isInterrupted()) {
+ count++;
+ if(count == 3) {
+ Log.i("UDP Service ClientThread", "Thread is running with mode: " + mode);
+ count = 0;
+ }
+ if(checkBound()) {
+ Log.i("UDP Service ClientThread", "Reconnecting sockets");
+ this.address = InetAddress.getByName("127.0.0.1");
+ socket_recv.disconnect();
+ socket_send.disconnect();
+ socket_recv.close();
+ socket_send.close();
+ socket_recv = null;
+ socket_send = null;
+ socket_recv = new DatagramSocket(null);
+ socket_send = new DatagramSocket(null);
+ socket_recv.setBroadcast(true);
+ socket_recv.setSoTimeout(4000);
+ socket_recv.setReuseAddress(true);
+ socket_send.setReuseAddress(true);
+ socket_send.bind(new InetSocketAddress(8177));
+ socket_recv.bind(new InetSocketAddress(8176));
+ setBound(false);
+ Log.i("UDP Service ClientThread", "Reconnected sockets");
+ }
+ // if(checkBound()) {
+ // Log.i("UDP Service", "Reset socket binds: " + mode);
+ // socket_recv.close();
+ // socket_send.close();
+ // socket_send.bind(new InetSocketAddress(8177));
+ // socket_recv.bind(new InetSocketAddress(8176));
+ // setBound(false);
+ // if(socket_send.isBound()) {
+ // Log.i("UDP Service", "Rebound socket: " + 8177);
+ // }
+ // else {
+ // Log.i("UDP Service", "Failed to rebound socket: " + 8177);
+ // }
+ // if(socket_recv.isBound()) {
+ // Log.i("UDP Service", "Rebound socket: " + 8176);
+ // }
+ // else {
+ // Log.i("UDP Service", "Failed to rebound socket: " + 8176);
+ // }
+
+ // }
+ // if(checkBound()) {
+ // Log.i("UDP Service", "Reset socket binds: " + mode);
+ // }
+ // else {
+ // Log.i("UDP Service", "Reset socket binds: " + mode);
+ // }
+ byte[] buffer = null;
+ String updated = "";
+ if(mode.equals("udp")) {
+ try {
+ if(socket_recv.isClosed()) {
+ socket_recv.bind(new InetSocketAddress(8176));
+ }
+ buffer = new byte[4];
+ DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
+ socket_recv.receive(packet);
+ } catch(java.net.SocketTimeoutException e) {
+ // showToast("Socket Timeout: " + e);
+ continue;
+ }
+ }
+ else if(mode.equals("http")) {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ Log.d("UDP Service ClientThread", "Thread exiting due to interrupt.");
+ return;
+ }
+ Log.d("UDP Service ClientThread", "Http String Url: " + url);
+ URL t_url = new URL(url);
+ HttpURLConnection con = (HttpURLConnection) t_url.openConnection();
+ con.setRequestMethod("GET");
+ con.setConnectTimeout(5000);
+ con.setReadTimeout(5000);
+ con.setInstanceFollowRedirects(true);
+ int status = con.getResponseCode();
+ Log.d("UDP Service ClientThread", "Got status from http: " + status);
+ if(status == 200) {
+ BufferedReader streamReader = new BufferedReader(new InputStreamReader(con.getInputStream()));
+ String data = streamReader.readLine();
+ try {
+ JSONObject obj = new JSONObject(data);
+ JSONArray arr = obj.getJSONArray("water_level");
+ float f = (float ) arr.getDouble(0);
+ updated = obj.getString("updated_on");
+ buffer = ByteBuffer.allocate(4).putFloat(f).array();
+ Log.d("UDP Service ClientThread", "Got value from http: " + f);
+ Log.d("UDP Service ClientThread", "Last updated on: " + updated);
+ } catch (JSONException e) {
+ Log.e("UDP Service ClientThread", "Invalid Json");
+ // showToast("Invalid Json" + e);
+ continue;
+ }
+ }
+ else {
+ // showToast("Bad Response From Server: " + status);
+ InputStreamReader streamReader = new InputStreamReader(con.getErrorStream());
+ continue;
+ }
+ }
+ else {
+ Log.d("UDP Service ClientThread", "Invalid mode: " + mode);
+ // showToast("Invalid mode: " + mode);
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ Log.d("UDP Service ClientThread", "Thread exiting due to interrupt.");
+ return;
+ }
+ continue;
+ }
+ float f;
+ if(bits_flipped) {
+ f = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ }
+ else {
+ f = ByteBuffer.wrap(buffer).order(ByteOrder.BIG_ENDIAN).getFloat();
+ }
+ Log.d("UDP Service ClientThread", "Received packet: " + f);
+ Log.d("UDP Service ClientThread", "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() > 60) {
+ 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_LOW);
+ manager.notify(2, builder.build());
+ Log.d("UDP Service ClientThread", "Notification sent");
+ if(!started) {
+ started = true;
+ }
+ }
+ if(socket_send.isClosed()) {
+ socket_send.bind(new InetSocketAddress(8177));
+ }
+ DatagramPacket send_packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("127.0.0.1"), 8180);
+ socket_send.send(send_packet);
+ Log.d("UDP Service ClientThread", "Sent packet");
+ if(mode.equals("http")) {
+ byte[] buff2 = updated.getBytes(StandardCharsets.UTF_8);
+ DatagramPacket send_packet_2 = new DatagramPacket(buff2, buff2.length, InetAddress.getByName("127.0.0.1"), 8185);
+ socket_send.send(send_packet_2);
+ Log.d("UDP Service ClientThread", "Sent last updated packet");
+ }
+ }
+ }
+ catch (IOException e) {
+ // showToast("IOException: " + e);
+ Log.e("UDP Service ClientThread", "IOException Error" + e);
+ } finally {
+ if (wifiLock.isHeld())
+ wifiLock.release();
+ if (multicastLock.isHeld())
+ multicastLock.release();
+ if (wakeLock.isHeld())
+ wakeLock.release();
+ if (socket_recv != null)
+ socket_recv.close();
+ }
+ }
+ }
+
+ public void startWaterLevelService() {
+ Log.d("UDP Service MainThread", "Starting the service from Activity");
+
+ Intent intent = new Intent(this, WaterLevelService.class);
+
+ startForegroundService(intent);
+ }
+ public WaterLevelService() {
+ super();
+ Log.i("UDP Service MainThread", "Class is created");
+ }
+
+ @Override
+ public android.os.IBinder onBind(android.content.Intent intent) { return mMessenger.getBinder(); }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Notification notification = createNotification();
+ startForeground(1, notification);
+ Log.i("UDP Service MainThread", "Creating Service");
+
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.i("UDP Service MainThread", "Destroying Service");
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Notification notification = createNotification();
+ startForeground(1, notification);
+
+
+ if (intent == null) {
+ Log.w("UDP Service MainThread", "Service restarted with null intent. Stopping to avoid crash.");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+ new Thread(new
+ Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Move your socket and thread starting logic here
+ if (udpThread == null || !udpThread.isAlive()) {
+ udpThread = new Thread(new UDPClient());
+ udpThread.start();
+ }
+ if (cmdThread == null || !cmdThread.isAlive()) {
+ cmdThread = new Thread(new UDPCommandClient());
+ cmdThread.start();
+ }
+ } catch (Exception e) {
+ Log.e("UDP Service MainThread", "Error starting threads", e);
+ }
+ }
+ }).start();
+
+ Log.d("UDP Service MainThread", "Actually starting the thread");
+
+ int ret = super.onStartCommand(intent, flags, startId);
+
+ // (new Thread(new UDPClient())).start();
+ // (new Thread(new UDPCommandClient())).start();
+
+ return START_NOT_STICKY;
+ }
+
+ public void showToast(String str) {
+ int duration = Toast.LENGTH_SHORT;
+ Toast toast = Toast.makeText(this, str, duration);
+ toast.show();
+ }
+
+ 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 MainThread", "Starting the service");
+ Intent intent = new Intent(activity, WaterLevelService.class);
+ activity.startForegroundService(intent);
+
+ }
+}
diff --git a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelServiceStarter.java b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelServiceStarter.java
new file mode 100644
index 0000000..9dccce9
--- /dev/null
+++ b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelServiceStarter.java
@@ -0,0 +1,118 @@
+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 android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import org.qtproject.qt.android.bindings.QtActivity;
+import android.os.Handler;
+import android.os.Looper;
+import android.content.ServiceConnection;
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+public class WaterLevelServiceStarter {
+ private static Messenger mServiceMessenger = null;
+ private static boolean mBound = false;
+ private static boolean restart_bind = false;
+
+ private static ServiceConnection connection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mServiceMessenger = new Messenger(service);
+ mBound = true;
+ Log.d("UDP Service StarterThread", "Connected to Service object reference");
+ if(restart_bind)
+ {
+ sendRestartMessage();
+ Log.d("UDP Service StarterThread", "Restart Threads in Service.");
+ }
+ restart_bind = false;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ mServiceMessenger = null;
+ mBound = false;
+ }
+ };
+
+ public static void startAndBindService(Context context) {
+ Intent intent = new Intent(context, WaterLevelService.class);
+ context.startForegroundService(intent);
+ context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ }
+
+ public static void bindService(Context context) {
+ restart_bind = true;
+ Intent intent = new Intent(context, WaterLevelService.class);
+ context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ }
+
+ private static boolean isMyServiceRunning(Class> serviceClass, Context context) {
+ ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (serviceClass.getName().equals(service.service.getClassName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ private static int getServicePid(Class> serviceClass, Context context) {
+ ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (serviceClass.getName().equals(service.service.getClassName())) {
+ return service.pid;
+ }
+ }
+ return 0;
+ }
+
+
+ public static void sendRestartMessage() {
+ if (!mBound || mServiceMessenger == null) {
+ Log.d("UDP Service StarterThread", "Service is not running.");
+ }
+ try {
+ Message msg = Message.obtain(null, WaterLevelService.MSG_RESTART_THREADS);
+ mServiceMessenger.send(msg);
+ Log.d("UDP Service StarterThread", "Sent restart signal to remote process");
+ } catch (RemoteException e) {
+ Log.e("UDP Service StarterThread", "Failed to send message", e);
+ }
+ }
+
+ public static void startService(Context context) {
+ // Log.d("UDP Service", "Starting service via QtActivity instance");
+
+ // if(!isMyServiceRunning(WaterLevelService.class, context)) {
+ // Intent intent = new Intent(context, WaterLevelService.class);
+ // context.startForegroundService(intent);
+ // }
+
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+
+ if (!isMyServiceRunning(WaterLevelService.class, context)) {
+ Log.d("UDP Service StarterThread", "Starting service safely on Main Looper");
+ startAndBindService(context);
+ }
+ else {
+ Log.d("UDP Service StarterThread", "Service already running, so restarting the threads.");
+ bindService(context);
+ }
+ Log.d("UDP Service StarterThread", "Background service running with pid: " + getServicePid(WaterLevelService.class, context));
+ }
+ });
+ }
+}
diff --git a/configinterface.cpp b/configinterface.cpp
index 8f40d45..73ae20a 100644
--- a/configinterface.cpp
+++ b/configinterface.cpp
@@ -1,60 +1,168 @@
#include "configinterface.h"
-#include
-#include
-#include
-
ConfigInterface::ConfigInterface(QObject *parent)
: QObject{parent}
{
-}
-
-QString ConfigInterface::fileName() const
-{
- return m_fileName;
-}
-
-void ConfigInterface::setFileName(const QString &newFileName)
-{
- if (m_fileName == newFileName)
- return;
- m_fileName = newFileName;
- std::fstream input(newFileName.toStdString(), std::fstream::in);
- if(!input.is_open()) {
- return;
+ 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::string data;
- std::string temp;
- while(std::getline(input, temp)) {
- data += temp;
+ 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, mode, url;
+ bool flipbits;
+ max = top["max_threshold"].toString("110");
+ min = top["min_threshold"].toString("10");
+ alert = top["alert_threshold"].toString("30");
+ flipbits = top["flip_bits"].toBool(true);
+ mode = top["mode"].toString("http");
+ url = top["url"].toString("https://unrealdragon.in/server/waterlevel");
+ setMax(max);
+ setMin(min);
+ setAlert(alert);
+ setFlipBits(flipbits);
+ setMode(mode);
+ setUrl(url);
+ }
+ else {
+ qDebug() << "File does not exist creating one...";
+ QJsonObject top;
+ top["max_threshold"] = "110";
+ top["min_threshold"] = "10";
+ top["alert_threshold"] = "30";
+ top["flip_bits"] = true;
+ top["mode"] = "http";
+ top["url"] = "https://unrealdragon.in/server/waterlevel";
+ QByteArray data = QJsonDocument(top).toJson();
+ bool ok = config.open(QFile::WriteOnly);
+ if(!ok) {
+ qDebug() << config.errorString();
+ return;
+ }
+ config.write(data);
+ config.flush();
+ config.close();
+ setMax("110");
+ setMin("10");
+ setAlert("30");
+ setFlipBits(true);
+ setMode("http");
+ setUrl("https://unrealdragon.in/server/waterlevel");
+ qDebug() << "Config set initial values completed";
}
- input.flush();
- input.close();
- QByteArray arr_data(data);
- QJsonDocument doc = QJsonDocument::fromJson(arr_data);
- // The following works even the qml part is not initailzed/connected
- // do not change back to just emit config()
- QMetaObject::invokeMethod(this, &ConfigInterface::config, Qt::QueuedConnection, doc.object());
- emit fileNameChanged();
}
-void ConfigInterface::deleteConfig()
+void ConfigInterface::saveConfig(QString max, QString min, QString alert, bool flipbits, QString mode, QString url)
{
- std::filesystem::remove(std::filesystem::path(m_fileName.toStdString()));
- emit configDeleted();
+ 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;
+ top["flip_bits"] = flipbits;
+ top["mode"] = mode;
+ top["url"] = url;
+ setMax(max);
+ setMin(min);
+ setAlert(alert);
+ setMode(mode);
+ QByteArray data = QJsonDocument(top).toJson();
+ config.open(QIODevice::WriteOnly);
+ config.write(data);
+ config.flush();
+ config.close();
}
-void ConfigInterface::saveConfig(QJsonObject config)
+QString ConfigInterface::alert() const
{
- QJsonDocument doc;
- doc.setObject(config);
- std::string data = doc.toJson().toStdString();
- std::fstream output(m_fileName.toStdString(), std::fstream::out);
- if(!output.is_open()) {
+ return m_alert;
+}
+
+void ConfigInterface::setAlert(const QString &newAlert)
+{
+ if (m_alert == newAlert)
return;
- }
- output << data;
- output.flush();
- output.close();
- QMetaObject::invokeMethod(this, &ConfigInterface::config, Qt::QueuedConnection, doc.object());
+ 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();
+}
+
+bool ConfigInterface::flipBits() const
+{
+ return m_flipBits;
+}
+
+void ConfigInterface::setFlipBits(bool newFlipBits)
+{
+ if (m_flipBits == newFlipBits)
+ return;
+ m_flipBits = newFlipBits;
+ emit flipBitsChanged();
+}
+
+QString ConfigInterface::mode() const
+{
+ return m_mode;
+}
+
+void ConfigInterface::setMode(const QString &newMode)
+{
+ if (m_mode == newMode)
+ return;
+ m_mode = newMode;
+ emit modeChanged();
+}
+
+QString ConfigInterface::url() const
+{
+ return m_url;
+}
+
+void ConfigInterface::setUrl(const QString &newUrl)
+{
+ if (m_url == newUrl)
+ return;
+ m_url = newUrl;
+ emit urlChanged();
}
diff --git a/configinterface.h b/configinterface.h
index e3cb75e..840cc2c 100644
--- a/configinterface.h
+++ b/configinterface.h
@@ -3,28 +3,61 @@
#include
#include
-#include
-#include
+#include
+#include
+#include
#include
+#include
+#include
class ConfigInterface : public QObject
{
Q_OBJECT
QML_ELEMENT
- Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged FINAL)
+ 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)
+ Q_PROPERTY(QString mode READ mode WRITE setMode NOTIFY modeChanged FINAL)
+ Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged FINAL)
+ Q_PROPERTY(bool flipBits READ flipBits WRITE setFlipBits NOTIFY flipBitsChanged FINAL)
public:
explicit ConfigInterface(QObject *parent = nullptr);
- QString fileName() const;
- void setFileName(const QString &newFileName);
+ QString alert() const;
+ void setAlert(const QString &newAlert);
+
+ QString max() const;
+ void setMax(const QString &newMax);
+
+ QString min() const;
+ void setMin(const QString &newMin);
+
+ bool flipBits() const;
+ void setFlipBits(bool newFlipBits);
+
+ QString mode() const;
+ void setMode(const QString &newMode);
+
+ QString url() const;
+ void setUrl(const QString &newUrl);
+
signals:
- void config(QJsonObject config);
- void fileNameChanged();
- void configDeleted();
+ void configData(QString max, QString min, QString alert, bool flipbits, QString mode, QString url);
+ void alertChanged();
+ void maxChanged();
+ void minChanged();
+ void flipBitsChanged();
+ void modeChanged();
+ void urlChanged();
+
public slots:
- void deleteConfig();
- void saveConfig(QJsonObject config);
+ void saveConfig(QString max, QString min, QString alert, bool flipbits, QString mode, QString url);
private:
- QString m_fileName;
+ QString m_alert;
+ QString m_max;
+ QString m_min;
+ bool m_flipBits;
+ QString m_mode;
+ QString m_url;
};
#endif // CONFIGINTERFACE_H
diff --git a/echo.mp3 b/echo.mp3
deleted file mode 100644
index 4373d95..0000000
Binary files a/echo.mp3 and /dev/null differ
diff --git a/images.qrc b/images.qrc
new file mode 100644
index 0000000..2d11999
--- /dev/null
+++ b/images.qrc
@@ -0,0 +1,5 @@
+
+
+ WaterTank1.png
+
+
diff --git a/main.cpp b/main.cpp
index ecedbf4..ffff36f 100644
--- a/main.cpp
+++ b/main.cpp
@@ -3,11 +3,8 @@
int main(int argc, char *argv[])
{
- qputenv("QT_SCALE_FACTOR", "1.0");
- qputenv("QT_SCALE_FACTOR_ROUNDING_POLICY", "Round");
QGuiApplication app(argc, argv);
-
QQmlApplicationEngine engine;
QObject::connect(
&engine,
@@ -15,7 +12,7 @@ int main(int argc, char *argv[])
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
- engine.loadFromModule("WaterLevelRewrite", "Main");
+ engine.loadFromModule("waterLevelSensor", "Main");
- return QCoreApplication::exec();
+ return app.exec();
}
diff --git a/media.qrc b/media.qrc
deleted file mode 100644
index 2af454f..0000000
--- a/media.qrc
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- echo.mp3
-
-
diff --git a/moving_container.h b/moving_container.h
new file mode 100644
index 0000000..966bce8
--- /dev/null
+++ b/moving_container.h
@@ -0,0 +1,109 @@
+#ifndef MOVING_CONTAINER
+#define MOVING_CONTAINER
+
+#include
+using ld = long double;
+
+template class average {
+ struct info {
+ info *next = nullptr;
+ T data;
+ };
+ info *head = nullptr, *tail = nullptr, *curr = nullptr;
+ ld avg = 0.0;
+ unsigned int count = 0;
+ unsigned int max_size = 10;
+
+public:
+ average(int max_size = 10) {
+ this->max_size = max_size;
+ }
+
+ ~average() {
+ curr = head;
+ if(!curr)
+ return;
+ while(curr != nullptr) {
+ info *now = curr;
+ curr = curr->next;
+ delete now;
+ }
+ curr = nullptr;
+ head = nullptr;
+ tail = nullptr;
+ }
+
+ unsigned int size() {
+ return this->count;
+ }
+
+ double get_average() {
+ return this->avg;
+ }
+
+ void clear() {
+ curr = head;
+ if(!curr)
+ return;
+ while(curr != nullptr) {
+ info *now = curr;
+ curr = curr->next;
+ delete now;
+ }
+ curr = nullptr;
+ head = nullptr;
+ tail = nullptr;
+ count = 0;
+ }
+
+ void recalculate_average() {
+ curr = head;
+ this->avg = 0;
+ this->count = 0;
+ while(curr != nullptr) {
+ info *now = curr;
+ auto t = now->data;
+ curr = curr->next;
+ count++;
+ avg = ((avg) * (ld) (count - 1) / (ld )(count)) + ((ld)(t) / (ld) count);
+ }
+ }
+
+ void push_back(T t) {
+ if(head == nullptr) {
+ auto *i = new info();
+ i->next = nullptr;
+ i->data = t;
+ head = i;
+ tail = i;
+ }
+ else {
+ info *i = new info();
+ if(this->count == max_size) {
+ T old_value;
+ if(head->next) {
+ auto *temp = head;
+ head = head->next;
+ old_value = temp->data;
+ delete temp;
+ }
+ recalculate_average();
+ tail->next = i;
+ i->next = nullptr;
+ i->data = t;
+ tail = i;
+ }
+ tail->next = i;
+ i->next = nullptr;
+ i->data = t;
+ tail = i;
+ }
+ count++;
+ avg = ((avg) * (ld) (count - 1) / (ld )(count)) + ((ld)(t) / (ld) count);
+ }
+};
+
+
+
+
+#endif
diff --git a/requestworker.cpp b/requestworker.cpp
deleted file mode 100644
index f0657ef..0000000
--- a/requestworker.cpp
+++ /dev/null
@@ -1,302 +0,0 @@
-#include "requestworker.h"
-#include
-#include
-#include
-#include
-#include
-#include
-
-RequestWorker::RequestWorker(QObject *parent)
- : QObject{parent}
-{
-#ifdef Q_OS_ANDROID
- wifi = new WiFiHelper(this);
- connect(wifi, &WiFiHelper::ssidRetrieved, this, &RequestWorker::gotWiFiSSID);
- poll_wifi = new QTimer(this);
- poll_wifi->setInterval(3000);
- poll_wifi->setSingleShot(false);
- connect(poll_wifi, &QTimer::timeout, this, &RequestWorker::pollWiFi);
- wifi->requestPermissionsAndFetch();
- poll_wifi->start();
-#endif
-}
-
-void RequestWorker::setConfig(QJsonObject config)
-{
- this->url = config["url"].toString();
- this->interval = config["interval"].toInt();
- this->max_height = config["max"].toInt();
- this->min_height = config["min"].toInt();
- this->alarm_thres = config["alert"].toInt();
- qDebug() << "In cpp config values are: ";
- qDebug() << "Url: " << url;
- qDebug() << "Interval: " << interval;
- qDebug() << "Max Height: " << max_height;
- qDebug() << "Min Height: " << min_height;
- qDebug() << "Alert: " << alarm_thres;
-}
-
-void RequestWorker::start()
-{
- if(manager) {
- manager->deleteLater();
- manager = nullptr;
- }
- manager = new QNetworkAccessManager();
- if(timer) {
- timer->deleteLater();
- timer = nullptr;
- }
- timer = new QTimer(this);
- timer->setInterval(interval);
- timer->setSingleShot(true);
- connect(timer, &QTimer::timeout, this, &RequestWorker::makeRequest);
- timer->start();
- stop_timer = false;
- setStatus("Waiting to send.");
-}
-
-void RequestWorker::stop()
-{
- if(timer->isActive())
- timer->stop();
- stop_timer = true;
- qDebug() << "Stopping" << timer->isActive();
-}
-
-QString RequestWorker::getRequestResponseString(QNetworkReply *rep, QString rep_data)
-{
- if(!rep) {
- return "No Reply";
- }
- QNetworkRequest req = rep->request();
- QString errorString = rep->errorString();
- QString resp = "GET " + req.url().toString() + "\n";
- for(auto &key: req.rawHeaderList()) {
- QByteArray value = req.rawHeader(key);
- resp += key + ": " + value + "\n";
- }
- qDebug() << "Raw header list size: " << req.rawHeaderList().size();
- QString ret;
- ret = "Request:\n";
- ret += resp;
- ret += "Response:\n";
- if(rep_data == "")
- ret += errorString;
- else
- ret += rep_data;
- return ret;
-}
-
-void RequestWorker::pollWiFi()
-{
-#ifdef Q_OS_ANDROID
- if(wifi) {
- wifi->requestPermissionsAndFetch();
- }
-#endif
-}
-
-void RequestWorker::gotWiFiSSID(QString ssid)
-{
- if(ssid.length() > 0 && ssid != "Permission Denied" && ssid != "") {
- emit connectionText("WiFi connected: " + ssid);
- }
- else if(ssid.length() == 0) {
- emit connectionText("WiFi might not be connected", true);
- }
- else if(ssid != "") {
- emit connectionText("Failed to query wifi info", true);
- }
- else {
- emit connectionText("WiFi disconnected", true);
- }
-}
-
-void RequestWorker::playAlarm()
-{
- if (player_started)
- return;
-
- // Step 1: Copy the qrc resource to a local cache file so Android can read it
-#ifdef Q_OS_ANDROID
- QString qrcPath = ":/sounds/echo.mp3";
- QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/echo.mp3";
-
- if (QFile::exists(cachePath)) {
- QFile::remove(cachePath);
- }
-
- if (QFile::copy(qrcPath, cachePath)) {
- qDebug() << "Successfully copied resource to cache:" << cachePath;
- } else {
- qDebug() << "Failed to copy resource to cache path!";
- return;
- }
-
- // Step 2: Use Android JNI to initialize and play via android.media.MediaPlayer
- QJniEnvironment env;
-
- // Create new android.media.MediaPlayer()
- QJniObject mediaPlayer("android/media/MediaPlayer");
- if (!mediaPlayer.isValid()) {
- qDebug() << "Failed to create native Android MediaPlayer!";
- return;
- }
-
- // Set data source using the local cache file path
- QJniObject jPath = QJniObject::fromString(cachePath);
- mediaPlayer.callMethod("setDataSource", "(Ljava/lang/String;)V", jPath.object());
-
- // Enable looping
- mediaPlayer.callMethod("setLooping", "(Z)V", true);
-
- // Prepare and start playback
- mediaPlayer.callMethod("prepare");
- mediaPlayer.callMethod("start");
-
- // Store a global reference so it doesn't get garbage collected immediately
- m_androidMediaPlayer = new QJniObject(mediaPlayer.object());
-
- player_started = true;
- qDebug() << "Native Android alarm started successfully.";
- return;
-#endif
- if(player) {
- player->deleteLater();
- player = nullptr;
- }
- if(audioOutput) {
- audioOutput->deleteLater();
- audioOutput->deleteLater();
- }
- qDebug() << "Loading player";
- player = new QMediaPlayer(this);
- audioOutput = new QAudioOutput(this);
- player->setLoops(QMediaPlayer::Loops::Infinite);
- player->setAudioOutput(audioOutput);
- if (QFile::exists(":/sounds/echo.mp3")) {
- qDebug() << "Resource found successfully!";
- } else {
- qDebug() << "Resource not found. Check CMakeLists.txt or .pro file!";
- }
- player->setSource(QUrl("qrc:/sounds/echo.mp3"));
- audioOutput->setVolume(0.5);
- connect(this, &RequestWorker::playMedia, player, &QMediaPlayer::play);
- player_started = true;
- emit playMedia();
-}
-
-void RequestWorker::makeRequest()
-{
- if(stop_timer)
- return;
- QNetworkRequest req;
- req.setUrl(this->url);
- disconnect(manager, nullptr, nullptr, nullptr);
- connect(manager, &QNetworkAccessManager::finished, this, &RequestWorker::requestFinished);
- manager->setTransferTimeout(15000);
- manager->get(req);
- setStatus("Request sent; Waiting for Reply.");
-}
-
-void RequestWorker::requestFinished(QNetworkReply *rep)
-{
- QByteArray data = rep->readAll();
- auto now = std::chrono::high_resolution_clock::now();
- auto time_diff = std::chrono::duration_cast(now - prev_time);
- if(data.size() == 0) {
- prev_time = now;
- timer->start();
- QString request_response_string = getRequestResponseString(rep);
- emit requestFailed(request_response_string);
- emit setStatus("Invalid Response; Resending Request");
- rep->deleteLater();
- rep = nullptr;
- return;
- }
- unsigned int time_in_microseconds = time_diff.count();
- QJsonObject jdata = QJsonDocument::fromJson(data).object();
- auto data_water_level = jdata["water_level"].toInt();
- float water_level = (float) ((float) (data_water_level) / 10.0);
- float rate_of_fill = ( float )(prev_distance - water_level) /(float) time_in_microseconds;
- rate_of_fill = rate_of_fill * 1'000'000;
- int expected_to_fill = (water_level - min_height) / rate_of_fill;
- int percentage_filled = (float (max_height - water_level) / float (max_height)) * 100;
- if(enable_alarm) {
- qDebug() << "Alarm enabled";
- if(water_level <= alarm_thres) {
- playAlarm();
- }
- }
- if(reply_number == 0) {
- rate_of_fill = 0;
- expected_to_fill = 0;
- }
- reply_number = 1;
- qDebug() << "Got water level: " << water_level;
- qDebug() << "Got rate of fill: " << rate_of_fill;
- qDebug() << "Got expected to fill: " << expected_to_fill;
- qDebug() << "Percentage filled: " << percentage_filled;
- qDebug() << "Time difference: " << time_in_microseconds;
- prev_distance = water_level;
- QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
- QString request_response_string = getRequestResponseString(rep, data);
- request_response_string += "Timestamp:\n" + timestamp;
- rep->deleteLater();
- rep = nullptr;
- emit networkReply(data_water_level, rate_of_fill, expected_to_fill, request_response_string);
- if(stop_timer) {
- setStatus("Timer stopped.");
- return;
- }
- prev_time = now;
- timer->start();
- QString status = "Got Reply on: " + timestamp + "; Waiting to Send.";
- setStatus(status);
-}
-
-QString RequestWorker::status() const
-{
- return m_status;
-}
-
-void RequestWorker::setStatus(const QString &newStatus)
-{
- if (m_status == newStatus)
- return;
- m_status = newStatus;
- emit statusChanged();
-}
-
-void RequestWorker::startAlarm()
-{
- enable_alarm = true;
-}
-
-void RequestWorker::stopAlarm()
-{
- if (!player_started)
- return;
-
- enable_alarm = false;
- player_started = false;
- if(player) {
- player->stop();
- player->deleteLater();
- player = nullptr;
- }
- if(audioOutput) {
- audioOutput->deleteLater();
- audioOutput = nullptr;
- }
-#ifdef Q_OS_ANDROID
- if (m_androidMediaPlayer && m_androidMediaPlayer->isValid()) {
- m_androidMediaPlayer->callMethod("stop");
- m_androidMediaPlayer->callMethod("release");
-
- delete m_androidMediaPlayer;
- m_androidMediaPlayer = nullptr;
- }
-#endif
-}
diff --git a/requestworker.h b/requestworker.h
deleted file mode 100644
index c759e44..0000000
--- a/requestworker.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#ifndef REQUESTWORKER_H
-#define REQUESTWORKER_H
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#ifdef Q_OS_ANDROID
-#include "wifihelper.h"
-#include
-#endif
-
-class RequestWorker : public QObject
-{
- Q_OBJECT
- QML_ELEMENT
- Q_PROPERTY(QString status READ status WRITE setStatus NOTIFY statusChanged FINAL)
- QNetworkAccessManager *manager = nullptr;
- QTimer *timer = nullptr;
- QString url;
- int interval;
- float prev_distance = 0;
- int max_height = 0;
- int min_height = 0;
- bool stop_timer = false;
- int reply_number = 0;
- int alarm_thres = 0;
- bool enable_alarm = false;
- bool player_started = false;
- QMediaPlayer *player = nullptr;
- QAudioOutput *audioOutput = nullptr;
-#ifdef Q_OS_ANDROID
- WiFiHelper *wifi = nullptr;
- QTimer *poll_wifi = nullptr;
-#endif
- std::chrono::time_point prev_time;
-public:
- explicit RequestWorker(QObject *parent = nullptr);
- QString status() const;
- void setStatus(const QString &newStatus);
-
-signals:
- void requestFailed(QString request_response_string);
- void networkReply(int distance, float rate_of_fill, int expected_to_fill, QString data);
- void statusChanged();
- void playMedia();
- void connectionText(QString text, bool errored = false);
-public slots:
- void startAlarm();
- void stopAlarm();
- void setConfig(QJsonObject config);
- void start();
- void stop();
-private slots:
- QString getRequestResponseString(QNetworkReply *rep, QString rep_data = "");
- void pollWiFi();
- void gotWiFiSSID(QString ssid);
- void playAlarm();
- void makeRequest();
- void requestFinished(QNetworkReply *rep);
-private:
- QString m_status;
-#ifdef Q_OS_ANDROID
- QJniObject *m_androidMediaPlayer = nullptr;
-#endif
-};
-
-#endif // REQUESTWORKER_H
diff --git a/service.cpp b/service.cpp
new file mode 100644
index 0000000..f48e455
--- /dev/null
+++ b/service.cpp
@@ -0,0 +1,122 @@
+#include "service.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Q_DECLARE_JNI_CLASS(Intent, "android/content/Intent")
+Q_DECLARE_JNI_CLASS(ComponentName, "android/content/ComponentName")
+Q_DECLARE_JNI_CLASS(WaterLevelService, "org/qtproject/example/appwaterLevelSensor/WaterLevelService")
+
+static volatile float level = -1;
+
+static void handleWaterLevelUpdate(JNIEnv *env, jobject /*obj*/, jbyteArray data) {
+ jbyte *bytes = env->GetByteArrayElements(data, nullptr);
+ jsize len = env->GetArrayLength(data);
+
+ // Process your data here
+ qDebug() << "Received data from Java broadcast! Length:" << len;
+
+
+ level = *((float *) (bytes));
+
+ qDebug() << "Level received as: " << level;
+
+ env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
+}
+
+extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return JNI_ERR;
+ }
+
+ // Register your native method ONCE when the library loads
+ jclass javaClass = env->FindClass("org/qtproject/example/appwaterLevelSensor/WaterLevelReceiver");
+ if (javaClass) {
+ static JNINativeMethod methods[] = {
+ {"updateWaterLevel", "([B)V", (void *)handleWaterLevelUpdate}
+ };
+ env->RegisterNatives(javaClass, methods, 1);
+ }
+
+ return JNI_VERSION_1_6;
+}
+
+
+
+service::service(QObject *parent)
+ : QObject{parent}
+{
+ if(QSysInfo::productType() == "android") {
+ qDebug() << "Running android service";
+ // QJniEnvironment env;
+ // // if (env->ExceptionCheck()) env->ExceptionClear();
+ // // static bool nativesRegistered = false;
+
+ // // if(!nativesRegistered) {
+ // // // jclass javaClass = env.findClass("org/qtproject/example/appwaterLevelSensor/WaterLevelReceiver");
+ // // // if (javaClass) {
+ // // // JNINativeMethod methods[] = {{"updateWaterLevel", "([B)V", (void *)handleWaterLevelUpdate}};
+ // // // if (env->RegisterNatives(javaClass, methods, 1) < 0) {
+ // // // qDebug() << "RegisterNatives failed!";
+ // // // } else {
+ // // // nativesRegistered = true;
+ // // // }
+ // // // } else {
+ // // // qDebug() << "Could not find WaterLevelReceiver class";
+ // // // }
+ // // // if (env->ExceptionCheck()) env->ExceptionClear();
+ // // }
+ // using namespace QtJniTypes;
+ // using namespace QNativeInterface;
+
+
+
+ // auto *androidApp = qGuiApp->nativeInterface();
+ // // Context context = androidApp->context();
+ // QJniObject context = QNativeInterface::QAndroidApplication::context();
+ // auto serviceClass = env.findClass(Traits::className());
+ // if (!serviceClass) {
+ // qDebug() << "FAILED to find WaterLevelService class!";
+ // env->ExceptionDescribe(); // VERY important
+ // env->ExceptionClear();
+ // return;
+ // }
+ // if (context.isValid()) {
+ // QJniObject::callStaticMethod<
+ // void>(
+ // "org/qtproject/example/appwaterLevelSensor/WaterLevelServiceStarter",
+ // "startService",
+ // "(Landroid/content/Context;)V",
+ // context.object());
+ // }
+ }
+}
+
+void service::moveAppToBackground()
+{
+ QJniObject activity = QNativeInterface::QAndroidApplication::context();
+ if (activity.isValid()) {
+ qDebug() << "Moved app to background";
+ activity.callMethod("moveTaskToBack", "(Z)Z", true);
+ }
+ qDebug() << "Closing";
+}
+
+void service::begin() {
+
+
+ // Only call this once the UI is visible
+ QJniObject context = QNativeInterface::QAndroidApplication::context();
+ QJniObject::callStaticMethod(
+ "org/qtproject/example/appwaterLevelSensor/WaterLevelServiceStarter",
+ "startService",
+ "(Landroid/content/Context;)V",
+ context.object()
+ );
+}
+
diff --git a/service.h b/service.h
new file mode 100644
index 0000000..1ba18a1
--- /dev/null
+++ b/service.h
@@ -0,0 +1,17 @@
+#ifndef SERVICE_H
+#define SERVICE_H
+
+#include
+
+class service : public QObject
+{
+ Q_OBJECT
+public:
+ explicit service(QObject *parent = nullptr);
+ void moveAppToBackground();
+ void begin();
+
+signals:
+};
+
+#endif // SERVICE_H
diff --git a/servicemain.cpp b/servicemain.cpp
new file mode 100644
index 0000000..e0a684e
--- /dev/null
+++ b/servicemain.cpp
@@ -0,0 +1,12 @@
+#include
+#include
+#include "service.h"
+
+int main(int argc, char *argv[])
+{
+ qWarning() << "Service starting from a separate .so file";
+ QAndroidService app(argc, argv);
+ service service;
+
+ return app.exec();
+}
\ No newline at end of file
diff --git a/udplistener.cpp b/udplistener.cpp
new file mode 100644
index 0000000..155df4e
--- /dev/null
+++ b/udplistener.cpp
@@ -0,0 +1,475 @@
+#include "udplistener.h"
+#include
+
+
+UDPListener::UDPListener(QObject *parent)
+ : QObject{parent}
+{
+ worker = new Worker();
+ thread = new QThread();
+ worker->moveToThread(thread);
+ connect(worker, &Worker::lastUpdateddatagramReceived, this, &UDPListener::lastUpdateddatagramReceived);
+ connect(worker, &Worker::datagramReceived, this, &UDPListener::datagramRecieved);
+ connect(worker, &Worker::socketError, this, &UDPListener::socketError);
+ connect(worker, &Worker::sendConfig, this, &UDPListener::sendConfigOnSet);
+ connect(this, &UDPListener::startWorker, worker, &Worker::run);
+ connect(this, &UDPListener::changeWorkerMode, worker, &Worker::changeMode);
+ thread->start();
+ emit startWorker();
+ timer = new QTimer(this);
+ timer->setInterval(10000);
+ timer->setSingleShot(false);
+ connect(timer, &QTimer::timeout, this, &UDPListener::connectionTimerTimedOut);
+ connect(qApp, &QCoreApplication::aboutToQuit, this, &UDPListener::destroy);
+}
+
+double UDPListener::distance() const
+{
+ return m_distance;
+}
+
+void UDPListener::setDistance(double newDistance)
+{
+ m_distance = newDistance;
+ emit distanceChanged();
+}
+
+void UDPListener::sendConfigOnSet()
+{
+ bool flip_bits = m_flip_bits;
+ QString url = m_url;
+ QString mode = m_mode;
+ float distance = m_distance;
+ if(!command_socket) {
+ command_socket = new QUdpSocket(this);
+ bool bind = command_socket->bind(QHostAddress::Any, 0);
+ if(bind) {
+ QNetworkDatagram data1, data2, data3, data4;
+ data1.setDestination(QHostAddress::LocalHost, 8178);
+ data2.setDestination(QHostAddress::LocalHost, 8178);
+ data3.setDestination(QHostAddress::LocalHost, 8178);
+ data4.setDestination(QHostAddress::LocalHost, 8178);
+
+ QString s_data = "";
+ while(s_data.toUtf8().size() != 1024) {
+ s_data += "=";
+ }
+ data1.setData(s_data.toUtf8());
+ command_socket->writeDatagram(data1);
+ command_socket->flush();
+
+ QString b_data = "";
+ while(b_data.toUtf8().size() != 1024) {
+ b_data += "=";
+ }
+ data2.setData(b_data.toUtf8());
+ command_socket->writeDatagram(data2);
+ command_socket->flush();
+
+ QString m_data = "";
+ while(m_data.toUtf8().size() != 1024) {
+ m_data += "=";
+ }
+ data3.setData(m_data.toUtf8());
+ command_socket->writeDatagram(data3);
+ command_socket->flush();
+
+ QString u_data = "";
+ while(u_data.toUtf8().size() != 1024) {
+ u_data += "=";
+ }
+ data4.setData(u_data.toUtf8());
+ command_socket->writeDatagram(data4);
+ command_socket->flush();
+
+ command_socket->close();
+ command_socket->deleteLater();
+ command_socket = nullptr;
+
+ qDebug() << "Sent all Command Statements.";
+ emit changeWorkerMode(mode, url);
+ }
+ else {
+ showError("Command Socket Bind Failed");
+ }
+ }
+}
+
+void UDPListener::moveAppToBackground()
+{
+#ifdef Q_OS_ANDROID
+ service->moveAppToBackground();
+#endif
+}
+
+void UDPListener::lastUpdateddatagramReceived(QByteArray data)
+{
+ QString s_data = QString::fromUtf8(data);
+ qDebug() << "Main Class last updated on: " << s_data;
+ QDateTime time = QDateTime::fromString(s_data, "dd/MM/yy hh:mm:ss").addYears(100);
+ QDateTime thres_time = QDateTime::currentDateTime().addSecs(-1800);
+ if(time.isValid() && thres_time < time)
+ {
+ emit lastUpdatedChanged(s_data, false);
+ }
+ else if(thres_time > time && time.isValid()) {
+ emit lastUpdatedChanged(s_data, true);
+ }
+ else if(!time.isValid()) {
+ emit lastUpdatedChanged("Invalid Date", true);
+ }
+}
+
+void UDPListener::continueDestruction()
+{
+ destroyed = true;
+}
+
+void UDPListener::resetAverages()
+{
+ averages.clear();
+}
+
+void UDPListener::setFlipBits(bool flip_bits)
+{
+ m_flip_bits = flip_bits;
+}
+
+void UDPListener::setConfig(float distance, bool flip_bits, QString mode, QString url)
+{
+ setFlipBits(flip_bits);
+ setDistance(distance);
+ setMode(mode);
+ setUrl(url);
+ emit changeWorkerMode(mode, url);
+ qDebug() << "Setting config from UI completed." << flip_bits << distance << mode << url;
+ averages.clear();
+}
+
+void UDPListener::socketError(QString err)
+{
+ qDebug() << "Socket error received: " << err;
+ if(err == "The bound address is already in use") {
+ err = "Port already in use.";
+ }
+ emit showError(err);
+}
+
+void UDPListener::startService()
+{
+#ifdef Q_OS_ANDROID
+ qDebug() << "Running on Android";
+ service = new class service();
+ qDebug() << "Ran service";
+ service->begin();
+#endif
+
+}
+
+void UDPListener::datagramRecieved(QByteArray data1, QByteArray data2)
+{
+ std::string correct_order = "";
+#ifdef Q_OS_ANDROID
+ if(!m_flip_bits)
+ correct_order = "0x" + data2.toHex().toStdString();
+ else
+ correct_order = "0x" + data1.toHex().toStdString();
+#else
+ if(!m_flip_bits)
+ correct_order = "0x" + data1.toHex().toStdString();
+ else
+ correct_order = "0x" + data2.toHex().toStdString();
+#endif
+ float data = convertToFloat(correct_order);
+ qDebug() << "Data received in main class: " << data;
+ averages.push_back(data);
+ prev_time_point = std::chrono::high_resolution_clock::now();
+ emit distanceReceived(data, QDateTime::currentDateTime().toString("dd/mm/yyyy hh:mm:ss.z ap"));
+ if(averages.size() >= 5) {
+ this->m_distance = averages.get_average();
+ emit distanceChanged();
+ }
+ if(averages.size() == 1)
+ emit connected();
+
+}
+
+void UDPListener::destroy()
+{
+ qDebug() << "Destroying UDP Listener";
+
+#ifdef Q_OS_ANDROID
+ delete service;
+#endif
+ disconnect(timer, nullptr, nullptr, nullptr);
+ timer->stop();
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 2000);
+ connect(this, &UDPListener::stopWorker, worker, &Worker::stop);
+ connect(worker, &Worker::stopped, this, &UDPListener::continueDestruction);
+ emit stopWorker();
+ while(!destroyed) {
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 2000);
+ }
+ thread->exit(0);
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 2000);
+ worker->deleteLater();
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 2000);
+ while(thread->isRunning()) {
+ qDebug() << "The thread is still running.";
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 2000);
+ }
+ thread->deleteLater();
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 2000);
+ timer->deleteLater();
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 2000);
+ qDebug() << "Deinitialized UDP Listener";
+}
+
+void UDPListener::connectionTimerTimedOut()
+{
+ using namespace std::chrono_literals;
+ auto current_time_point = std::chrono::high_resolution_clock::now();
+ if((current_time_point - prev_time_point) > 10s) {
+ qDebug() << "Disconnected::" << current_time_point.time_since_epoch() << " " << prev_time_point.time_since_epoch();
+ }
+ else {
+ qDebug() << "All fine";
+ }
+}
+
+float UDPListener::convertToFloat(std::string hex)
+{
+ uint32_t num;
+ sscanf(hex.c_str(), "%x", &num);
+ float dist = *((float*)&num);
+ return dist;
+}
+
+Worker::Worker(QObject *parent)
+{
+
+}
+
+void Worker::gotHttpReply(QNetworkReply *rep)
+{
+ QByteArray m_data = rep->readAll();
+ rep->deleteLater();
+ qDebug() << "Got reply from http server";
+ QJsonObject top = QJsonDocument::fromJson(m_data).object();
+ float distance = (float ) top["water_level"].toArray()[0].toDouble();
+ QByteArray data, data2;
+ qDebug() << "Updated on: " << top["updated_on"].toString();
+ data = QByteArray::fromRawData((char *)&distance, sizeof(float));
+ char *buff = new char[4];
+ char *reader = (char *) &distance;
+ for(int i=0;i< sizeof(float) ;i++) {
+ qDebug() << i << "vs" << sizeof(float) - 1 - i;
+ buff[i] = reader[sizeof(float) - 1 - i];
+ }
+ delete[] buff;
+ data2 = QByteArray::fromRawData(buff, sizeof(float));
+ emit datagramReceived(data, data2);
+ emit lastUpdateddatagramReceived(top["updated_on"].toString().toUtf8());
+}
+
+void Worker::httpTimerTimeout()
+{
+ qDebug() << "Worker httpTimer Timeout";
+ QNetworkRequest req;
+ req.setUrl(QUrl("https://unrealdragon.in/server/waterlevel"));
+ disconnect(manager, nullptr, nullptr, nullptr);
+ connect(manager, &QNetworkAccessManager::finished, this, &Worker::gotHttpReply);
+ manager->get(req);
+}
+
+void Worker::stop()
+{
+ if(socket_2) {
+ disconnect(socket_2, nullptr, nullptr, nullptr);
+ socket_2->close();
+ delete socket_2;
+ }
+ if(socket) {
+ disconnect(socket, nullptr, nullptr, nullptr);
+ socket->close();
+ delete socket;
+ }
+ if(socket_3) {
+ disconnect(socket_3, nullptr, nullptr, nullptr);
+ socket_3->close();
+ delete socket_3;
+ }
+ if(httpTimer) {
+ disconnect(httpTimer, nullptr, nullptr, nullptr);
+ delete httpTimer;
+ }
+ if(manager) {
+ disconnect(manager, nullptr, nullptr, nullptr);
+ delete manager;
+ }
+ emit stopped();
+}
+
+void Worker::run()
+{
+ using namespace std::chrono_literals;
+ socket = new QUdpSocket(this);
+ socket_2 = new QUdpSocket(this);
+ socket_3 = new QUdpSocket(this);
+ httpTimer = new QTimer(this);
+ manager = new QNetworkAccessManager(this);
+#ifdef Q_OS_ANDROID
+ // If it is android use it as a command socket;
+ bool bind = socket->bind(QHostAddress::LocalHost, 8180, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress);
+ bool bind2 = socket_2->bind(QHostAddress::LocalHost, 8185, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress);
+ bool bind3 = socket_3->bind(QHostAddress::LocalHost, 8186, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress);
+#else
+ // Else use it as a receiver;
+ bool bind = socket->bind(QHostAddress("192.168.1.255"), 8176);
+ bool bind2 = true;
+#endif
+ if(!bind || !bind2 || !bind3)
+ {
+ qDebug() << "UI Worker socket bind failed" << bind << socket->errorString();
+ qDebug() << "UI Worker socket bind failed" << bind2 << socket_2->errorString();
+ qDebug() << "UI Worker socket bind failed" << bind3 << socket_3->errorString();
+ emit socketError(socket->errorString());
+ emit socketError(socket_2->errorString());
+ emit socketError(socket_3->errorString());
+ qApp->exit(-1);
+ }
+ connect(httpTimer, &QTimer::timeout, this, &Worker::httpTimerTimeout);
+ connect(socket, &QUdpSocket::readyRead, this, &Worker::socketDataReceived);
+ connect(socket_2, &QUdpSocket::readyRead, this, &Worker::socket2DataReceived);
+ connect(socket_3, &QUdpSocket::readyRead, this, &Worker::socket3DataReceived);
+#ifdef Q_OS_ANDROID
+ qDebug() << "Waiting for message from service.";
+#endif
+
+}
+
+void Worker::changeMode(QString mode, QString url)
+{
+ qDebug() << "Worker changing mode: " << mode << url;
+ this->mode = mode;
+ this->url = url;
+ if(mode == "http" && QSysInfo::productType() != "android") {
+ httpTimer->setInterval(2000);
+ httpTimer->setSingleShot(false);
+ httpTimer->start();
+ }
+ else if(mode == "udp") {
+ httpTimer->stop();
+ socketDataReceived();
+ }
+ if(socket) {
+ if(socket->hasPendingDatagrams()) {
+ socketDataReceived();
+ }
+ }
+ if(socket_2) {
+ if(socket_2->hasPendingDatagrams()) {
+ socket2DataReceived();
+ }
+ }
+ if(socket_3) {
+ if(socket_3->hasPendingDatagrams()) {
+ socket3DataReceived();
+ }
+ }
+
+}
+
+void Worker::socket3DataReceived()
+{
+ if(socket_3->hasPendingDatagrams()) {
+ while(this->mode == "" && this->url == "") {
+ QCoreApplication::processEvents();
+ // qDebug() << "Worker is waiting for mode to be set.";
+ }
+ size_t data_size = socket_3->pendingDatagramSize();
+ qDebug() << "Socket 3 has pending data: " << data_size;
+ if(data_size < 100000) {
+ char buff[data_size];
+ socket_3->readDatagram(buff, data_size);
+ QByteArray data = QByteArray::fromRawData(buff, data_size);
+ if(QString::fromUtf8(data) == "") {
+ qDebug() << "Data Received from Service Threads.";
+ emit sendConfig();
+ }
+ }
+ }
+}
+
+void Worker::socket2DataReceived()
+{
+ if(socket_2->hasPendingDatagrams()) {
+ if(mode == "http") {
+ size_t data_size = socket_2->pendingDatagramSize();
+ qDebug() << "Socket 2 has pending data: " << data_size;
+ char buff[data_size];
+ socket_2->readDatagram(buff, data_size);
+ QByteArray data = QByteArray::fromRawData(buff, data_size);
+ emit lastUpdateddatagramReceived(data);
+ }
+ else if(mode == "udp") {
+ qDebug() << "Last Updated on Socket data received in udp mode ignoring it.";
+ }
+ else {
+ qDebug() << "Mode is not set in socket 2";
+ }
+ }
+
+}
+
+void Worker::socketDataReceived()
+{
+ if(socket->hasPendingDatagrams()) {
+ if(mode == "udp" || (mode == "http" && QSysInfo::productType() == "android")) {
+ qDebug() << "Socket has pending data: " << socket->pendingDatagramSize();
+ char buff[4] = {0x00, 0x00, 0x00, 0x00};
+ socket->readDatagram(buff, 4);
+ auto data= QByteArray(buff,4);
+ char ch[4];
+ for(int i=0;i<4;i++) {
+ ch[i] = buff[3-i];
+ }
+ auto data2 = QByteArray(ch,4);
+ std::string correct_order = "";
+ qDebug() << "Distance received: " << correct_order;
+ emit datagramReceived(data, data2);
+ }
+ else if(mode == "http") {
+ qDebug() << "Socket data received in http mode ignoring it.";
+ }
+ else {
+ qDebug() << "Mode is not set in socket.";
+ }
+ }
+}
+
+QString UDPListener::mode() const
+{
+ return m_mode;
+}
+
+void UDPListener::setMode(const QString &newMode)
+{
+ if (m_mode == newMode)
+ return;
+ m_mode = newMode;
+ emit modeChanged();
+}
+
+QString UDPListener::url() const
+{
+ return m_url;
+}
+
+void UDPListener::setUrl(const QString &newUrl)
+{
+ if (m_url == newUrl)
+ return;
+ m_url = newUrl;
+ emit urlChanged();
+}
diff --git a/udplistener.h b/udplistener.h
new file mode 100644
index 0000000..079dd35
--- /dev/null
+++ b/udplistener.h
@@ -0,0 +1,116 @@
+#ifndef UDPLISTENER_H
+#define UDPLISTENER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "moving_container.h"
+
+#ifdef Q_OS_ANDROID
+#include "service.h"
+#endif
+
+
+class Worker: public QObject {
+ Q_OBJECT
+ QUdpSocket *socket = nullptr;
+ QUdpSocket *socket_2 = nullptr;
+ QUdpSocket *socket_3 = nullptr;
+ QString mode = "";
+ QString url = "";
+ QNetworkAccessManager *manager = nullptr;
+ QTimer *httpTimer = nullptr;
+public:
+ explicit Worker(QObject *parent = nullptr);
+signals:
+ void sendConfig();
+ void stopped();
+ void socketError(QString error);
+ void lastUpdateddatagramReceived(QByteArray data);
+ void datagramReceived(QByteArray data, QByteArray data2);
+public slots:
+ void gotHttpReply(QNetworkReply *rep);
+ void httpTimerTimeout();
+ void stop();
+ void run();
+ void changeMode(QString mode, QString url);
+private slots:
+ void socket3DataReceived();
+ void socket2DataReceived();
+ void socketDataReceived();
+};
+
+class UDPListener : public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+ Q_PROPERTY(double distance READ distance WRITE setDistance NOTIFY distanceChanged FINAL)
+ Q_PROPERTY(QString mode READ mode WRITE setMode NOTIFY modeChanged FINAL)
+ Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged FINAL)
+ bool destroyed = false;
+#ifdef Q_OS_ANDROID
+ service *service = nullptr;
+#endif
+public:
+ explicit UDPListener(QObject *parent = nullptr);
+ double distance() const;
+ void setDistance(double newDistance);
+ QString mode() const;
+ void setMode(const QString &newMode);
+
+ QString url() const;
+ void setUrl(const QString &newUrl);
+
+signals:
+ void lastUpdatedChanged(QString last_updated, bool warn);
+ void stopWorker();
+ void showError(QString err);
+ void distanceChanged();
+ void startWorker();
+ void connected();
+ void disconnected();
+ void distanceReceived(double dist, QString timestamp);
+ void changeWorkerMode(QString mode, QString url);
+ void modeChanged();
+ void initLoadFinished();
+ void urlChanged();
+
+public slots:
+ void sendConfigOnSet();
+ void moveAppToBackground();
+ void lastUpdateddatagramReceived(QByteArray data);
+ void continueDestruction();
+ void resetAverages();
+ void setFlipBits(bool flip_bits);
+ void setConfig(float distance, bool flip_bits, QString mode, QString url);
+ void socketError(QString err);
+ void startService();
+ void datagramRecieved(QByteArray data, QByteArray data2);
+private slots:
+ void destroy();
+ void connectionTimerTimedOut();
+ float convertToFloat(std::string hex);
+private:
+ QUdpSocket *command_socket = nullptr;
+ std::chrono::time_point prev_time_point;
+ average averages;
+ double m_distance;
+ Worker *worker = nullptr;
+ QThread *thread = nullptr;
+ QTimer *timer = nullptr;
+ bool m_flip_bits;
+ QString m_mode;
+ QString m_url;
+};
+
+#endif // UDPLISTENER_H
diff --git a/wifihelper.cpp b/wifihelper.cpp
deleted file mode 100644
index 835cea7..0000000
--- a/wifihelper.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-#include "wifihelper.h"
-#include
-#include
-#ifdef Q_OS_ANDROID
-#include
-#endif
-#include
-
-WiFiHelper::WiFiHelper(QObject *parent)
- : QObject{parent}
-{
-
-}
-
-QString WiFiHelper::getWifiSsid() {
-#ifdef Q_OS_ANDROID
- // Get Android application context in Qt 6
- QJniObject context = QNativeInterface::QAndroidApplication::context();
- if (!context.isValid()) return QString();
-
- // Get WifiManager service
- QJniObject wifiService = QJniObject::fromString("wifi");
- QJniObject wifiManager = context.callObjectMethod(
- "getSystemService",
- "(Ljava/lang/String;)Ljava/lang/Object;",
- wifiService.object()
- );
- if (!wifiManager.isValid()) return QString();
-
- // Fetch WifiInfo
- QJniObject wifiInfo = wifiManager.callObjectMethod(
- "getConnectionInfo",
- "()Landroid/net/wifi/WifiInfo;"
- );
- if (!wifiInfo.isValid()) return QString();
-
- // Get SSID string
- QJniObject ssidJni = wifiInfo.callObjectMethod(
- "getSSID",
- "()Ljava/lang/String;"
- );
- if (!ssidJni.isValid()) return QString();
-
- QString ssid = ssidJni.toString();
-
- // Android returns SSIDs wrapped in double quotes (e.g., "\"MyWiFi\"")
- if (ssid.startsWith('"') && ssid.endsWith('"') && ssid.length() > 1) {
- ssid = ssid.mid(1, ssid.length() - 2);
- }
-
- return ssid;
-#endif
- return "";
-}
-
-void WiFiHelper::requestPermissionsAndFetch() {
-#ifdef Q_OS_ANDROID
- QLocationPermission locationPermission;
- locationPermission.setAccuracy(QLocationPermission::Accuracy::Precise);
- Qt::PermissionStatus status = qApp->checkPermission(locationPermission);
-
- if (status == Qt::PermissionStatus::Granted) {
- emit ssidRetrieved(getWifiSsid());
- } else {
- qDebug() << "Requesting permission.";
- qApp->requestPermission(locationPermission, this, [this](const QPermission &permission) {
- if (permission.status() == Qt::PermissionStatus::Granted) {
- qDebug() << "Requested permission and got ssid";
- emit ssidRetrieved(getWifiSsid());
- } else {
- qDebug() << "Failed to get WiFi SSID due to denied permission.";
- emit ssidRetrieved("Permission Denied");
- }
- });
- }
-#endif
-}
diff --git a/wifihelper.h b/wifihelper.h
deleted file mode 100644
index ab10632..0000000
--- a/wifihelper.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef WIFIHELPER_H
-#define WIFIHELPER_H
-
-#include
-
-class WiFiHelper : public QObject
-{
- Q_OBJECT
-public:
- explicit WiFiHelper(QObject *parent = nullptr);
- Q_INVOKABLE QString getWifiSsid();
- Q_INVOKABLE void requestPermissionsAndFetch();
-
-signals:
- void ssidRetrieved(const QString &ssid);
-};
-#endif // WIFIHELPER_H