first commit

This commit is contained in:
TheUnrealDragon 2026-04-01 18:33:19 +05:30
commit 9bfa1e11fe
26 changed files with 1751 additions and 0 deletions

82
.gitignore vendored Normal file
View file

@ -0,0 +1,82 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
*.qbs.user*
CMakeLists.txt.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe
# Directories with generated files
.moc/
.obj/
.pch/
.rcc/
.uic/
/build*/

97
CMakeLists.txt Normal file
View file

@ -0,0 +1,97 @@
cmake_minimum_required(VERSION 3.16)
project(waterLevelSensor VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTORCC ON)
find_package(Qt6 REQUIRED COMPONENTS Quick Network)
qt_standard_project_setup(REQUIRES 6.9)
qt_add_executable(appwaterLevelSensor
main.cpp
images.qrc
)
if(ANDROID)
qt_add_library(service SHARED
servicemain.cpp
service.h service.cpp
)
set_property(TARGET appwaterLevelSensor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/android)
set_property(TARGET service APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/android)
target_link_libraries(service
PRIVATE
Qt::Core
Qt::CorePrivate
Qt::Network
Qt::Quick
)
qt_add_qml_module(appwaterLevelSensor
URI waterLevelSensor
QML_FILES
Main.qml
RESOURCES
RESOURCES images.qrc
SOURCES moving_container.h
SOURCES configinterface.h configinterface.cpp
SOURCES udplistener.h udplistener.cpp
SOURCES service.h
)
target_link_libraries(appwaterLevelSensor
PRIVATE Qt6::Quick Qt6::Network service
)
qt_finalize_executable(appwaterLevelSensor)
add_dependencies(appwaterLevelSensor service)
qt_generate_deploy_app_script(
TARGET appwaterLevelSensor
OUTPUT_SCRIPT deploy_script
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})
endif()
if(NOT ANDROID)
qt_add_qml_module(appwaterLevelSensor
URI waterLevelSensor
QML_FILES
Main.qml
RESOURCES
RESOURCES images.qrc
SOURCES udplistener.h udplistener.cpp
SOURCES moving_container.h
SOURCES configinterface.h configinterface.cpp
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manu ally though.
set_target_properties(appwaterLevelSensor PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appwaterLevelSensor
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(appwaterLevelSensor
PRIVATE Qt6::Quick Qt6::Network
)
qt_finalize_executable(appwaterLevelSensor)
include(GNUInstallDirs)
install(TARGETS appwaterLevelSensor
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()

532
Main.qml Normal file
View file

@ -0,0 +1,532 @@
import QtQuick
import waterLevelSensor
import QtQuick.Effects
import QtQuick.Controls
import QtQuick.Controls.Material
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
id: root
property color accent_color: "#F05FAF5F"
property color danger_color: "#F0AF2F1F"
property color water_color: "#F00F7FAF"
Material.accent: root.accent_color
property int font_enlarged_text_size: 20
property int font_normal_text_size: 18
property double distance: 0
property int distance_count: 0
// visibility: Window.FullScreen
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
}
height: parent.height / 1.5
width: parent.width / 1.5
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
}
height: max_thres_input.height
verticalAlignment: Text.AlignVCenter
color: root.accent_color
font.pixelSize: root.font_normal_text_size
}
TextField {
id: max_thres_input
text: config.max
anchors {
left: max_thres_label.right
leftMargin: 5
top: settings_label.bottom
topMargin: 20
}
color: root.accent_color
font.pixelSize: root.font_normal_text_size
}
Text {
id: min_thres_label
text: "Min Threshold: "
anchors {
topMargin: 20
top: max_thres_label.bottom
left: parent.left
}
height: min_thres_input.height
verticalAlignment: Text.AlignVCenter
color: root.accent_color
font.pixelSize: root.font_normal_text_size
}
TextField {
id: min_thres_input
text: config.min
anchors {
left: min_thres_label.right
leftMargin: 5
top: max_thres_label.bottom
topMargin: 20
}
color: root.accent_color
font.pixelSize: root.font_normal_text_size
}
Text {
id: alert_thres_label
text: "Alert Percentage: "
anchors {
topMargin: 20
top: min_thres_label.bottom
left: parent.left
}
height: alert_thres_input.height
verticalAlignment: Text.AlignVCenter
color: root.accent_color
font.pixelSize: root.font_normal_text_size
}
TextField {
id: alert_thres_input
text: config.alert
anchors {
left: alert_thres_label.right
leftMargin: 5
top: min_thres_label.bottom
topMargin: 20
}
color: root.accent_color
font.pixelSize: root.font_normal_text_size
}
Text {
id: settings_error_label
property string settings_error_text: ""
text: "Error: " + settings_error_text
visible: settings_error_text === "" ? false : true
anchors {
topMargin: 20
top: alert_thres_label.bottom
left: parent.left
horizontalCenter: parent.horizontalCenter
}
horizontalAlignment: Text.AlignHCenter
color: root.danger_color
font.pixelSize: root.font_normal_text_size
}
Rectangle {
id: save_config
anchors {
topMargin: 10
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
color: "#F0121212"
width: parent.width / 2.5
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);
console.log("Parsed values:", max, min, alert);
if(max < min) {
settings_error_label.settings_error_text = "Max cannot be less than min";
return;
}
if (alert > 100 || alert < 0) {
settings_error_label.settings_error_text = "Alert must be between 0 to 100";
return;
}
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
console.log("Alert effective reading is: " + alert_effective_reading + " for value: " + alert);
listener.setAlert(alert_effective_reading);
config.saveConfig(max_thres_input.text, min_thres_input.text, alert_thres_input.text);
settings_popup.close();
}
onReleased: {
save_config.color = settings_rect_pressed_prev
}
}
}
}
}
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
width: parent.width / 2.5
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
}
Text {
id: error_text
property string error_string: ""
text: "Error: " + error_string
visible: error_string === "" ? false : true;
anchors {
top: info_percent_text.bottom
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
onConfigData: (max, min, alert) => {
console.log("Got config data as: ", max, min, alert);
}
}
UDPListener {
id: listener
onShowError: (err) => {
error_text.error_string = err;
}
onDistanceChanged: {
console.log("Distance changed");
root.distance = listener.distance;
error_text.error_string = "";
console.log("Distance count: ", root.distance_count);
if(root.distance_count >= 5) {
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;
}
console.log("Current reading is: " + current_reading)
console.log("Normalized height is: " + normalized_height)
water_level.current_level = normalized_height;
water_level.percentage_filled = percentage_filled;
}
}
onConnected: {
error_text.error_string = "";
console.log("Connected");
status_indicator.color = "#F05F5F0F"
status_label.text = "Waiting for more values";
}
onDisconnected: {
error_text.error_string = "Disconnected";
console.log("Disconnected");
status_indicator.color = "#F05F5F0F"
status_label.text = "Disconnected";
}
onDistanceReceived: (distance, time) => {
if(root.distance_count >= 1)
dataBlink();
if(root.distance_count == 0) {
let current_min_reading = Math.max(0, (parseFloat(min_thres_input.text)));
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
let alert = parseFloat(alert_thres_input.text);
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
console.log("Alert effective reading is: " + alert_effective_reading + " for value: " + alert);
listener.setAlert(alert_effective_reading);
}
distance_count += 1;
}
}
onClosing:(close)=> {
if(Qt.platform.os === "android"){
close.accepted = false
} else {
close.accepted = true
}
}
function dataBlink() {
let old_color = status_indicator.color;
let new_color = "#F04FAF5F";
status_indicator.color = new_color;
status_indicator.old_color = old_color;
status_indicator_timer.start();
}
}

