diff --git a/.gitignore b/.gitignore index aa3808c..c9e89eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,82 +1,2 @@ -# 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*/ +build/* +.qtcreator/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b80b0d..957c9c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,111 +1,69 @@ cmake_minimum_required(VERSION 3.16) -project(waterLevelSensor VERSION 0.1 LANGUAGES CXX) +project(WaterLevelRewrite VERSION 0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTORCC ON) -find_package(Qt6 REQUIRED COMPONENTS Quick Network CorePrivate) +find_package(Qt6 REQUIRED COMPONENTS Gui Core Quick Multimedia) -qt_standard_project_setup(REQUIRES 6.11) - -qt_add_executable(appwaterLevelSensor - main.cpp - images.qrc -) - - -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 - 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 -) - -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}) +qt_standard_project_setup(REQUIRES 6.10) +if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/importedcontent/CMakeLists.txt") + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/importedcontent) endif() -if(NOT ANDROID) -qt_add_qml_module(appwaterLevelSensor - URI waterLevelSensor + +qt_add_executable(appWaterLevelRewrite + main.cpp +) + +qt_add_qml_module(appWaterLevelRewrite + URI WaterLevelRewrite QML_FILES Main.qml - RESOURCES - RESOURCES images.qrc - SOURCES udplistener.h udplistener.cpp - SOURCES moving_container.h - SOURCES configinterface.h configinterface.cpp + 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 ) -# 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 +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 + ) +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" ) -target_link_libraries(appwaterLevelSensor - PRIVATE Qt6::Quick Qt6::Network +target_link_libraries(appWaterLevelRewrite + PRIVATE Qt6::Quick Qt6::Multimedia Qt6::Gui Qt6::Core ) -qt_finalize_executable(appwaterLevelSensor) - -include(GNUInstallDirs) -install(TARGETS appwaterLevelSensor +install(TARGETS appWaterLevelRewrite BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -endif() diff --git a/ColorInput.qml b/ColorInput.qml new file mode 100644 index 0000000..a4cebe7 --- /dev/null +++ b/ColorInput.qml @@ -0,0 +1,21 @@ +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 new file mode 100644 index 0000000..dd058bc --- /dev/null +++ b/ConnectButton.qml @@ -0,0 +1,63 @@ +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 new file mode 100644 index 0000000..0cdbb38 --- /dev/null +++ b/CustomCheckBox.qml @@ -0,0 +1,17 @@ +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 e7844b9..28693c5 100644 --- a/Main.qml +++ b/Main.qml @@ -1,71 +1,59 @@ import QtQuick -import waterLevelSensor +import WaterLevelRewrite import QtQuick.Effects import QtQuick.Controls import QtQuick.Controls.Material Window { - width: 640 - height: 1080 + width: 960 + height: 1400 visible: true - title: qsTr("Hello World") + title: qsTr("Water Level Rewrite") 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" - // 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 - } - + 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 } Rectangle { anchors.fill: parent - id: background - color: "#DF0F0F0F" + color: root.background_color + id: root_rect Rectangle { id: top_bar height: Math.min(root.height / 12, 100) @@ -76,729 +64,321 @@ Window { topMargin: 25 } color: "#0A0F0F0F" - - Rectangle { - id: settings_rect + SettingsBarIcon { + id: settings_icon height: top_bar.height - 20 - width: top_bar.width / 12 + width: height anchors { top: parent.top - left: parent.left + right: parent.right bottomMargin: 5 topMargin: 5 leftMargin: 5 } - Behavior on color { - ColorAnimation { - duration: 200 - } - } - color: "#0A0F0F0F" - border.color: "#AF121212" - border.width: 0.5 - radius: 2 - Text { - id: settings_text - text: "⚙" - anchors.centerIn: parent - color: root.accent_color - font.pointSize: root.font_enlarged_text_size - } - - MouseArea { - id: settings_button_area - anchors.fill: parent - hoverEnabled: true - property color settings_rect_pressed_prev: "#00000000" - property color settings_rect_entered_prev: "#00000000" - onEntered: { - settings_rect_entered_prev = settings_rect.color - settings_rect.color = "#5A1F1F1F" - } - onExited: { - settings_rect.color = settings_rect_entered_prev - } - onPressed: { - settings_rect_pressed_prev = settings_rect.color - settings_rect.color = "#5A1F4F1F" - settings_popup.open() - } - onReleased: { - settings_rect.color = settings_rect_pressed_prev - } + radius: 100 + accent_color: root.accent_color + onOpen: { + settings_popup.open() } } - - - Rectangle { - property color old_color: "#0F0F0F0F" - id: status_indicator - width: 20 - height: 20 - radius: 20 + SettingsBarIcon { + height: top_bar.height - 20 + width: height anchors { top: parent.top - right: parent.right - topMargin: 20 + left: parent.left bottomMargin: 5 + topMargin: 10 leftMargin: 5 - rightMargin: 20 } - color: "#F0AF2F1F" - layer.enabled: true - layer.effect: MultiEffect { - shadowEnabled: true + text: "⇅" + color: "#0A0F0F0F" + radius: 100 + accent_color: root.accent_color + onOpen: { + request_response_popup.open() } } - 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" + } + + 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 } + 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: base - color: "#0A0F0F0F" - width: parent.width - height: parent.height - top_bar.height + id: button_base + color: "#00000000" + width: parent.width / 1.5 + height: parent.height / 20 anchors { - top: top_bar.bottom - bottom: parent.bottom horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: 30 } - - Popup { - id: settings_popup - background: Rectangle { - color: "#F0121212" - radius: 5 - border.color: root.accent_color - border.width: 1 - } - 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 - } - - - 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 - } - } - - } - } - } - - Rectangle { - id: tank_lid - color: "#F0121212" + 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 { - 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 + left: parent.left + leftMargin: 5 + bottom: parent.bottom bottomMargin: 5 } - Behavior on color { - ColorAnimation { - duration: 200 + onConnect: { + if(connect_button.mode === 0) + { + requester.start(); + root.status = "Waiting to Send."; } - } - Behavior on height { - NumberAnimation { - duration: 200 + else { + root.status = "Stopped."; + requester.stop(); } } } - 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) + " %" + 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" anchors { - top: info_text.bottom - topMargin: 10 - horizontalCenter: parent.horizontalCenter + left: connect_button.right + leftMargin: 10 + bottom: parent.bottom + bottomMargin: 5 } - 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 + onConnect: { + if(start_alarm.mode === 0) + { + requester.startAlarm(); + root.alarm_status_text = "Started Alarm; Waiting for Trigger: " + root.root_config.alert + } + else { + requester.stopAlarm(); + root.alarm_status_text = ""; + } } - 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; + } - Timer { - id: status_indicator_timer - running: false - repeat: false - triggeredOnStart: false - interval: 300 - onTriggered: { - status_indicator.color = status_indicator.old_color; + onConnectionText: (text, error) => { + root.connection_text_string = text + if(!error) { + connection_text.color = root.accent_color + } + else { + connection_text.color = root.danger_color + } } } ConfigInterface { - id: config - onConfigData: (max, min, alert, mode, url) => { - console.log("Got config data as: ", max, min, alert, mode, url); - } - onModeChanged: { - root.mode = mode; - } - } - 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(); + 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."; } } - - } - - 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 + 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 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(); + + 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 + + } + } + onClosing: { + requester.stopAlarm(); } } diff --git a/NotificationManager.java b/NotificationManager.java deleted file mode 100644 index 5656db5..0000000 --- a/NotificationManager.java +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 7198d51..0000000 --- a/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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 new file mode 100644 index 0000000..468bcda --- /dev/null +++ b/RequestResponsePopup.qml @@ -0,0 +1,30 @@ +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 new file mode 100644 index 0000000..35072b7 --- /dev/null +++ b/SettingsBarIcon.qml @@ -0,0 +1,49 @@ +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 new file mode 100644 index 0000000..6a97c2b --- /dev/null +++ b/SettingsPopup.qml @@ -0,0 +1,594 @@ +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 new file mode 100644 index 0000000..633fac8 --- /dev/null +++ b/WaterTank.qml @@ -0,0 +1,204 @@ +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 deleted file mode 100644 index 24ef3c1..0000000 Binary files a/WaterTank1.png and /dev/null differ diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index ead6548..2598885 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,18 +1,18 @@ - - - + + + + - - - + + - - - - - - - @@ -49,9 +38,11 @@ + + android:value="appWaterLevelRewrite" /> + diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index ea6c193..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -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 deleted file mode 100644 index 7d0f630..0000000 Binary files a/android/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable-mdpi/ic_launcher.png b/android/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 9260e4e..0000000 Binary files a/android/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable-xhdpi/ic_launcher.png b/android/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index c3e5ba9..0000000 Binary files a/android/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable-xxhdpi/ic_launcher.png b/android/res/drawable-xxhdpi/ic_launcher.png deleted file mode 100644 index 1257bb6..0000000 Binary files a/android/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable-xxxhdpi/ic_launcher.png b/android/res/drawable-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4ed8a75..0000000 Binary files a/android/res/drawable-xxxhdpi/ic_launcher.png and /dev/null 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 deleted file mode 100644 index aac66dc..0000000 Binary files a/android/src/org/qtproject/example/appwaterLevelSensor/.WaterLevelService.java.kate-swp and /dev/null differ diff --git a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelReceiver.java b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelReceiver.java deleted file mode 100644 index 60b004c..0000000 --- a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelReceiver.java +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index cc765d0..0000000 --- a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java +++ /dev/null @@ -1,536 +0,0 @@ -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 deleted file mode 100644 index 9dccce9..0000000 --- a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelServiceStarter.java +++ /dev/null @@ -1,118 +0,0 @@ -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 73ae20a..8f40d45 100644 --- a/configinterface.cpp +++ b/configinterface.cpp @@ -1,168 +1,60 @@ #include "configinterface.h" +#include +#include +#include + ConfigInterface::ConfigInterface(QObject *parent) : QObject{parent} { - auto paths = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - qDebug() << "Got writable path as: " << paths; - std::filesystem::path dir = paths.toStdString(); - if(!QDir(dir).exists()) { - QDir().mkpath(paths); +} + +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; } - 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); + std::string data; + std::string temp; + while(std::getline(input, temp)) { + data += temp; } - 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() +{ + std::filesystem::remove(std::filesystem::path(m_fileName.toStdString())); + emit configDeleted(); +} + +void ConfigInterface::saveConfig(QJsonObject config) +{ + 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; } -} - -void ConfigInterface::saveConfig(QString max, QString min, QString alert, bool flipbits, QString mode, QString url) -{ - 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(); -} - -QString ConfigInterface::alert() const -{ - return m_alert; -} - -void ConfigInterface::setAlert(const QString &newAlert) -{ - if (m_alert == newAlert) - return; - m_alert = newAlert; - emit alertChanged(); -} - -QString ConfigInterface::max() const -{ - return m_max; -} - -void ConfigInterface::setMax(const QString &newMax) -{ - if (m_max == newMax) - return; - m_max = newMax; - emit maxChanged(); -} - -QString ConfigInterface::min() const -{ - return m_min; -} - -void ConfigInterface::setMin(const QString &newMin) -{ - if (m_min == newMin) - return; - m_min = newMin; - emit minChanged(); -} - -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(); + output << data; + output.flush(); + output.close(); + QMetaObject::invokeMethod(this, &ConfigInterface::config, Qt::QueuedConnection, doc.object()); } diff --git a/configinterface.h b/configinterface.h index 840cc2c..e3cb75e 100644 --- a/configinterface.h +++ b/configinterface.h @@ -3,61 +3,28 @@ #include #include -#include -#include -#include -#include -#include #include +#include +#include class ConfigInterface : public QObject { Q_OBJECT QML_ELEMENT - Q_PROPERTY(QString min READ min WRITE setMin NOTIFY minChanged FINAL) - Q_PROPERTY(QString max READ max WRITE setMax NOTIFY maxChanged FINAL) - Q_PROPERTY(QString alert READ alert WRITE setAlert NOTIFY alertChanged FINAL) - 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) + Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged FINAL) public: explicit ConfigInterface(QObject *parent = nullptr); - QString alert() const; - void setAlert(const QString &newAlert); - - QString max() const; - void setMax(const QString &newMax); - - QString min() const; - void setMin(const QString &newMin); - - bool flipBits() const; - void setFlipBits(bool newFlipBits); - - QString mode() const; - void setMode(const QString &newMode); - - QString url() const; - void setUrl(const QString &newUrl); - + QString fileName() const; + void setFileName(const QString &newFileName); signals: - 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(); - + void config(QJsonObject config); + void fileNameChanged(); + void configDeleted(); public slots: - void saveConfig(QString max, QString min, QString alert, bool flipbits, QString mode, QString url); + void deleteConfig(); + void saveConfig(QJsonObject config); private: - QString m_alert; - QString m_max; - QString m_min; - bool m_flipBits; - QString m_mode; - QString m_url; + QString m_fileName; }; #endif // CONFIGINTERFACE_H diff --git a/echo.mp3 b/echo.mp3 new file mode 100644 index 0000000..4373d95 Binary files /dev/null and b/echo.mp3 differ diff --git a/images.qrc b/images.qrc deleted file mode 100644 index 2d11999..0000000 --- a/images.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - WaterTank1.png - - diff --git a/main.cpp b/main.cpp index ffff36f..ecedbf4 100644 --- a/main.cpp +++ b/main.cpp @@ -3,8 +3,11 @@ 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, @@ -12,7 +15,7 @@ int main(int argc, char *argv[]) &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - engine.loadFromModule("waterLevelSensor", "Main"); + engine.loadFromModule("WaterLevelRewrite", "Main"); - return app.exec(); + return QCoreApplication::exec(); } diff --git a/media.qrc b/media.qrc new file mode 100644 index 0000000..2af454f --- /dev/null +++ b/media.qrc @@ -0,0 +1,5 @@ + + + echo.mp3 + + diff --git a/moving_container.h b/moving_container.h deleted file mode 100644 index 966bce8..0000000 --- a/moving_container.h +++ /dev/null @@ -1,109 +0,0 @@ -#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 new file mode 100644 index 0000000..f0657ef --- /dev/null +++ b/requestworker.cpp @@ -0,0 +1,302 @@ +#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 new file mode 100644 index 0000000..c759e44 --- /dev/null +++ b/requestworker.h @@ -0,0 +1,74 @@ +#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 deleted file mode 100644 index f48e455..0000000 --- a/service.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#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 deleted file mode 100644 index 1ba18a1..0000000 --- a/service.h +++ /dev/null @@ -1,17 +0,0 @@ -#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 deleted file mode 100644 index e0a684e..0000000 --- a/servicemain.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#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 deleted file mode 100644 index 155df4e..0000000 --- a/udplistener.cpp +++ /dev/null @@ -1,475 +0,0 @@ -#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 deleted file mode 100644 index 079dd35..0000000 --- a/udplistener.h +++ /dev/null @@ -1,116 +0,0 @@ -#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 new file mode 100644 index 0000000..835cea7 --- /dev/null +++ b/wifihelper.cpp @@ -0,0 +1,77 @@ +#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 new file mode 100644 index 0000000..ab10632 --- /dev/null +++ b/wifihelper.h @@ -0,0 +1,17 @@ +#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