Compare commits

..
18 changed files with 1335 additions and 262 deletions

View file

@ -3,11 +3,16 @@ cmake_minimum_required(VERSION 3.16)
project(WaterLevelRewrite VERSION 0.1 LANGUAGES CXX) project(WaterLevelRewrite VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON) 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) 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 qt_add_executable(appWaterLevelRewrite
main.cpp main.cpp
) )
@ -16,27 +21,45 @@ qt_add_qml_module(appWaterLevelRewrite
URI WaterLevelRewrite URI WaterLevelRewrite
QML_FILES QML_FILES
Main.qml Main.qml
QML_FILES WaterTank.qml WaterTank.qml
QML_FILES SettingsBarIcon.qml SettingsBarIcon.qml
QML_FILES SettingsPopup.qml SettingsPopup.qml
SOURCES configinterface.h configinterface.cpp ConnectButton.qml
QML_FILES ConnectButton.qml SOURCES
SOURCES requestworker.h requestworker.cpp 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(APPLE)
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(appWaterLevelRewrite PROPERTIES set_target_properties(appWaterLevelRewrite PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appWaterLevelRewrite # MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appWaterLevelRewrite
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE MACOSX_BUNDLE TRUE
)
endif()
if(WIN32)
set_target_properties(appWaterLevelRewrite PROPERTIES
WIN32_EXECUTABLE TRUE WIN32_EXECUTABLE TRUE
) )
endif()
set_target_properties(appWaterLevelRewrite PROPERTIES
QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android"
)
target_link_libraries(appWaterLevelRewrite target_link_libraries(appWaterLevelRewrite
PRIVATE Qt6::Quick PRIVATE Qt6::Quick Qt6::Multimedia Qt6::Gui Qt6::Core
) )
install(TARGETS appWaterLevelRewrite install(TARGETS appWaterLevelRewrite

21
ColorInput.qml Normal file
View file

@ -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
}
}

17
CustomCheckBox.qml Normal file
View file

@ -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
}
}

253
Main.qml
View file