14
NotificationManager.java Normal file
View file

@ -0,0 +1,14 @@
package org.qtproject.example.androidnotifier;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.BitmapFactory;
import android.app.NotificationChannel;
public class NotificationManager {
}

BIN
WaterTank1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

View file

@ -0,0 +1,68 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.qtproject.example.appwaterLevelSensor"
android:installLocation="auto"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<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="appwaterLevelSensor"
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:fullBackupOnly="false">
<service android:name=".WaterLevelService"
android:foregroundServiceType="specialUse"
android:exported="true">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Monitoring water levels via local network UDP packets for real-time sensor alerts." />
<meta-data android:name="android.app.lib_name" android:value="service"/>
<meta-data android:name="android.app.background_running" android:value="true"/>
</service>
<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="appwaterLevelSensor"
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="appwaterLevelSensor" />
<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>

83
android/build.gradle Normal file
View file

@ -0,0 +1,83 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
//noinspection AndroidGradlePluginVersion
classpath 'com.android.tools.build:gradle:8.8.0'
}
}
repositories {
google()
mavenCentral()
}
apply plugin: qtGradlePluginType
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
//noinspection GradleDependency
implementation 'androidx.core:core:1.16.0'
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
* - qtGradlePluginType - whether to build an app or a library
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
namespace = androidPackageName
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
ndkVersion = androidNdkVersion
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qtAndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError = false
}
aaptOptions {
// Do not compress Qt binary resources file
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion qtMinSdkVersion
targetSdkVersion qtTargetSdkVersion
ndk.abiFilters = qtTargetAbiList.split(",")
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -0,0 +1,17 @@
package org.qtproject.example.appwaterLevelSensor;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class WaterLevelReceiver extends BroadcastReceiver {
public native static void updateWaterLevel(byte[] data);
@Override
public void onReceive(Context context, Intent intent) {
if ("com.example.WATER_LEVEL_UPDATE".equals(intent.getAction())) {
byte[] data = intent.getByteArrayExtra("level_data");
updateWaterLevel(data);
}
}
}

View file

@ -0,0 +1,210 @@
package org.qtproject.example.appwaterLevelSensor;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.BitmapFactory;
import android.app.NotificationChannel;
import org.qtproject.qt.android.bindings.QtService;
import android.util.Log;
import android.content.Intent;
import java.lang.Runnable;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.*;
import org.qtproject.qt.android.bindings.QtActivity;
import android.app.Service;
import java.time.Instant;
import java.time.Duration;
public class WaterLevelService extends Service {
private static float alert_thres_distance = 1000000;
public class UDPCommandClient implements Runnable {
private DatagramSocket socket_cmd_recv;
private DatagramSocket socket_cmd_send;
@Override
public void run() {
Log.d("UDP Service", "Command thread initialising");
try {
socket_cmd_recv = new DatagramSocket(8178, InetAddress.getByName("localhost"));
socket_cmd_send = new DatagramSocket(8179);
Log.d("UDP Service", "Created command thread sockets");
while (!Thread.currentThread().isInterrupted()) {
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket_cmd_recv.receive(packet);
String str = new String(buffer, StandardCharsets.UTF_8);
if(str.startsWith("<SET NAME=ALERT VALUE=")) {
str = str.replace("<SET NAME=ALERT VALUE=", "");
str = str.replace("=", "");
str = str.replace(">", "");
alert_thres_distance = Float.parseFloat(str);
}
Log.d("UDP Service ", "Received command packet " + str);
}
}
catch (IOException e) {
Log.e("UDP Service", "Socket Error", e);
}
}
}
public class UDPClient implements Runnable {
private DatagramSocket socket_recv;
private DatagramSocket socket_send;
private Instant prevAlertTimestamp;
private boolean started = false;
@Override
public void run() {
Log.d("UDP Service", "Thread initialising");
android.net.wifi.WifiManager wifi = (android.net.wifi.WifiManager) getSystemService(Context.WIFI_SERVICE);
android.net.wifi.WifiManager.MulticastLock multicastLock = wifi.createMulticastLock("water_level_lock");
android.os.PowerManager pm = (android.os.PowerManager) getSystemService(Context.POWER_SERVICE);
android.os.PowerManager.WakeLock wakeLock = pm.newWakeLock(android.os.PowerManager.PARTIAL_WAKE_LOCK, "WaterLevel:WakeLock");
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
String alertChannelId = "water_alert_channel";
NotificationChannel channel = new NotificationChannel(
alertChannelId,
"Water Level Alerts",
NotificationManager.IMPORTANCE_HIGH
);
manager.createNotificationChannel(channel);
try {
multicastLock.acquire();
wakeLock.acquire();
socket_recv = new DatagramSocket(8176);
socket_send = new DatagramSocket(8177);
socket_recv.setBroadcast(true);
Log.d("UDP Service", "Created Sockets");
while (!Thread.currentThread().isInterrupted()) {
byte[] buffer = new byte[4];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket_recv.receive(packet);
float f = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getFloat();
Log.d("UDP Service ", "Received packet: " + f);
Log.d("UDP Service ", "Threshold is: " + alert_thres_distance);
boolean duration_passed = false;
if(!started) {
duration_passed = true;
prevAlertTimestamp = Instant.now();
}
else {
Instant currentTimestamp = Instant.now();
Duration duration = Duration.between(prevAlertTimestamp, currentTimestamp);
if(duration.toMinutes() > 30) {
duration_passed = true;
}
prevAlertTimestamp = currentTimestamp;
}
if(f > alert_thres_distance && alert_thres_distance < 999999 && duration_passed) {
Notification.Builder builder = new Notification.Builder(WaterLevelService.this, alertChannelId)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setContentTitle("Water Level Sensor")
.setContentText("Water Level is below alert threshold")
.setPriority(Notification.PRIORITY_HIGH);
manager.notify(2, builder.build());
Log.d("UDP Service", "Notification sent");
}
DatagramPacket send_packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("localhost"), 8180);
socket_send.send(send_packet);
Log.d("UDP Service ", "Sent packet");
}
}
catch (IOException e) {
Log.e("UDP Service", "Socket Error", e);
} finally {
if (multicastLock.isHeld())
multicastLock.release();
if (wakeLock.isHeld())
wakeLock.release();
if (socket_recv != null)
socket_recv.close();
}
}
}
public void startWaterLevelService() {
Log.d("UDP Service", "Starting the service from Activity");
Intent intent = new Intent(this, WaterLevelService.class);
startForegroundService(intent);
}
public WaterLevelService() {
super();
Log.i("UDP Service", "Class is created");
}
@Override
public android.os.IBinder onBind(android.content.Intent intent) { return null; }
@Override
public void onCreate() {
super.onCreate();
Notification notification = createNotification();
startForeground(1, notification);
Log.i("UDP Service", "Creating Service");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("UDP Service", "Destroying Service");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Notification notification = createNotification();
startForeground(1, notification);
Log.d("UDP Service", "Actually starting the thread");
int ret = super.onStartCommand(intent, flags, startId);
(new Thread(new UDPClient())).start();
(new Thread(new UDPCommandClient())).start();
return START_STICKY;
}
private Notification createNotification() {
String channelId = "water_level_channel";
NotificationManager manager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(
channelId,
"Water Level Service",
NotificationManager.IMPORTANCE_LOW
);
manager.createNotificationChannel(channel);
return new Notification.Builder(this, channelId)
.setContentTitle("Water Level Sensor Running")
.setContentText("Monitoring water level...")
.setSmallIcon(android.R.drawable.ic_menu_info_details)
.build();
}
public static void startQtAndroidService(QtActivity activity) {
Log.d("UDP Service", "Starting the service");
Intent intent = new Intent(activity, WaterLevelService.class);
activity.startForegroundService(intent);
}
}

