diff --git a/CMakeLists.txt b/CMakeLists.txt
index 103c3b1..957c9c2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,11 +3,16 @@ cmake_minimum_required(VERSION 3.16)
project(WaterLevelRewrite VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_AUTORCC ON)
-find_package(Qt6 REQUIRED COMPONENTS Quick)
+find_package(Qt6 REQUIRED COMPONENTS Gui Core Quick Multimedia)
qt_standard_project_setup(REQUIRES 6.10)
+if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/importedcontent/CMakeLists.txt")
+ add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/importedcontent)
+endif()
+
qt_add_executable(appWaterLevelRewrite
main.cpp
)
@@ -16,27 +21,45 @@ qt_add_qml_module(appWaterLevelRewrite
URI WaterLevelRewrite
QML_FILES
Main.qml
- QML_FILES WaterTank.qml
- QML_FILES SettingsBarIcon.qml
- QML_FILES SettingsPopup.qml
- SOURCES configinterface.h configinterface.cpp
- QML_FILES ConnectButton.qml
- SOURCES requestworker.h requestworker.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 manually though.
+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
-# 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
- WIN32_EXECUTABLE TRUE
+ QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android"
)
target_link_libraries(appWaterLevelRewrite
- PRIVATE Qt6::Quick
+ PRIVATE Qt6::Quick Qt6::Multimedia Qt6::Gui Qt6::Core
)
install(TARGETS appWaterLevelRewrite
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
index 9225e44..dd058bc 100644
--- a/ConnectButton.qml
+++ b/ConnectButton.qml
@@ -10,6 +10,8 @@ Rectangle {
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 {
@@ -20,7 +22,7 @@ Rectangle {
anchors.centerIn: parent
font.pointSize: root.font_enlarged_text_size
color: root.font_color
- text: root.text
+ text: mode ? root.text2 : root.text
horizontalAlignment: Text.AlignHCenter
}
@@ -46,10 +48,16 @@ Rectangle {
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 db445e4..28693c5 100644
--- a/Main.qml
+++ b/Main.qml
@@ -5,8 +5,8 @@ import QtQuick.Controls
import QtQuick.Controls.Material
Window {
- width: 640
- height: 1080
+ width: 960
+ height: 1400
visible: true
title: qsTr("Water Level Rewrite")
id: root
@@ -14,6 +14,7 @@ Window {
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
@@ -21,12 +22,38 @@ Window {
property int distance_count_thres: 3
property string mode: "http"
property string status: "Initializing"
-
+ property string connection_text_string: "Reading WiFi Status..."
+ property string alarm_status_text: ""
+ property double water_tank_height_factor: 1.3
+ property double water_tank_width_factor: 2
+ property var root_config: ({})
+ property var base_config: {
+ "max": 0,
+ "min": 0,
+ "alert": 0,
+ "interval": 0,
+ "url": "none",
+ "water_tank_width": 2,
+ "water_tank_height": 1.3,
+ "rate_of_fill_enable": true,
+ "current_reading_enable": true,
+ "percentage_enable": true,
+ "filling_status_enable": true,
+ "expected_to_fill_enable": true,
+ "accent_color": "F05FAF5F",
+ "dark_accent_color": "FF5F7F5F",
+ "danger_color": "F0AF2F1F",
+ "water_color": "F00F7FAF",
+ "water_color_above_alert": "F00FAF0F",
+ "background_color": "1F1F1F",
+ "font_normal_text_size": 16,
+ "font_enlarged_text_size": 20
+ }
Rectangle {
anchors.fill: parent
color: root.background_color
-
+ id: root_rect
Rectangle {
id: top_bar
height: Math.min(root.height / 12, 100)
@@ -38,11 +65,12 @@ Window {
}
color: "#0A0F0F0F"
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
@@ -54,12 +82,40 @@ Window {
settings_popup.open()
}
}
+
+ SettingsBarIcon {
+ height: top_bar.height - 20
+ width: height
+ anchors {
+ top: parent.top
+ left: parent.left
+ bottomMargin: 5
+ topMargin: 10
+ leftMargin: 5
+ }
+ text: "⇅"
+ color: "#0A0F0F0F"
+ radius: 100
+ accent_color: root.accent_color
+ onOpen: {
+ request_response_popup.open()
+ }
+ }
+ }
+
+ RequestResponsePopup {
+ id: request_response_popup
+ accent_color: root.accent_color
+ font_enlarged_text_size: 20
+
}
SettingsPopup {
id: settings_popup
accent_color: root.accent_color
- font_enlarged_text_size: 20
+ 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
@@ -70,32 +126,66 @@ Window {
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: top_bar.bottom
+ top: connection_text.bottom
horizontalCenter: parent.horizontalCenter
- topMargin: 0
+ topMargin: 40
}
- height: (parent.height - top_bar.height) / 1.2
- width: parent.width / 1.2
+ 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: "#F00F7FAF"
+ 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: connect_button.top
+ bottom: button_base.top
bottomMargin: 20
horizontalCenter: parent.horizontalCenter
}
@@ -104,25 +194,77 @@ Window {
font.pointSize: root.font_normal_text_size
}
- ConnectButton {
- id: connect_button
+ Rectangle {
+ id: button_base
+ color: "#00000000"
+ width: parent.width / 1.5
height: parent.height / 20
- width: parent.width / 4
- 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 {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
- bottomMargin: 50
+ bottomMargin: 30
}
- onConnect: {
- requester.start();
- root.status = "Waiting to Send.";
+ ConnectButton {
+ id: connect_button
+ width: parent.width / 2.1
+ height: parent.height
+ font_color: root.accent_color
+ font_enlarged_text_size: root.font_enlarged_text_size
+ font_normal_text_size: root.font_normal_text_size
+ color: root.background_color
+ accent_color: root.accent_color
+ anchors {
+ left: parent.left
+ leftMargin: 5
+ bottom: parent.bottom
+ bottomMargin: 5
+ }
+ onConnect: {
+ if(connect_button.mode === 0)
+ {
+ requester.start();
+ root.status = "Waiting to Send.";
+ }
+ else {
+ root.status = "Stopped.";
+ requester.stop();
+ }
+ }
}
+
+ 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 {
+ left: connect_button.right
+ leftMargin: 10
+ bottom: parent.bottom
+ bottomMargin: 5
+ }
+ 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 = "";
+ }
+ }
+ }
+
}
+
+
}
RequestWorker {
@@ -130,10 +272,26 @@ Window {
onStatusChanged: () => {
root.status = requester.status;
}
- onNetworkReply: (water_level, fill_rate, expected) => {
+ onNetworkReply: (water_level, fill_rate, expected, data) => {
tank.setDistance(water_level);
tank.rate_of_fill = fill_rate;
tank.expected_to_fill = expected;
+ status.color = root.accent_color;
+ request_response_popup.request_response_string = data;
+ }
+ onRequestFailed: (data) => {
+ status.color = root.danger_color;
+ request_response_popup.request_response_string = data;
+ }
+
+ onConnectionText: (text, error) => {
+ root.connection_text_string = text
+ if(!error) {
+ connection_text.color = root.accent_color
+ }
+ else {
+ connection_text.color = root.danger_color
+ }
}
}
@@ -141,16 +299,86 @@ Window {
id: config_interface
fileName: "config.json"
onConfig: (config) => {
- settings_popup.config = config
+ settings_popup.config = config;
console.log("Loading initial config");
setConfig(config);
if(root.status === "Initializing") {
- root.status = "Waiting to connect.";
+ root.status = "Press Connect.";
}
}
+ onConfigDeleted: () => {
+ let config = base_config;
+ settings_popup.config = config;
+ console.log("Loading initial config");
+ setConfig(config);
+ if(root.status === "Initializing") {
+ root.status = "Press Connect.";
+ }
+ }
+
function setConfig(config) {
tank.setConfig(config);
requester.setConfig(config);
+ root.root_config = config;
+ if(config.water_tank_height === undefined) {
+ config.water_tank_height = 2
+ }
+ if(config.water_tank_width === undefined) {
+ config.water_tank_width = 2.2
+ }
+ if(config.rate_of_fill_enable === undefined) {
+ config.rate_of_fill_enable = true
+ }
+ if(config.current_reading_enable === undefined) {
+ config.current_reading_enable = true
+ }
+ if(config.percentage_enable === undefined) {
+ config.percentage_enable = true
+ }
+ if(config.filling_status_enable === undefined) {
+ config.filling_status_enable = true
+ }
+ if(config.expected_to_fill_enable === undefined) {
+ config.expected_to_fill_enable = true
+ }
+ if(config.font_enlarged_text_size === undefined) {
+ config.font_enlarged_text_size = 20
+ }
+ if(config.font_normal_text_size === undefined) {
+ config.font_normal_text_size = 16
+ }
+ if(config.accent_color === undefined) {
+ config.accent_color = "F05FAF5F"
+ }
+ if(config.danger_color === undefined) {
+ config.danger_color = "F0AF2F1F"
+ }
+ if(config.dark_accent_color === undefined) {
+ config.dark_accent_color = "FF5F7F5F"
+ }
+ if(config.water_color === undefined) {
+ config.water_color = "F00F7FAF"
+ }
+ if(config.water_color_above_alert === undefined) {
+ config.water_color_above_alert = "F00FAF0F"
+ }
+ if(config.background_color === undefined) {
+ config.background_color = "1F1F1F"
+ }
+ root.water_tank_height_factor = config.water_tank_height
+ root.water_tank_width_factor = config.water_tank_width
+ root.accent_color = "#" + config.accent_color
+ root.danger_color = "#" + config.danger_color
+ root.dark_accent_color = "#" + config.dark_accent_color
+ root.water_color = "#" + config.water_color
+ root.water_color_above_alert = "#" + config.water_color_above_alert
+ root.background_color = "#" + config.background_color
+ root.font_enlarged_text_size = config.font_enlarged_text_size
+ root.font_normal_text_size = config.font_normal_text_size
+
}
}
+ onClosing: {
+ requester.stopAlarm();
+ }
}
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
index d360331..35072b7 100644
--- a/SettingsBarIcon.qml
+++ b/SettingsBarIcon.qml
@@ -8,6 +8,7 @@ Rectangle {
signal open;
property color accent_color: "#00000000"
property int font_enlarged_text_size: 20
+ property string text: "⚙"
Behavior on color {
ColorAnimation {
duration: 200
@@ -15,8 +16,10 @@ Rectangle {
}
Text {
id: settings_text
- 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
}
diff --git a/SettingsPopup.qml b/SettingsPopup.qml
index 051c306..6a97c2b 100644
--- a/SettingsPopup.qml
+++ b/SettingsPopup.qml
@@ -6,15 +6,32 @@ 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"
+ "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 {
@@ -23,178 +40,469 @@ Popup {
border.color: settings_popup.accent_color
border.width: 1
}
- height: Math.max(parent.height / 1.2, contentHeight)
- width: parent.width / 1.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"
- Text {
- id: settings_label
- text: "Settings"
- width: parent.width
+ ScrollView {
+ id: rect_base_scroll_view
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
+ bottom: save_config.top
+ bottomMargin: 10
}
- horizontalAlignment: Text.AlignHCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_enlarged_text_size
- }
+ width: parent.width
+ clip: true
+ // Use contentWidth and contentHeight to define the scrollable area
+ contentWidth: availableWidth
+ contentHeight: settings_column.height
- Text {
- id: max_thres_label
- text: "Max Threshold: "
- anchors {
- topMargin: 20
- top: settings_label.bottom
- left: parent.left
- verticalCenter: max_thres_input.verticalCenter
+ 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();
+ }
+ }
}
- height: max_thres_input.height
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.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: settings_popup.accent_color
- font.pointSize: settings_popup.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: settings_popup.accent_color
- font.pointSize: settings_popup.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: settings_popup.accent_color
- font.pointSize: settings_popup.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: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
-
- Text {
- id: interval_label
- text: "Update Interval: "
- anchors {
- topMargin: 20
- top: alert_thres_label.bottom
- left: parent.left
- verticalCenter: interval_input.verticalCenter
- }
- height: interval_input.height
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
-
-
- TextField {
- id: interval_input
- text: config.interval
- anchors {
- right: parent.right
- rightMargin: 5
- top: alert_thres_input.bottom
- topMargin: 20
- }
- width: parent.width / 2
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
-
- Text {
- id: http_url_label
- text: "Url: "
- anchors {
- topMargin: 20
- top: interval_label.bottom
- left: parent.left
- verticalCenter: http_url_input.verticalCenter
- }
- height: http_url_input.height
- verticalAlignment: Text.AlignVCenter
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- }
-
- TextField {
- id: http_url_input
- text: config.url
- anchors {
- right: parent.right
- rightMargin: 20
- leftMargin: 20
- top: interval_input.bottom
- topMargin: 20
- }
- width: Math.max(parent.width / 2, parent.width - http_url_label.width - anchors.leftMargin)
- color: settings_popup.accent_color
- font.pointSize: settings_popup.font_normal_text_size
- cursorPosition: 0
- }
-
Rectangle {
id: save_config
anchors {
@@ -237,7 +545,43 @@ Popup {
let alert = parseFloat(alert_thres_input.text);
let url = http_url_input.text;
let interval = parseInt(interval_input.text);
- var new_config = {"max": max, "min": min, "alert": alert, "url": url, "interval": interval}
+ 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();
}
@@ -245,7 +589,6 @@ Popup {
save_config.color = settings_rect_pressed_prev
}
}
-
}
}
}
\ No newline at end of file
diff --git a/WaterTank.qml b/WaterTank.qml
index 5c1b5af..633fac8 100644
--- a/WaterTank.qml
+++ b/WaterTank.qml
@@ -9,16 +9,25 @@ Item {
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 double distance: 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
@@ -28,21 +37,24 @@ Item {
id: tank_lid
color: root.tank_lid_color
anchors {
- bottom: tank_base.top
+ top: parent.top
horizontalCenter: tank_base.horizontalCenter
bottomMargin: -5
}
radius: 10
- width: parent.width / 4
- height: parent.height / 15
+ width: tank_base.width / 3
+ height: tank_base.height / 15
}
Rectangle {
id: tank_base
color: root.tank_base_color
- anchors.centerIn: parent
- width: parent.width / 2
- height: parent.height / 2
+ 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
@@ -70,81 +82,102 @@ Item {
}
}
- Text {
- id: water_level_reading
- text: "Current Reading: " + root.distance + " cm"
- font.pointSize: 16
- color: root.text_color
+ ScrollView {
+ id: rect_base_scroll_view
anchors {
margins: 5
top: tank_base.bottom
+ bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
- horizontalAlignment: Text.AlignHCenter
+ 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
+ }
+ }
}
- Text {
- id: water_level_percentage
- text: "Percentage: " + root.percentage_filled + " %"
- font.pointSize: 16
- color: root.text_color
- anchors {
- margins: 5
- top: water_level_reading.bottom
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- }
-
- Text {
- id: fill_status
- text: "Filling status: " + (( root.rate_of_fill > 0)? "Filling" : "Discharging")
- font.pointSize: 16
- color: root.text_color
- anchors {
- margins: 5
- top: water_level_percentage.bottom
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- }
- Text {
- id: rate_of_fill_label
- text: "Rate of fill: " + root.rate_of_fill + " cm/s"
- font.pointSize: 16
- color: root.text_color
- anchors {
- margins: 5
- top: fill_status.bottom
- horizontalCenter: parent.horizontalCenter
- }
- horizontalAlignment: Text.AlignHCenter
- }
- Text {
- id: expected_to_fill_label
- text: "Expected to fill in: " + root.expected_to_fill + " s"
- font.pointSize: 16
- color: root.text_color
- visible: (root.rate_of_fill > 1) ? true : false
- anchors {
- margins: 5
- top: rate_of_fill_label.bottom
- 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;
- console.log("Distance changed to: ", 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, root.distance - 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)
@@ -155,8 +188,17 @@ Item {
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/android/AndroidManifest.xml b/android/AndroidManifest.xml
new file mode 100644
index 0000000..2598885
--- /dev/null
+++ b/android/AndroidManifest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/configinterface.cpp b/configinterface.cpp
index 258560f..8f40d45 100644
--- a/configinterface.cpp
+++ b/configinterface.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
ConfigInterface::ConfigInterface(QObject *parent)
: QObject{parent}
@@ -37,6 +38,12 @@ void ConfigInterface::setFileName(const QString &newFileName)
emit fileNameChanged();
}
+void ConfigInterface::deleteConfig()
+{
+ std::filesystem::remove(std::filesystem::path(m_fileName.toStdString()));
+ emit configDeleted();
+}
+
void ConfigInterface::saveConfig(QJsonObject config)
{
QJsonDocument doc;
diff --git a/configinterface.h b/configinterface.h
index 606b496..e3cb75e 100644
--- a/configinterface.h
+++ b/configinterface.h
@@ -19,7 +19,9 @@ public:
signals:
void config(QJsonObject config);
void fileNameChanged();
+ void configDeleted();
public slots:
+ void deleteConfig();
void saveConfig(QJsonObject config);
private:
QString m_fileName;
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/main.cpp b/main.cpp
index c438cd2..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,
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/requestworker.cpp b/requestworker.cpp
index eff8079..f0657ef 100644
--- a/requestworker.cpp
+++ b/requestworker.cpp
@@ -3,11 +3,22 @@
#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)
@@ -16,11 +27,13 @@ void RequestWorker::setConfig(QJsonObject config)
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()
@@ -48,6 +61,130 @@ 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()
@@ -66,17 +203,32 @@ void RequestWorker::makeRequest()
void RequestWorker::requestFinished(QNetworkReply *rep)
{
QByteArray data = rep->readAll();
- rep->deleteLater();
- rep = nullptr;
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 )(water_level - prev_distance) /(float) time_in_microseconds;
+ 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 = (max_height - water_level) / rate_of_fill;
+ 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;
@@ -85,16 +237,22 @@ void RequestWorker::requestFinished(QNetworkReply *rep)
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;
- emit networkReply(water_level, rate_of_fill, expected_to_fill);
+ 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: " + QDateTime::currentDateTime().toString("hh:mm:ss") + "; Waiting to Send.";
+ QString status = "Got Reply on: " + timestamp + "; Waiting to Send.";
setStatus(status);
}
@@ -110,3 +268,35 @@ void RequestWorker::setStatus(const QString &newStatus)
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
index 05fbafa..c759e44 100644
--- a/requestworker.h
+++ b/requestworker.h
@@ -6,9 +6,15 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
+#ifdef Q_OS_ANDROID
+#include "wifihelper.h"
+#include
+#endif
class RequestWorker : public QObject
{
@@ -24,26 +30,45 @@ class RequestWorker : public QObject
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 networkReply(float distance, float rate_of_fill, int expected_to_fill);
+ 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/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