WaterLevelSensor/Main.qml

804 lines
32 KiB
QML
Raw Normal View History

2026-04-01 18:33:19 +05:30
import QtQuick
import waterLevelSensor
import QtQuick.Effects
import QtQuick.Controls
import QtQuick.Controls.Material
Window {
width: 640
2026-04-04 12:24:03 +05:30
height: 1080
2026-04-01 18:33:19 +05:30
visible: true
title: qsTr("Hello World")
id: root
property color accent_color: "#F05FAF5F"
2026-04-13 17:49:08 +05:30
property color dark_accent_color: "#FF5F7F5F"
2026-04-01 18:33:19 +05:30
property color danger_color: "#F0AF2F1F"
property color water_color: "#F00F7FAF"
Material.accent: root.accent_color
property int font_enlarged_text_size: 20
2026-04-03 18:46:34 +05:30
property int font_normal_text_size: 16
2026-04-01 18:33:19 +05:30
property double distance: 0
property int distance_count: 0
2026-04-17 15:43:25 +05:30
property int distance_count_thres: 3
2026-04-13 17:49:08 +05:30
property string mode: "http"
2026-04-01 18:33:19 +05:30
// visibility: Window.FullScreen
2026-04-04 12:24:03 +05:30
Component.onCompleted: {
2026-04-17 15:43:25 +05:30
loading_popup.open();
}
Popup {
id: loading_popup
background: Rectangle {
color: "#B0121212"
radius: 5
border.color: root.accent_color
border.width: 1
2026-04-04 12:24:03 +05:30
}
2026-04-17 15:43:25 +05:30
height: parent.height / 10
width: parent.width / 1.1
anchors.centerIn: Overlay.overlay
closePolicy: Popup.NoAutoClose
BusyIndicator {
id: loading_indicator
running: true
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
}
Text {
id: loading_text
text: "Loading..."
anchors {
leftMargin: 20
left: loading_indicator.right
verticalCenter: parent.verticalCenter
}
verticalAlignment: Text.AlignVCenter
color: root.accent_color
font.pointSize: root.font_enlarged_text_size
}
2026-04-04 12:24:03 +05:30
}
2026-04-01 18:33:19 +05:30
Rectangle {
anchors.fill: parent
id: background
color: "#DF0F0F0F"
Rectangle {
id: top_bar
height: Math.min(root.height / 12, 100)
width: root.width
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: 25
}
color: "#0A0F0F0F"
Rectangle {
id: settings_rect
height: top_bar.height - 20
width: top_bar.width / 12
anchors {
top: parent.top
left: parent.left
bottomMargin: 5
topMargin: 5
leftMargin: 5
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
color: "#0A0F0F0F"
border.color: "#AF121212"
border.width: 0.5
radius: 2
Text {
id: settings_text
text: "⚙"
anchors.centerIn: parent
color: root.accent_color
font.pointSize: root.font_enlarged_text_size
}
MouseArea {
id: settings_button_area
anchors.fill: parent
hoverEnabled: true
property color settings_rect_pressed_prev: "#00000000"
property color settings_rect_entered_prev: "#00000000"
onEntered: {
settings_rect_entered_prev = settings_rect.color
settings_rect.color = "#5A1F1F1F"
}
onExited: {
settings_rect.color = settings_rect_entered_prev
}
onPressed: {
settings_rect_pressed_prev = settings_rect.color
settings_rect.color = "#5A1F4F1F"
settings_popup.open()
}
onReleased: {
settings_rect.color = settings_rect_pressed_prev
}
}
}
Rectangle {
property color old_color: "#0F0F0F0F"
id: status_indicator
width: 20
height: 20
radius: 20
anchors {
top: parent.top
right: parent.right
topMargin: 20
bottomMargin: 5
leftMargin: 5
rightMargin: 20
}
color: "#F0AF2F1F"
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
}
}
Text {
id: status_label
anchors {
top: parent.top
topMargin: 20
right: status_indicator.left
rightMargin: 10
}
horizontalAlignment: Text.AlignRight
width: parent.width - status_indicator.width
height: parent.height
color: root.accent_color
font.pointSize: root.font_normal_text_size
text: "Disconnected"
}
}
Rectangle {
id: base
color: "#0A0F0F0F"
width: parent.width
height: parent.height - top_bar.height
anchors {
top: top_bar.bottom
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
Popup {
id: settings_popup
background: Rectangle {
color: "#F0121212"
radius: 5
border.color: root.accent_color
border.width: 1
}
2026-04-13 17:57:36 +05:30
height: Math.max(parent.height / 1.2, contentHeight)
2026-04-13 17:49:08 +05:30
width: parent.width / 1.1
2026-04-01 18:33:19 +05:30
anchors.centerIn: Overlay.overlay
Rectangle {
id: settings_base
anchors.fill: parent
color: "#F0121212"
Text {
id: settings_label
text: "Settings"
width: parent.width
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
horizontalAlignment: Text.AlignHCenter
color: root.accent_color
font.pointSize: root.font_enlarged_text_size
}
Text {
id: max_thres_label
text: "Max Threshold: "
anchors {
topMargin: 20
top: settings_label.bottom
left: parent.left
2026-04-04 12:24:03 +05:30
verticalCenter: max_thres_input.verticalCenter
2026-04-01 18:33:19 +05:30
}
height: max_thres_input.height
verticalAlignment: Text.AlignVCenter
color: root.accent_color
2026-04-03 18:46:34 +05:30
font.pointSize: root.font_normal_text_size
2026-04-01 18:33:19 +05:30
}
TextField {
id: max_thres_input
text: config.max
anchors {
2026-04-04 12:24:03 +05:30
right: parent.right
rightMargin: 5
2026-04-01 18:33:19 +05:30
top: settings_label.bottom
topMargin: 20
}
2026-04-03 18:46:34 +05:30
width: parent.width / 2
2026-04-01 18:33:19 +05:30
color: root.accent_color
2026-04-03 18:46:34 +05:30
font.pointSize: root.font_normal_text_size
2026-04-01 18:33:19 +05:30
}
Text {
id: min_thres_label
text: "Min Threshold: "
anchors {
topMargin: 20
top: max_thres_label.bottom
left: parent.left
2026-04-04 12:24:03 +05:30
verticalCenter: min_thres_input.verticalCenter
2026-04-01 18:33:19 +05:30
}
height: min_thres_input.height
verticalAlignment: Text.AlignVCenter
color: root.accent_color
2026-04-03 18:46:34 +05:30
font.pointSize: root.font_normal_text_size
2026-04-01 18:33:19 +05:30
}
TextField {
id: min_thres_input
text: config.min
anchors {
2026-04-04 12:24:03 +05:30
right: parent.right
rightMargin: 5
2026-04-01 18:33:19 +05:30
top: max_thres_label.bottom
topMargin: 20
}
2026-04-03 18:46:34 +05:30
width: parent.width / 2
2026-04-01 18:33:19 +05:30
color: root.accent_color
2026-04-03 18:46:34 +05:30
font.pointSize: root.font_normal_text_size
2026-04-01 18:33:19 +05:30
}
Text {
id: alert_thres_label
text: "Alert Percentage: "
anchors {
topMargin: 20
top: min_thres_label.bottom
left: parent.left
2026-04-04 12:24:03 +05:30
verticalCenter: alert_thres_input.verticalCenter
2026-04-01 18:33:19 +05:30
}
height: alert_thres_input.height
verticalAlignment: Text.AlignVCenter
color: root.accent_color
2026-04-03 18:46:34 +05:30
font.pointSize: root.font_normal_text_size
2026-04-01 18:33:19 +05:30
}
TextField {
id: alert_thres_input
text: config.alert
anchors {
2026-04-04 12:24:03 +05:30
right: parent.right
rightMargin: 5
2026-04-01 18:33:19 +05:30
top: min_thres_label.bottom
topMargin: 20
}
2026-04-03 18:46:34 +05:30
width: parent.width / 2
2026-04-01 18:33:19 +05:30
color: root.accent_color
2026-04-03 18:46:34 +05:30
font.pointSize: root.font_normal_text_size
2026-04-01 18:33:19 +05:30
}
2026-04-03 18:46:34 +05:30
CheckBox {
id: flip_bits_box
text: "Flip Bits"
checked: config.flipBits
anchors {
topMargin: 20
top: alert_thres_label.bottom
2026-04-04 12:24:03 +05:30
horizontalCenter: parent.horizontalCenter
2026-04-03 18:46:34 +05:30
}
contentItem: Text {
text: flip_bits_box.text
font.pointSize: root.font_normal_text_size
color: root.accent_color
leftPadding: flip_bits_box.indicator.width + flip_bits_box.spacing
}
}
2026-04-13 17:49:08 +05:30
Text {
id: settings_mode_text
anchors {
topMargin: 20
top: flip_bits_box.bottom
left: parent.left
}
font.pointSize: root.font_normal_text_size
color: root.accent_color
text: "Mode: "
}
Row {
id: mode_row
anchors {
left: settings_mode_text.right
right: parent.right
verticalCenter: settings_mode_text.verticalCenter
leftMargin: 10
}
RadioButton {
id: udp_radio_button
checked: config.mode === "udp" ? true : false
text: "UDP"
font.pointSize: root.font_normal_text_size
contentItem: Text {
text: udp_radio_button.text
font: udp_radio_button.font
opacity: enabled ? 1.0 : 0.3
color: udp_radio_button.down ? root.dark_accent_color : root.accent_color
verticalAlignment: Text.AlignVCenter
leftPadding: udp_radio_button.indicator.width + udp_radio_button.spacing
}
onCheckedChanged: {
if(checked) {
root.mode = "udp";
}
}
}
RadioButton {
id: http_radio_button
checked: config.mode === "http" ? true : false
text: "HTTP"
font.pointSize: root.font_normal_text_size
contentItem: Text {
text: http_radio_button.text
font: http_radio_button.font
opacity: enabled ? 1.0 : 0.3
color: http_radio_button.down ? root.dark_accent_color : root.accent_color
verticalAlignment: Text.AlignVCenter
leftPadding: http_radio_button.indicator.width + http_radio_button.spacing
}
onCheckedChanged: {
if(checked) {
root.mode = "http";
}
}
}
}
Text {
id: http_url_label
text: "Url: "
anchors {
topMargin: 20
top: settings_mode_text.bottom
left: parent.left
verticalCenter: http_url_input.verticalCenter
}
height: http_url_input.height
verticalAlignment: Text.AlignVCenter
color: root.accent_color
font.pointSize: root.font_normal_text_size
}
2026-04-03 18:46:34 +05:30
2026-04-13 17:49:08 +05:30
TextField {
id: http_url_input
text: config.url
anchors {
right: parent.right
rightMargin: 20
leftMargin: 20
top: mode_row.bottom
topMargin: 20
}
enabled: root.mode === "http" ? true : false
width: Math.max(parent.width / 2, parent.width - http_url_label.width - anchors.leftMargin)
color: root.accent_color
font.pointSize: root.font_normal_text_size
cursorPosition: 0
}
2026-04-01 18:33:19 +05:30
Text {
id: settings_error_label
property string settings_error_text: ""
text: "Error: " + settings_error_text
visible: settings_error_text === "" ? false : true
anchors {
topMargin: 20
2026-04-13 17:49:08 +05:30
top: http_url_label.bottom
bottom: reset_averages.top
2026-04-03 18:46:34 +05:30
bottomMargin: 10
2026-04-01 18:33:19 +05:30
left: parent.left
horizontalCenter: parent.horizontalCenter
}
horizontalAlignment: Text.AlignHCenter
color: root.danger_color
font.pixelSize: root.font_normal_text_size
}
2026-04-03 18:46:34 +05:30
Rectangle {
id: reset_averages
anchors {
topMargin: 10
bottom: save_config.top
2026-04-04 12:24:03 +05:30
bottomMargin: 10
2026-04-03 18:46:34 +05:30
horizontalCenter: parent.horizontalCenter
}
color: "#F0121212"
2026-04-04 12:24:03 +05:30
width: parent.width / 1.3
2026-04-03 18:46:34 +05:30
height: 60
border {
width: 1
color: root.accent_color
}
radius: 3
2026-04-01 18:33:19 +05:30
2026-04-03 18:46:34 +05:30
Text {
anchors.centerIn: parent
text: "Reset Averages"
color: root.accent_color
font.pointSize: root.font_enlarged_text_size
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
property color settings_rect_pressed_prev: "#00000000"
property color settings_rect_entered_prev: "#00000000"
onEntered: {
2026-04-04 12:24:03 +05:30
settings_rect_entered_prev = reset_averages.color
reset_averages.color = "#5A1F1F1F"
2026-04-03 18:46:34 +05:30
}
onExited: {
2026-04-04 12:24:03 +05:30
reset_averages.color = settings_rect_entered_prev
2026-04-03 18:46:34 +05:30
}
onPressed: {
2026-04-04 12:24:03 +05:30
settings_rect_pressed_prev = reset_averages.color
reset_averages.color = "#5A1F4F1F"
2026-04-03 18:46:34 +05:30
listener.resetAverages();
}
onReleased: {
2026-04-04 12:24:03 +05:30
reset_averages.color = settings_rect_pressed_prev
2026-04-03 18:46:34 +05:30
}
}
}
2026-04-01 18:33:19 +05:30
Rectangle {
id: save_config
anchors {
topMargin: 10
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
color: "#F0121212"
2026-04-04 12:24:03 +05:30
width: parent.width / 1.3
2026-04-01 18:33:19 +05:30
height: 60
border {
width: 1
color: root.accent_color
}
radius: 3
Text {
anchors.centerIn: parent
text: "Save"
color: root.accent_color
font.pointSize: root.font_enlarged_text_size
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
property color settings_rect_pressed_prev: "#00000000"
property color settings_rect_entered_prev: "#00000000"
onEntered: {
settings_rect_entered_prev = save_config.color
save_config.color = "#5A1F1F1F"
}
onExited: {
save_config.color = settings_rect_entered_prev
}
onPressed: {
settings_rect_pressed_prev = save_config.color
save_config.color = "#5A1F4F1F"
let max = parseFloat(min_thres_input.text);
let min = parseFloat(min_thres_input.text);
let alert = parseFloat(alert_thres_input.text);
2026-04-03 18:46:34 +05:30
let flipBits = flip_bits_box.checked;
2026-04-13 17:49:08 +05:30
let mode = root.mode;
let url = http_url_input.text;
2026-04-03 18:46:34 +05:30
console.log("Parsed values:", max, min, alert, flip_bits_box.checked);
2026-04-01 18:33:19 +05:30
if(max < min) {
settings_error_label.settings_error_text = "Max cannot be less than min";
return;
}
if (alert > 100 || alert < 0) {
settings_error_label.settings_error_text = "Alert must be between 0 to 100";
return;
}
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
console.log("Alert effective reading is: " + alert_effective_reading + " for value: " + alert);
2026-04-13 17:49:08 +05:30
listener.setConfig(alert_effective_reading, flipBits, mode, url);
config.saveConfig(max_thres_input.text, min_thres_input.text, alert_thres_input.text, flip_bits_box.checked, mode, url);
2026-04-01 18:33:19 +05:30
settings_popup.close();
}
onReleased: {
save_config.color = settings_rect_pressed_prev
}
}
2026-04-03 18:46:34 +05:30
2026-04-01 18:33:19 +05:30
}
}
}
Rectangle {
id: tank_lid
color: "#F0121212"
anchors {
bottom: tank_base.top
horizontalCenter: tank_base.horizontalCenter
bottomMargin: -5
}
radius: 10
width: parent.width / 4
height: parent.height / 15
}
Rectangle {
id: tank_base
color: "#0FFFFFFF"
anchors.centerIn: parent
2026-04-04 12:24:03 +05:30
width: parent.width / 2
2026-04-01 18:33:19 +05:30
height: parent.height / 2
border.color: "#F0121212"
border.width: 5
radius: 4
}
Rectangle {
id: water_level
color: root.water_color
property double height_max: tank_base.height - 10
property double current_level: 0
property double percentage_filled: 0
height: water_level.current_level
width: tank_base.width - 10
anchors {
bottom: tank_base.bottom
horizontalCenter: tank_base.horizontalCenter
bottomMargin: 5
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
Behavior on height {
NumberAnimation {
duration: 200
}
}
}
Text {
id: info_text
text: "Current Reading: " + root.distance.toFixed(2) + " cm"
anchors {
top: tank_base.bottom
topMargin: 20
horizontalCenter: parent.horizontalCenter
}
horizontalAlignment: Text.AlignHCenter
color: root.accent_color
font.pixelSize: root.font_normal_text_size
}
Text {
id: info_percent_text
text: "Percentage filled: " + water_level.percentage_filled.toFixed(2) + " %"
anchors {
top: info_text.bottom
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
horizontalAlignment: Text.AlignHCenter
color: root.accent_color
font.pixelSize: root.font_normal_text_size
}
2026-04-13 19:34:36 +05:30
Text {
id: last_updated_text
property string last_updated: ""
text: "Last Updated On Server On: " + last_updated
anchors {
top: info_percent_text.bottom
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
horizontalAlignment: Text.AlignHCenter
color: root.accent_color
font.pixelSize: root.font_normal_text_size
visible: config.mode === "http" ? true : false
}
2026-04-01 18:33:19 +05:30
Text {
id: error_text
property string error_string: ""
text: "Error: " + error_string
visible: error_string === "" ? false : true;
anchors {
2026-04-13 19:34:36 +05:30
top: last_updated_text.bottom
2026-04-01 18:33:19 +05:30
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
horizontalAlignment: Text.AlignHCenter
color: root.danger_color
font.pixelSize: root.font_normal_text_size
}
}
}
Timer {
id: status_indicator_timer
running: false
repeat: false
triggeredOnStart: false
interval: 300
onTriggered: {
status_indicator.color = status_indicator.old_color;
}
}
ConfigInterface {
id: config
2026-04-13 17:49:08 +05:30
onConfigData: (max, min, alert, mode, url) => {
console.log("Got config data as: ", max, min, alert, mode, url);
}
onModeChanged: {
root.mode = mode;
2026-04-01 18:33:19 +05:30
}
}
2026-04-13 17:49:08 +05:30
Timer {
2026-04-16 17:18:02 +05:30
property int count: 0
2026-04-13 17:49:08 +05:30
id: init_timer
running: true
2026-04-16 17:18:02 +05:30
repeat: true
2026-04-13 17:49:08 +05:30
interval: 1000
onTriggered: {
2026-04-16 17:18:02 +05:30
if(count === 0) {
listener.startService();
count++;
return;
}
else {
console.log("Timer triggered saving config, hope everything is loaded by now");
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
let alert = parseFloat(alert_thres_input.text);
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
console.log("Setting initial configs: ", alert_effective_reading, config.flipBits , config.mode, config.url);
listener.setConfig(alert_effective_reading, config.flipBits , config.mode, config.url);
http_url_input.cursorPosition = 0;
init_timer.repeat = false;
init_timer.stop();
}
2026-04-13 17:49:08 +05:30
}
2026-04-01 18:33:19 +05:30
2026-04-13 17:49:08 +05:30
}
2026-04-01 18:33:19 +05:30
UDPListener {
id: listener
onShowError: (err) => {
error_text.error_string = err;
}
2026-04-16 17:18:02 +05:30
onLastUpdatedChanged: (update, warn) => {
2026-04-13 19:34:36 +05:30
console.log("QML Last Updated: ", update);
2026-04-16 17:18:02 +05:30
if(!warn)
{
last_updated_text.last_updated = update;
last_updated_text.color = root.accent_color;
}
else
{
last_updated_text.last_updated = update;
last_updated_text.color = root.danger_color;
}
2026-04-13 19:34:36 +05:30
}
2026-04-01 18:33:19 +05:30
onDistanceChanged: {
console.log("Distance changed");
root.distance = listener.distance;
error_text.error_string = "";
console.log("Distance count: ", root.distance_count);
2026-04-17 15:43:25 +05:30
if(root.distance_count >= root.distance_count_thres) {
if(loading_popup.opened) {
loading_popup.close();
}
2026-04-01 18:33:19 +05:30
status_label.text = "Recieving values";
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
let current_reading = Math.max(0, root.distance - current_min_reading);
let height_max_possible = water_level.height_max;
let percentage_filled = Math.max(0, ((effective_max_reading - current_reading) / effective_max_reading * 100))
let normalized_height = percentage_filled * height_max_possible / 100;
let alert = parseFloat(alert_thres_input.text);
if(percentage_filled <= alert) {
console.log("Water Level is lower than alert");
water_level.color = root.danger_color;
}
else {
water_level.color = root.water_color;
}
2026-04-09 14:26:21 +05:30
if(root.distance_count > 1000) {
2026-04-17 15:43:25 +05:30
root.distance_count = root.distance_count_thres;
2026-04-09 14:26:21 +05:30
}
2026-04-01 18:33:19 +05:30
console.log("Current reading is: " + current_reading)
console.log("Normalized height is: " + normalized_height)
water_level.current_level = normalized_height;
water_level.percentage_filled = percentage_filled;
}
}
onConnected: {
error_text.error_string = "";
console.log("Connected");
status_indicator.color = "#F05F5F0F"
status_label.text = "Waiting for more values";
}
onDisconnected: {
error_text.error_string = "Disconnected";
console.log("Disconnected");
status_indicator.color = "#F05F5F0F"
status_label.text = "Disconnected";
}
onDistanceReceived: (distance, time) => {
if(root.distance_count >= 1)
dataBlink();
if(root.distance_count == 0) {
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
let alert = parseFloat(alert_thres_input.text);
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
console.log("Alert effective reading is: " + alert_effective_reading + " for value: " + alert);
2026-04-13 17:49:08 +05:30
listener.setConfig(alert_effective_reading, flip_bits_box.checked, root.mode, http_url_input.text);
2026-04-01 18:33:19 +05:30
}
distance_count += 1;
}
}
2026-04-16 17:18:02 +05:30
onClosing: (close)=> {
2026-04-01 18:33:19 +05:30
if(Qt.platform.os === "android"){
2026-04-16 17:18:02 +05:30
// close.accepted = false
// listener.moveAppToBackground();
// console.log("Moving to backgound");
close.accepted = true;
2026-04-01 18:33:19 +05:30
} else {
close.accepted = true
}
}
function dataBlink() {
2026-04-04 12:24:03 +05:30
if(status_indicator_timer.running) {
status_indicator_timer.stop();
}
let old_color = "#F05F5F0F";
2026-04-01 18:33:19 +05:30
let new_color = "#F04FAF5F";
status_indicator.color = new_color;
status_indicator.old_color = old_color;
status_indicator_timer.start();
}
}