View file

@ -0,0 +1,19 @@
package org.qtproject.example.appwaterLevelSensor;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import org.qtproject.qt.android.bindings.QtActivity;
public class WaterLevelServiceStarter {
public static void startService(Context context) {
Log.d("UDP Service", "Starting service via QtActivity instance");
Intent intent = new Intent(context, WaterLevelService.class);
context.startForegroundService(intent);
}
}

108
configinterface.cpp Normal file
View file

@ -0,0 +1,108 @@
#include "configinterface.h"
ConfigInterface::ConfigInterface(QObject *parent)
: QObject{parent}
{
auto paths = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
qDebug() << "Got writable path as: " << paths;
std::filesystem::path dir = paths.toStdString();
if(!QDir(dir).exists()) {
QDir().mkpath(paths);
}
std::filesystem::path path = dir / "config.json";
QFile config(path);
if(config.exists()) {
qDebug() << "Loading config file....";
bool ok = config.open(QFile::ReadOnly);
if(!ok) {
qDebug() << config.errorString();
return;
}
auto data = config.readAll();
config.close();
QJsonObject top = QJsonDocument::fromJson(data).object();
QString max, min, alert;
max = top["max_threshold"].toString();
min = top["min_threshold"].toString();
alert = top["alert_threshold"].toString();
setMax(max);
setMin(min);
setAlert(alert);
}
else {
qDebug() << "File does not exist creating one...";
QJsonObject top;
top["max_threshold"] = "125";
top["min_threshold"] = "25";
top["alert_threshold"] = "100";
QByteArray data = QJsonDocument(top).toJson();
bool ok = config.open(QFile::WriteOnly);
if(!ok) {
qDebug() << config.errorString();
return;
}
config.write(data);
config.flush();
config.close();
}
}
void ConfigInterface::saveConfig(QString max, QString min, QString alert)
{
auto paths = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
qDebug() << "Got writable path as: " << paths;
std::filesystem::path path = paths.toStdString();
QFile config(path / "config.json");
qDebug() << "Writing to config file..." << max << min << alert;
QJsonObject top;
top["max_threshold"] = max;
top["min_threshold"] = min;
top["alert_threshold"] = alert;
setMax(max);
setMin(min);
setAlert(alert);
QByteArray data = QJsonDocument(top).toJson();
config.open(QIODevice::WriteOnly);
config.write(data);
config.flush();
config.close();
}
QString ConfigInterface::alert() const
{
return m_alert;
}
void ConfigInterface::setAlert(const QString &newAlert)
{
if (m_alert == newAlert)
return;
m_alert = newAlert;
emit alertChanged();
}
QString ConfigInterface::max() const
{
return m_max;
}
void ConfigInterface::setMax(const QString &newMax)
{
if (m_max == newMax)
return;
m_max = newMax;
emit maxChanged();
}
QString ConfigInterface::min() const
{
return m_min;
}
void ConfigInterface::setMin(const QString &newMin)
{
if (m_min == newMin)
return;
m_min = newMin;
emit minChanged();
}