@ -5,8 +5,8 @@ import QtQuick.Controls
import QtQuick.Controls.Material import QtQuick.Controls.Material
Window { Window {
width: 640 width: 960
height: 1080 height: 1400
visible: true visible: true
title: qsTr("Water Level Rewrite") title: qsTr("Water Level Rewrite")
id: root id: root
@ -14,6 +14,7 @@ Window {
property color dark_accent_color: "#FF5F7F5F" property color dark_accent_color: "#FF5F7F5F"
property color danger_color: "#F0AF2F1F" property color danger_color: "#F0AF2F1F"
property color water_color: "#F00F7FAF" property color water_color: "#F00F7FAF"
property color water_color_above_alert: "#F00FAF0F"
property color background_color: "#1F1F1F" property color background_color: "#1F1F1F"
Material.accent: root.accent_color Material.accent: root.accent_color
property int font_enlarged_text_size: 20 property int font_enlarged_text_size: 20
@ -21,12 +22,38 @@ Window {
property int distance_count_thres: 3 property int distance_count_thres: 3
property string mode: "http" property string mode: "http"
property string status: "Initializing" 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 { Rectangle {
anchors.fill: parent anchors.fill: parent
color: root.background_color color: root.background_color
id: root_rect
Rectangle { Rectangle {
id: top_bar id: top_bar
height: Math.min(root.height / 12, 100) height: Math.min(root.height / 12, 100)
@ -38,6 +65,7 @@ Window {
} }
color: "#0A0F0F0F" color: "#0A0F0F0F"
SettingsBarIcon { SettingsBarIcon {
id: settings_icon
height: top_bar.height - 20 height: top_bar.height - 20
width: height width: height
anchors { anchors {
@ -54,12 +82,40 @@ Window {
settings_popup.open() 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 { SettingsPopup {
id: settings_popup id: settings_popup
accent_color: root.accent_color 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) { function setConfig(new_config: var) {
settings_popup.config.max = new_config.max settings_popup.config.max = new_config.max
settings_popup.config.min = new_config.min settings_popup.config.min = new_config.min
@ -70,32 +126,66 @@ Window {
console.log("Saving config: ", config) console.log("Saving config: ", config)
config_interface.saveConfig(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 { WaterTank {
id: tank id: tank
anchors { anchors {
top: top_bar.bottom top: connection_text.bottom
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
topMargin: 100 topMargin: 40
} }
height: (parent.height - top_bar.height) / 2 height: (root_rect.height) / root.water_tank_height_factor
width: parent.width / 2.2 width: root_rect.width / root.water_tank_width_factor
base_color: root.background_color base_color: root.background_color
distance: 0 distance: 0
tank_lid_color: "#F0121212" tank_lid_color: "#F0121212"
tank_base_color: "#0FFFFFFF" tank_base_color: "#0FFFFFFF"
water_level_color: "#F00F7FAF" water_level_color: root.water_color
tank_base_border_color: "#F0121212" tank_base_border_color: "#F0121212"
water_color_above_alert: root.water_color_above_alert
min_thres_input: 0 min_thres_input: 0
max_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 { Text {
id: status id: status
text: root.status text: root.status
anchors { anchors {
bottom: connect_button.top bottom: button_base.top
bottomMargin: 20 bottomMargin: 20
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
@ -104,19 +194,30 @@ Window {
font.pointSize: root.font_normal_text_size font.pointSize: root.font_normal_text_size
} }
Rectangle {
id: button_base
color: "#00000000"
width: parent.width / 1.5
height: parent.height / 20
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 30
}
ConnectButton { ConnectButton {
id: connect_button id: connect_button
height: parent.height / 20 width: parent.width / 2.1
width: parent.width / 4 height: parent.height
font_color: root.accent_color font_color: root.accent_color
font_enlarged_text_size: root.font_enlarged_text_size font_enlarged_text_size: root.font_enlarged_text_size
font_normal_text_size: root.font_normal_text_size font_normal_text_size: root.font_normal_text_size
color: root.background_color color: root.background_color
accent_color: root.accent_color accent_color: root.accent_color
anchors { anchors {
horizontalCenter: parent.horizontalCenter left: parent.left
leftMargin: 5
bottom: parent.bottom bottom: parent.bottom
bottomMargin: 50 bottomMargin: 5
} }
onConnect: { onConnect: {
if(connect_button.mode === 0) if(connect_button.mode === 0)
@ -130,6 +231,40 @@ Window {
} }
} }
} }
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 { RequestWorker {
@ -137,14 +272,26 @@ Window {
onStatusChanged: () => { onStatusChanged: () => {
root.status = requester.status; root.status = requester.status;
} }
onNetworkReply: (water_level, fill_rate, expected) => { onNetworkReply: (water_level, fill_rate, expected, data) => {
tank.setDistance(water_level); tank.setDistance(water_level);
tank.rate_of_fill = fill_rate; tank.rate_of_fill = fill_rate;
tank.expected_to_fill = expected; tank.expected_to_fill = expected;
status.color = root.accent_color; status.color = root.accent_color;
request_response_popup.request_response_string = data;
} }
onRequestFailed: () => { onRequestFailed: (data) => {
status.color = root.danger_color; 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
}
} }
} }
@ -152,16 +299,86 @@ Window {
id: config_interface id: config_interface
fileName: "config.json" fileName: "config.json"
onConfig: (config) => { onConfig: (config) => {
settings_popup.config = config settings_popup.config = config;
console.log("Loading initial config"); console.log("Loading initial config");
setConfig(config); setConfig(config);
if(root.status === "Initializing") { if(root.status === "Initializing") {
root.status = "Press 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) { function setConfig(config) {
tank.setConfig(config); tank.setConfig(config);
requester.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();
}
} }

30
RequestResponsePopup.qml Normal file
View file

@ -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
}
}
}

View file

@ -8,6 +8,7 @@ Rectangle {
signal open; signal open;
property color accent_color: "#00000000" property color accent_color: "#00000000"
property int font_enlarged_text_size: 20 property int font_enlarged_text_size: 20
property string text: "⚙"
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: 200 duration: 200
@ -15,8 +16,10 @@ Rectangle {
} }
Text { Text {
id: settings_text id: settings_text
text: "⚙" text: settings_rect.text
anchors.centerIn: parent anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: settings_rect.accent_color color: settings_rect.accent_color
font.pointSize: settings_rect.font_enlarged_text_size font.pointSize: settings_rect.font_enlarged_text_size
} }

View file

@ -6,15 +6,32 @@ import QtQuick.Controls.Material
Popup { Popup {
id: settings_popup id: settings_popup
signal saveConfig(config: var) signal saveConfig(config: var)
signal deleteConfig()
property color accent_color: "#00000000" property color accent_color: "#00000000"
property int font_enlarged_text_size: 20 property int font_enlarged_text_size: 20
property int font_normal_text_size: 16 property int font_normal_text_size: 16
property color background_color: "#00000000"
property var config: { property var config: {
"max": 0, "max": 0,
"min": 0, "min": 0,
"alert": 0, "alert": 0,
"interval": 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 { background: Rectangle {
@ -31,170 +48,461 @@ Popup {
anchors.fill: parent anchors.fill: parent
color: "#F0121212" 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 { Text {
id: settings_label id: settings_label
text: "Settings" text: "Settings"
width: parent.width width: parent.width
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
color: settings_popup.accent_color color: settings_popup.accent_color
font.pointSize: settings_popup.font_enlarged_text_size font.pointSize: settings_popup.font_enlarged_text_size
} }
// Max Threshold
Row {
width: parent.width
spacing: 10
Text { Text {
id: max_thres_label
text: "Max Threshold: " text: "Max Threshold: "
anchors { width: parent.width * 0.4
topMargin: 20
top: settings_label.bottom
left: parent.left
verticalCenter: max_thres_input.verticalCenter
}
height: max_thres_input.height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: settings_popup.accent_color color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size font.pointSize: settings_popup.font_normal_text_size
anchors {
verticalCenter: max_thres_input.verticalCenter
} }
}
TextField { TextField {
id: max_thres_input id: max_thres_input
text: config.max text: config.max
anchors { width: parent.width * 0.55
right: parent.right
rightMargin: 5
top: settings_label.bottom
topMargin: 20
}
width: parent.width / 2
color: settings_popup.accent_color color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size 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
// Min Threshold
Row {
width: parent.width
spacing: 10
Text {
text: "Min Threshold: "
width: parent.width * 0.4
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: settings_popup.accent_color color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size font.pointSize: settings_popup.font_normal_text_size
anchors {
verticalCenter: min_thres_input.verticalCenter
} }
}
TextField { TextField {
id: min_thres_input id: min_thres_input
text: config.min text: config.min
anchors { width: parent.width * 0.55
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 color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size 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 { TextField {
id: alert_thres_input id: alert_thres_input
text: config.alert text: config.alert
anchors { width: parent.width * 0.55
right: parent.right
rightMargin: 5
top: min_thres_label.bottom
topMargin: 20
}
width: parent.width / 2
color: settings_popup.accent_color color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size 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
// Update Interval
Row {
width: parent.width
spacing: 10
Text {
text: "Update Interval: "
width: parent.width * 0.4
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: settings_popup.accent_color color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size font.pointSize: settings_popup.font_normal_text_size
anchors {
verticalCenter: interval_input.verticalCenter
}
} }
TextField { TextField {
id: interval_input id: interval_input
text: config.interval text: config.interval
anchors { width: parent.width * 0.55
right: parent.right
rightMargin: 5
top: alert_thres_input.bottom
topMargin: 20
}
width: parent.width / 2
color: settings_popup.accent_color color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size 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
// URL
Row {
width: parent.width
spacing: 10
Text {
text: "Url: "
width: parent.width * 0.4
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: settings_popup.accent_color color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size font.pointSize: settings_popup.font_normal_text_size
anchors {
verticalCenter: http_url_input.verticalCenter
} }
}
TextField { TextField {
id: http_url_input id: http_url_input
text: config.url text: config.url
anchors { width: parent.width * 0.55
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 color: settings_popup.accent_color
font.pointSize: settings_popup.font_normal_text_size font.pointSize: settings_popup.font_normal_text_size
cursorPosition: 0 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 { Rectangle {
id: save_config id: save_config
anchors { anchors {
@ -237,7 +545,43 @@ Popup {
let alert = parseFloat(alert_thres_input.text); let alert = parseFloat(alert_thres_input.text);
let url = http_url_input.text; let url = http_url_input.text;
let interval = parseInt(interval_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.saveConfig(new_config)
settings_popup.close(); settings_popup.close();
} }
@ -245,7 +589,6 @@ Popup {
save_config.color = settings_rect_pressed_prev save_config.color = settings_rect_pressed_prev
} }
} }
} }
} }
} }

View file

@ -9,9 +9,12 @@ Item {
property color tank_lid_color: "#F0121212" property color tank_lid_color: "#F0121212"
property color tank_base_color: "#00FFFFFF" property color tank_base_color: "#00FFFFFF"
property color water_level_color: "#F00F7FAF" property color water_level_color: "#F00F7FAF"
property color water_color_above_alert: "#F00FAF0F"
property color tank_base_border_color: "#F0121212" property color tank_base_border_color: "#F0121212"
property color text_color: "#F05FAF5F" property color text_color: "#F05FAF5F"
property double height_max: tank_base.height - 10 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 current_level: 0
property double percentage_filled: 0 property double percentage_filled: 0
property int distance: 0 property int distance: 0
@ -19,6 +22,12 @@ Item {
property double max_thres_input: 0 property double max_thres_input: 0
property double rate_of_fill: 0 property double rate_of_fill: 0
property double expected_to_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 { Rectangle {
id: base id: base
@ -28,7 +37,7 @@ Item {
id: tank_lid id: tank_lid
color: root.tank_lid_color color: root.tank_lid_color
anchors { anchors {
bottom: tank_base.top top: parent.top
horizontalCenter: tank_base.horizontalCenter horizontalCenter: tank_base.horizontalCenter
bottomMargin: -5 bottomMargin: -5
} }
@ -40,9 +49,12 @@ Item {
Rectangle { Rectangle {
id: tank_base id: tank_base
color: root.tank_base_color color: root.tank_base_color
anchors.centerIn: parent anchors {
top: tank_lid.bottom
horizontalCenter: parent.horizontalCenter
}
width: parent.width width: parent.width
height: parent.height height: parent.height * 0.55
border.color: root.tank_base_border_color border.color: root.tank_base_border_color
border.width: 5 border.width: 5
radius: 4 radius: 4
@ -70,14 +82,29 @@ Item {
} }
} }
Text { ScrollView {
id: water_level_reading id: rect_base_scroll_view
text: "Current Reading: " + (root.distance / 10)+ " cm"
font.pointSize: 16
color: root.text_color
anchors { anchors {
margins: 5 margins: 5
top: tank_base.bottom 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 horizontalCenter: parent.horizontalCenter
} }
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@ -85,12 +112,11 @@ Item {
Text { Text {
id: water_level_percentage id: water_level_percentage
text: "Percentage: " + root.percentage_filled + " %" text: "Percentage: " + root.percentage_filled.toFixed(2) + " %"
font.pointSize: 16 font.pointSize: root.font_normal_text_size
visible: enable_percentage_reading
color: root.text_color color: root.text_color
anchors { anchors {
margins: 5
top: water_level_reading.bottom
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@ -99,11 +125,10 @@ Item {
Text { Text {
id: fill_status id: fill_status
text: "Filling status: " + (( root.rate_of_fill > 0)? "Filling" : "Discharging") text: "Filling status: " + (( root.rate_of_fill > 0)? "Filling" : "Discharging")
font.pointSize: 16 font.pointSize: root.font_normal_text_size
color: root.text_color color: root.text_color
visible: enable_filling_status
anchors { anchors {
margins: 5
top: water_level_percentage.bottom
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@ -111,11 +136,10 @@ Item {
Text { Text {
id: rate_of_fill_label id: rate_of_fill_label
text: "Rate of fill: " + root.rate_of_fill + " cm/s" text: "Rate of fill: " + root.rate_of_fill + " cm/s"
font.pointSize: 16 font.pointSize: root.font_normal_text_size
color: root.text_color color: root.text_color
visible: enable_rate_of_fill
anchors { anchors {
margins: 5
top: fill_status.bottom
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@ -123,20 +147,28 @@ Item {
Text { Text {
id: expected_to_fill_label id: expected_to_fill_label
text: "Expected to fill in: " + root.expected_to_fill + " s" text: "Expected to fill in: " + root.expected_to_fill + " s"
font.pointSize: 16 font.pointSize: root.font_normal_text_size
color: root.text_color color: root.text_color
visible: (root.rate_of_fill > 0) ? true : false visible: (root.rate_of_fill > 0 && enable_expected_to_fill === true) ? true : false
anchors { anchors {
margins: 5
top: rate_of_fill_label.bottom
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
} }
}
}
function setConfig(config: var) { function setConfig(config: var) {
min_thres_input = config.min; min_thres_input = config.min;
max_thres_input = config.max; 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) { function setDistance(distance) {
@ -156,8 +188,17 @@ Item {
console.log("Current reading is: " + current_reading) console.log("Current reading is: " + current_reading)
console.log("Normalized height is: " + normalized_height) console.log("Normalized height is: " + normalized_height)
console.log("Percentage filled is: " + percentage_filled) 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.current_level = normalized_height;
root.percentage_filled = percentage_filled; 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
}
} }
} }

View file

@ -0,0 +1,61 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.qtproject.example.appWaterLevelRewrite"
android:installLocation="auto"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" />
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true" />
<application
android:name="org.qtproject.qt.android.bindings.QtApplication"
android:hardwareAccelerated="true"
android:label="appWaterLevelRewrite"
android:requestLegacyExternalStorage="true"
android:enableOnBackInvokedCallback="false"
android:allowBackup="true"
android:fullBackupOnly="false">
<activity
android:name="org.qtproject.qt.android.bindings.QtActivity"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:label="appWaterLevelRewrite"
android:launchMode="singleTop"
android:screenOrientation="unspecified"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="appWaterLevelRewrite" />
<meta-data
android:name="android.app.arguments"
android:value="" />
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.qtprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/qtprovider_paths"/>
</provider>
</application>
</manifest>

View file

@ -2,6 +2,7 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <filesystem>
ConfigInterface::ConfigInterface(QObject *parent) ConfigInterface::ConfigInterface(QObject *parent)
: QObject{parent} : QObject{parent}
@ -37,6 +38,12 @@ void ConfigInterface::setFileName(const QString &newFileName)
emit fileNameChanged(); emit fileNameChanged();
} }
void ConfigInterface::deleteConfig()
{
std::filesystem::remove(std::filesystem::path(m_fileName.toStdString()));
emit configDeleted();
}
void ConfigInterface::saveConfig(QJsonObject config) void ConfigInterface::saveConfig(QJsonObject config)
{ {
QJsonDocument doc; QJsonDocument doc;

View file

@ -19,7 +19,9 @@ public:
signals: signals:
void config(QJsonObject config); void config(QJsonObject config);
void fileNameChanged(); void fileNameChanged();
void configDeleted();
public slots: public slots:
void deleteConfig();
void saveConfig(QJsonObject config); void saveConfig(QJsonObject config);
private: private:
QString m_fileName; QString m_fileName;

BIN
echo.mp3 Normal file

Binary file not shown.

View file

@ -3,8 +3,11 @@
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
qputenv("QT_SCALE_FACTOR", "1.0");
qputenv("QT_SCALE_FACTOR_ROUNDING_POLICY", "Round");
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
QObject::connect( QObject::connect(
&engine, &engine,

5
media.qrc Normal file
View file

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/sounds">
<file>echo.mp3</file>
</qresource>
</RCC>

View file

@ -3,11 +3,22 @@
#include <QJsonValue> #include <QJsonValue>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonDocument> #include <QJsonDocument>
#include <QFile>
#include <QStandardPaths>
RequestWorker::RequestWorker(QObject *parent) RequestWorker::RequestWorker(QObject *parent)
: 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) void RequestWorker::setConfig(QJsonObject config)
@ -16,11 +27,13 @@ void RequestWorker::setConfig(QJsonObject config)
this->interval = config["interval"].toInt(); this->interval = config["interval"].toInt();
this->max_height = config["max"].toInt(); this->max_height = config["max"].toInt();
this->min_height = config["min"].toInt(); this->min_height = config["min"].toInt();
this->alarm_thres = config["alert"].toInt();
qDebug() << "In cpp config values are: "; qDebug() << "In cpp config values are: ";
qDebug() << "Url: " << url; qDebug() << "Url: " << url;
qDebug() << "Interval: " << interval; qDebug() << "Interval: " << interval;
qDebug() << "Max Height: " << max_height; qDebug() << "Max Height: " << max_height;
qDebug() << "Min Height: " << min_height; qDebug() << "Min Height: " << min_height;
qDebug() << "Alert: " << alarm_thres;
} }
void RequestWorker::start() void RequestWorker::start()
@ -51,6 +64,129 @@ void RequestWorker::stop()
qDebug() << "Stopping" << timer->isActive(); 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 != "<unknown ssid>") {
emit connectionText("WiFi connected: " + ssid);
}
else if(ssid.length() == 0) {
emit connectionText("WiFi might not be connected", true);
}
else if(ssid != "<unknown 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<void>("setDataSource", "(Ljava/lang/String;)V", jPath.object<jstring>());
// Enable looping
mediaPlayer.callMethod<void>("setLooping", "(Z)V", true);
// Prepare and start playback
mediaPlayer.callMethod<void>("prepare");
mediaPlayer.callMethod<void>("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() void RequestWorker::makeRequest()
{ {
if(stop_timer) if(stop_timer)
@ -67,15 +203,16 @@ void RequestWorker::makeRequest()
void RequestWorker::requestFinished(QNetworkReply *rep) void RequestWorker::requestFinished(QNetworkReply *rep)
{ {
QByteArray data = rep->readAll(); QByteArray data = rep->readAll();
rep->deleteLater();
rep = nullptr;
auto now = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now();
auto time_diff = std::chrono::duration_cast<std::chrono::microseconds>(now - prev_time); auto time_diff = std::chrono::duration_cast<std::chrono::microseconds>(now - prev_time);
if(data.size() == 0) { if(data.size() == 0) {
prev_time = now; prev_time = now;
timer->start(); timer->start();
emit requestFailed(); QString request_response_string = getRequestResponseString(rep);
emit requestFailed(request_response_string);
emit setStatus("Invalid Response; Resending Request"); emit setStatus("Invalid Response; Resending Request");
rep->deleteLater();
rep = nullptr;
return; return;
} }
unsigned int time_in_microseconds = time_diff.count(); unsigned int time_in_microseconds = time_diff.count();
@ -85,6 +222,13 @@ void RequestWorker::requestFinished(QNetworkReply *rep)
float rate_of_fill = ( float )(prev_distance - water_level) /(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; rate_of_fill = rate_of_fill * 1'000'000;
int expected_to_fill = (water_level - min_height) / 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) { if(reply_number == 0) {
rate_of_fill = 0; rate_of_fill = 0;
expected_to_fill = 0; expected_to_fill = 0;
@ -93,16 +237,22 @@ void RequestWorker::requestFinished(QNetworkReply *rep)
qDebug() << "Got water level: " << water_level; qDebug() << "Got water level: " << water_level;
qDebug() << "Got rate of fill: " << rate_of_fill; qDebug() << "Got rate of fill: " << rate_of_fill;
qDebug() << "Got expected to fill: " << expected_to_fill; qDebug() << "Got expected to fill: " << expected_to_fill;
qDebug() << "Percentage filled: " << percentage_filled;
qDebug() << "Time difference: " << time_in_microseconds; qDebug() << "Time difference: " << time_in_microseconds;
prev_distance = water_level; prev_distance = water_level;
emit networkReply(data_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) { if(stop_timer) {
setStatus("Timer stopped."); setStatus("Timer stopped.");
return; return;
} }
prev_time = now; prev_time = now;
timer->start(); 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); setStatus(status);
} }
@ -118,3 +268,35 @@ void RequestWorker::setStatus(const QString &newStatus)
m_status = newStatus; m_status = newStatus;
emit statusChanged(); 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<void>("stop");
m_androidMediaPlayer->callMethod<void>("release");
delete m_androidMediaPlayer;
m_androidMediaPlayer = nullptr;
}
#endif
}

View file

@ -6,9 +6,15 @@
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply> #include <QNetworkReply>
#include <QMediaPlayer>
#include <QAudioOutput>
#include <QTimer> #include <QTimer>
#include <QThread> #include <QThread>
#include <chrono> #include <chrono>
#ifdef Q_OS_ANDROID
#include "wifihelper.h"
#include <QJniObject>
#endif
class RequestWorker : public QObject class RequestWorker : public QObject
{ {
@ -24,27 +30,45 @@ class RequestWorker : public QObject
int min_height = 0; int min_height = 0;
bool stop_timer = false; bool stop_timer = false;
int reply_number = 0; 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<std::chrono::high_resolution_clock> prev_time; std::chrono::time_point<std::chrono::high_resolution_clock> prev_time;
public: public:
explicit RequestWorker(QObject *parent = nullptr); explicit RequestWorker(QObject *parent = nullptr);
QString status() const; QString status() const;
void setStatus(const QString &newStatus); void setStatus(const QString &newStatus);
signals: signals:
void requestFailed(); void requestFailed(QString request_response_string);
void networkReply(int distance, float rate_of_fill, int expected_to_fill); void networkReply(int distance, float rate_of_fill, int expected_to_fill, QString data);
void statusChanged(); void statusChanged();
void playMedia();
void connectionText(QString text, bool errored = false);
public slots: public slots:
void startAlarm();
void stopAlarm();
void setConfig(QJsonObject config); void setConfig(QJsonObject config);
void start(); void start();
void stop(); void stop();
private slots: private slots:
QString getRequestResponseString(QNetworkReply *rep, QString rep_data = "");
void pollWiFi();
void gotWiFiSSID(QString ssid);
void playAlarm();
void makeRequest(); void makeRequest();
void requestFinished(QNetworkReply *rep); void requestFinished(QNetworkReply *rep);
private: private:
QString m_status; QString m_status;
#ifdef Q_OS_ANDROID
QJniObject *m_androidMediaPlayer = nullptr;
#endif
}; };
#endif // REQUESTWORKER_H #endif // REQUESTWORKER_H

77
wifihelper.cpp Normal file
View file

@ -0,0 +1,77 @@
#include "wifihelper.h"
#include <QCoreApplication>
#include <QGuiApplication>
#ifdef Q_OS_ANDROID
#include <QJniObject>
#endif
#include <QPermissions>
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
}

17
wifihelper.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef WIFIHELPER_H
#define WIFIHELPER_H
#include <QObject>
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