45
configinterface.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef CONFIGINTERFACE_H
#define CONFIGINTERFACE_H
#include <QObject>
#include <QQmlEngine>
#include <QFile>
#include <QDir>
#include <QStandardPaths>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonDocument>
class ConfigInterface : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString min READ min WRITE setMin NOTIFY minChanged FINAL)
Q_PROPERTY(QString max READ max WRITE setMax NOTIFY maxChanged FINAL)
Q_PROPERTY(QString alert READ alert WRITE setAlert NOTIFY alertChanged FINAL)
public:
explicit ConfigInterface(QObject *parent = nullptr);
QString alert() const;
void setAlert(const QString &newAlert);
QString max() const;
void setMax(const QString &newMax);
QString min() const;
void setMin(const QString &newMin);
signals:
void configData(QString max, QString min, QString alert);
void alertChanged();
void maxChanged();
void minChanged();
public slots:
void saveConfig(QString max, QString min, QString alert);
private:
QString m_alert;
QString m_max;
QString m_min;
};
#endif // CONFIGINTERFACE_H

5
images.qrc Normal file
View file

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>WaterTank1.png</file>
</qresource>
</RCC>

18
main.cpp Normal file
View file

@ -0,0 +1,18 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("waterLevelSensor", "Main");
return app.exec();
}

Some files were not shown because too many files have changed in this diff Show more