This commit is contained in:
TheUnrealDragon 2026-04-13 17:49:08 +05:30
commit 984c5acacc
7 changed files with 376 additions and 37 deletions

View file

@ -16,10 +16,22 @@ qt_add_executable(appwaterLevelSensor
if(ANDROID)
include(FetchContent)
FetchContent_Declare(
android_openssl
DOWNLOAD_EXTRACT_TIMESTAMP true
URL https://github.com/KDAB/android_openssl/archive/refs/heads/master.zip
)
FetchContent_MakeAvailable(android_openssl)
include(${android_openssl_SOURCE_DIR}/android_openssl.cmake)
qt_add_library(service SHARED
servicemain.cpp
service.h service.cpp
)
set_property(TARGET appwaterLevelSensor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/android)
set_property(TARGET service APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
@ -45,6 +57,7 @@ qt_add_qml_module(appwaterLevelSensor
SOURCES service.h
RESOURCES README.md
)
add_android_openssl_libraries(service appwaterLevelSensor)
target_link_libraries(appwaterLevelSensor
PRIVATE Qt6::Quick Qt6::Network service
)

130
Main.qml
View file

@ -11,6 +11,7 @@ Window {
title: qsTr("Hello World")
id: root
property color accent_color: "#F05FAF5F"
property color dark_accent_color: "#FF5F7F5F"
property color danger_color: "#F0AF2F1F"
property color water_color: "#F00F7FAF"
Material.accent: root.accent_color
@ -18,6 +19,7 @@ Window {
property int font_normal_text_size: 16
property double distance: 0
property int distance_count: 0
property string mode: "http"
// visibility: Window.FullScreen
Component.onCompleted: {
if(Qt.platform.os !== "android") {
@ -150,8 +152,8 @@ Window {
border.color: root.accent_color
border.width: 1
}
height: parent.height / 1.5
width: parent.width / 1.5
height: parent.height / 1.2
width: parent.width / 1.1
anchors.centerIn: Overlay.overlay
@ -279,7 +281,95 @@ Window {
}
}
Text {
id: settings_mode_text
anchors {
topMargin: 20
top: flip_bits_box.bottom
left: parent.left
}
font.pointSize: root.font_normal_text_size
color: root.accent_color
text: "Mode: "
}
Row {
id: mode_row
anchors {
left: settings_mode_text.right
right: parent.right
verticalCenter: settings_mode_text.verticalCenter
leftMargin: 10
}
RadioButton {
id: udp_radio_button
checked: config.mode === "udp" ? true : false
text: "UDP"
font.pointSize: root.font_normal_text_size
contentItem: Text {
text: udp_radio_button.text
font: udp_radio_button.font
opacity: enabled ? 1.0 : 0.3
color: udp_radio_button.down ? root.dark_accent_color : root.accent_color
verticalAlignment: Text.AlignVCenter
leftPadding: udp_radio_button.indicator.width + udp_radio_button.spacing
}
onCheckedChanged: {
if(checked) {
root.mode = "udp";
}
}
}
RadioButton {
id: http_radio_button
checked: config.mode === "http" ? true : false
text: "HTTP"
font.pointSize: root.font_normal_text_size
contentItem: Text {
text: http_radio_button.text
font: http_radio_button.font
opacity: enabled ? 1.0 : 0.3
color: http_radio_button.down ? root.dark_accent_color : root.accent_color
verticalAlignment: Text.AlignVCenter
leftPadding: http_radio_button.indicator.width + http_radio_button.spacing
}
onCheckedChanged: {
if(checked) {
root.mode = "http";
}
}
}
}
Text {
id: http_url_label
text: "Url: "
anchors {
topMargin: 20
top: settings_mode_text.bottom
left: parent.left
verticalCenter: http_url_input.verticalCenter
}
height: http_url_input.height
verticalAlignment: Text.AlignVCenter
color: root.accent_color
font.pointSize: root.font_normal_text_size
}
TextField {
id: http_url_input
text: config.url
anchors {
right: parent.right
rightMargin: 20
leftMargin: 20
top: mode_row.bottom
topMargin: 20
}
enabled: root.mode === "http" ? true : false
width: Math.max(parent.width / 2, parent.width - http_url_label.width - anchors.leftMargin)
color: root.accent_color
font.pointSize: root.font_normal_text_size
cursorPosition: 0
}
Text {
id: settings_error_label
@ -288,8 +378,8 @@ Window {
visible: settings_error_text === "" ? false : true
anchors {
topMargin: 20
top: flip_bits_box.bottom
bottom: flip_bits_box.top
top: http_url_label.bottom
bottom: reset_averages.top
bottomMargin: 10
left: parent.left
horizontalCenter: parent.horizontalCenter
@ -387,6 +477,8 @@ Window {
let min = parseFloat(min_thres_input.text);
let alert = parseFloat(alert_thres_input.text);
let flipBits = flip_bits_box.checked;
let mode = root.mode;
let url = http_url_input.text;
console.log("Parsed values:", max, min, alert, flip_bits_box.checked);
if(max < min) {
settings_error_label.settings_error_text = "Max cannot be less than min";
@ -400,8 +492,8 @@ Window {
let effective_max_reading = Math.max(0, (parseFloat(max_thres_input.text)) - current_min_reading);
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
console.log("Alert effective reading is: " + alert_effective_reading + " for value: " + alert);
listener.setConfig(alert_effective_reading, flipBits);
config.saveConfig(max_thres_input.text, min_thres_input.text, alert_thres_input.text, flip_bits_box.checked);
listener.setConfig(alert_effective_reading, flipBits, mode, url);
config.saveConfig(max_thres_input.text, min_thres_input.text, alert_thres_input.text, flip_bits_box.checked, mode, url);
settings_popup.close();
}
onReleased: {
@ -520,11 +612,30 @@ Window {
ConfigInterface {
id: config
onConfigData: (max, min, alert) => {
console.log("Got config data as: ", max, min, alert);
onConfigData: (max, min, alert, mode, url) => {
console.log("Got config data as: ", max, min, alert, mode, url);
}
onModeChanged: {
root.mode = mode;
}
}
Timer {
id: init_timer
running: true
repeat: false
interval: 1000
onTriggered: {
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;
}
}
UDPListener {
id: listener
@ -585,8 +696,7 @@ Window {
let alert = parseFloat(alert_thres_input.text);
let alert_effective_reading = (effective_max_reading * (100 - alert) / 100) + current_min_reading;
console.log("Alert effective reading is: " + alert_effective_reading + " for value: " + alert);
listener.setConfig(alert_effective_reading, flip_bits_box.checked);
listener.setConfig(alert_effective_reading, flip_bits_box.checked, root.mode, http_url_input.text);
}
distance_count += 1;

View file

@ -19,11 +19,18 @@ import org.qtproject.qt.android.bindings.QtActivity;
import android.app.Service;
import java.time.Instant;
import java.time.Duration;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
public class WaterLevelService extends Service {
private static float alert_thres_distance = 1000000;
private boolean bits_flipped = false;
private String mode = "";
private String url = "";
public class UDPCommandClient implements Runnable {
private DatagramSocket socket_cmd_recv;
@ -60,7 +67,20 @@ public class WaterLevelService extends Service {
bits_flipped = true;
}
}
else if(str.startsWith("<SET NAME=MODE VALUE=")) {
str = str.replace("<SET NAME=MODE VALUE=", "");
str = str.replace("=", "");
str = str.replace(">", "");
Log.d("UDP Service ", "Mode " + str);
mode = str.toLowerCase();
}
else if(str.startsWith("<SET NAME=URL VALUE=")) {
str = str.replace("<SET NAME=URL VALUE=", "");
str = str.replace("=", "");
str = str.replace(">", "");
Log.d("UDP Service ", "Url " + str);
url = str.toLowerCase();
}
}
}
catch (IOException e) {
@ -108,16 +128,61 @@ public class WaterLevelService extends Service {
while (!Thread.currentThread().isInterrupted()) {
count++;
if(count == 3) {
Log.i("UDP Service", "Thread is running");
Log.i("UDP Service", "Thread is running with mode: " + mode);
count = 0;
}
byte[] buffer = new byte[4];
byte[] buffer = null;
if(mode.equals("udp")) {
try {
buffer = new byte[4];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket_recv.receive(packet);
} catch(java.net.SocketTimeoutException e) {
continue;
}
}
else if(mode.equals("http")) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
continue;
}
Log.d("UDP Service", "Http String Url: " + url);
URL t_url = new URL(url);
HttpURLConnection con = (HttpURLConnection) t_url.openConnection();
con.setRequestMethod("GET");
con.setConnectTimeout(5000);
con.setReadTimeout(5000);
con.setInstanceFollowRedirects(true);
int status = con.getResponseCode();
Log.d("UDP Service", "Got status from http: " + status);
if(status == 200) {
BufferedReader streamReader = new BufferedReader(new InputStreamReader(con.getInputStream()));
String data = streamReader.readLine();
try {
JSONObject obj = new JSONObject(data);
JSONArray arr = obj.getJSONArray("water_level");
float f = (float ) arr.getDouble(0);
buffer = ByteBuffer.allocate(4).putFloat(f).array();
Log.d("UDP Service", "Got value from http: " + f);
} catch (JSONException e) {
Log.e("UDP Service", "Invalid Json");
}
}
else {
InputStreamReader streamReader = new InputStreamReader(con.getErrorStream());
continue;
}
}
else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
continue;
}
Log.d("UDP Service", "Invalid mode: " + mode);
continue;
}
float f;
if(bits_flipped) {
f = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getFloat();

View file

@ -21,16 +21,20 @@ ConfigInterface::ConfigInterface(QObject *parent)
auto data = config.readAll();
config.close();
QJsonObject top = QJsonDocument::fromJson(data).object();
QString max, min, alert;
QString max, min, alert, mode, url;
bool flipbits;
max = top["max_threshold"].toString();
min = top["min_threshold"].toString();
alert = top["alert_threshold"].toString();
flipbits = top["flip_bits"].toBool();
mode = top["mode"].toString();
url = top["url"].toString();
setMax(max);
setMin(min);
setAlert(alert);
setFlipBits(flipbits);
setMode(mode);
setUrl(url);
}
else {
qDebug() << "File does not exist creating one...";
@ -38,6 +42,9 @@ ConfigInterface::ConfigInterface(QObject *parent)
top["max_threshold"] = "125";
top["min_threshold"] = "25";
top["alert_threshold"] = "100";
top["flip_bits"] = true;
top["mode"] = "http";
top["url"] = "https://unrealdragon.in/server/waterlevel";
QByteArray data = QJsonDocument(top).toJson();
bool ok = config.open(QFile::WriteOnly);
if(!ok) {
@ -47,10 +54,17 @@ ConfigInterface::ConfigInterface(QObject *parent)
config.write(data);
config.flush();
config.close();
setMax("125");
setMin("25");
setAlert("100");
setFlipBits(true);
setMode("http");
setUrl("https://unrealdragon.in/server/waterlevel");
qDebug() << "Config set initial values completed";
}
}
void ConfigInterface::saveConfig(QString max, QString min, QString alert, bool flipbits)
void ConfigInterface::saveConfig(QString max, QString min, QString alert, bool flipbits, QString mode, QString url)
{
auto paths = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
qDebug() << "Got writable path as: " << paths;
@ -62,9 +76,12 @@ void ConfigInterface::saveConfig(QString max, QString min, QString alert, bool f
top["min_threshold"] = min;
top["alert_threshold"] = alert;
top["flip_bits"] = flipbits;
top["mode"] = mode;
top["url"] = url;
setMax(max);
setMin(min);
setAlert(alert);
setMode(mode);
QByteArray data = QJsonDocument(top).toJson();
config.open(QIODevice::WriteOnly);
config.write(data);
@ -123,3 +140,29 @@ void ConfigInterface::setFlipBits(bool newFlipBits)
m_flipBits = newFlipBits;
emit flipBitsChanged();
}
QString ConfigInterface::mode() const
{
return m_mode;
}
void ConfigInterface::setMode(const QString &newMode)
{
if (m_mode == newMode)
return;
m_mode = newMode;
emit modeChanged();
}
QString ConfigInterface::url() const
{
return m_url;
}
void ConfigInterface::setUrl(const QString &newUrl)
{
if (m_url == newUrl)
return;
m_url = newUrl;
emit urlChanged();
}

View file

@ -17,6 +17,8 @@ class ConfigInterface : public QObject
Q_PROPERTY(QString min READ min WRITE setMin NOTIFY minChanged FINAL)
Q_PROPERTY(QString max READ max WRITE setMax NOTIFY maxChanged FINAL)
Q_PROPERTY(QString alert READ alert WRITE setAlert NOTIFY alertChanged FINAL)
Q_PROPERTY(QString mode READ mode WRITE setMode NOTIFY modeChanged FINAL)
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged FINAL)
Q_PROPERTY(bool flipBits READ flipBits WRITE setFlipBits NOTIFY flipBitsChanged FINAL)
public:
explicit ConfigInterface(QObject *parent = nullptr);
@ -32,21 +34,30 @@ public:
bool flipBits() const;
void setFlipBits(bool newFlipBits);
QString mode() const;
void setMode(const QString &newMode);
QString url() const;
void setUrl(const QString &newUrl);
signals:
void configData(QString max, QString min, QString alert, bool flipbits);
void configData(QString max, QString min, QString alert, bool flipbits, QString mode, QString url);
void alertChanged();
void maxChanged();
void minChanged();
void flipBitsChanged();
void modeChanged();
void urlChanged();
public slots:
void saveConfig(QString max, QString min, QString alert, bool flipbits);
void saveConfig(QString max, QString min, QString alert, bool flipbits, QString mode, QString url);
private:
QString m_alert;
QString m_max;
QString m_min;
bool m_flipBits;
QString m_mode;
QString m_url;
};
#endif // CONFIGINTERFACE_H

View file

@ -14,6 +14,7 @@ UDPListener::UDPListener(QObject *parent)
connect(worker, &Worker::datagramReceived, this, &UDPListener::datagramRecieved);
connect(worker, &Worker::socketError, this, &UDPListener::socketError);
connect(this, &UDPListener::startWorker, worker, &Worker::run);
connect(this, &UDPListener::changeWorkerMode, worker, &Worker::changeMode);
thread->start();
emit startWorker();
timer = new QTimer(this);
@ -54,16 +55,18 @@ void UDPListener::setFlipBits(bool flip_bits)
m_flip_bits = flip_bits;
}
void UDPListener::setConfig(float distance, bool flip_bits)
void UDPListener::setConfig(float distance, bool flip_bits, QString mode, QString url)
{
setFlipBits(flip_bits);
if(!command_socket) {
command_socket = new QUdpSocket(this);
bool bind = command_socket->bind(8181);
if(bind) {
QNetworkDatagram data1, data2;
QNetworkDatagram data1, data2, data3, data4;
data1.setDestination(QHostAddress::LocalHost, 8178);
data2.setDestination(QHostAddress::LocalHost, 8178);
data3.setDestination(QHostAddress::LocalHost, 8178);
data4.setDestination(QHostAddress::LocalHost, 8178);
QString s_data = "<SET NAME=ALERT VALUE=" + QString::number(distance) + ">";
while(s_data.toUtf8().size() != 1024) {
@ -72,6 +75,7 @@ void UDPListener::setConfig(float distance, bool flip_bits)
data1.setData(s_data.toUtf8());
command_socket->writeDatagram(data1);
command_socket->flush();
QString b_data = "<SET NAME=BITS_FLIPPED VALUE=" + QString((flip_bits ? "TRUE" : "FALSE")) + ">";
while(b_data.toUtf8().size() != 1024) {
b_data += "=";
@ -79,9 +83,28 @@ void UDPListener::setConfig(float distance, bool flip_bits)
data2.setData(b_data.toUtf8());
command_socket->writeDatagram(data2);
command_socket->flush();
QString m_data = "<SET NAME=MODE VALUE=" + mode + ">";
while(m_data.toUtf8().size() != 1024) {
m_data += "=";
}
data3.setData(m_data.toUtf8());
command_socket->writeDatagram(data3);
command_socket->flush();
QString u_data = "<SET NAME=URL VALUE=" + url + ">";
while(u_data.toUtf8().size() != 1024) {
u_data += "=";
}
data4.setData(u_data.toUtf8());
command_socket->writeDatagram(data4);
command_socket->flush();
command_socket->close();
command_socket->deleteLater();
command_socket = nullptr;
emit changeWorkerMode(mode, url);
}
}
averages.clear();
@ -179,10 +202,45 @@ Worker::Worker(QObject *parent)
}
void Worker::gotHttpReply(QNetworkReply *rep)
{
QByteArray m_data = rep->readAll();
rep->deleteLater();
QJsonObject top = QJsonDocument::fromJson(m_data).object();
float distance = top["water_level"].toArray()[0].toDouble();
QByteArray data, data2;
data = QByteArray::fromRawData((char *)&distance, sizeof(float));
char *buff = new char[4];
char *reader = (char *) &distance;
for(int i=0;i< sizeof(float) ;i++) {
buff[i] = reader[sizeof(float) - 1 - i];
}
data2 = QByteArray::fromRawData(buff, sizeof(float));
emit datagramReceived(data, data2);
}
void Worker::httpTimerTimeout()
{
QNetworkRequest req;
req.setUrl(QUrl("https://unrealdragon.in/server/waterlevel"));
disconnect(manager, nullptr, nullptr, nullptr);
connect(manager, &QNetworkAccessManager::finished, this, &Worker::gotHttpReply);
manager->get(req);
}
void Worker::stop()
{
disconnect(socket, nullptr, nullptr, nullptr);
delete socket;
if(httpTimer) {
disconnect(httpTimer, nullptr, nullptr, nullptr);
delete httpTimer;
}
if(manager) {
disconnect(manager, nullptr, nullptr, nullptr);
delete manager;
}
emit stopped();
}
@ -190,9 +248,13 @@ void Worker::run()
{
using namespace std::chrono_literals;
socket = new QUdpSocket(this);
httpTimer = new QTimer(this);
manager = new QNetworkAccessManager(this);
#ifdef Q_OS_ANDROID
// If it is android use it as a command socket;
bool bind = socket->bind(QHostAddress::Any, 8180);
#else
// Else use it as a receiver;
bool bind = socket->bind(QHostAddress("192.168.1.255"), 8176);
#endif
if(!bind)
@ -200,13 +262,30 @@ void Worker::run()
qDebug() << "UI Worker socket bind failed" << bind << socket->errorString();
emit socketError(socket->errorString());
}
connect(httpTimer, &QTimer::timeout, this, &Worker::httpTimerTimeout);
connect(socket, &QUdpSocket::readyRead, this, &Worker::socketDataReceived);
}
void Worker::changeMode(QString mode, QString url)
{
qDebug() << "Worker changing mode: " << mode << url;
this->mode = mode;
this->url = url;
if(mode == "http") {
httpTimer->setInterval(2000);
httpTimer->setSingleShot(false);
httpTimer->start();
}
else if(mode == "udp") {
httpTimer->stop();
socketDataReceived();
}
}
void Worker::socketDataReceived()
{
if(socket->hasPendingDatagrams()) {
if(mode == "udp") {
qDebug() << "Socket has pending data: " << socket->pendingDatagramSize();
char buff[4] = {0x00, 0x00, 0x00, 0x00};
socket->readDatagram(buff, 4);
@ -220,4 +299,8 @@ void Worker::socketDataReceived()
qDebug() << "Distance received: " << correct_order;
emit datagramReceived(data, data2);
}
else if(mode == "http") {
qDebug() << "Socket data received in http mode ignoring it.";
}
}
}

View file

@ -8,11 +8,21 @@
#include <QQmlEngine>
#include <QTimer>
#include <chrono>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include "moving_container.h"
class Worker: public QObject {
Q_OBJECT
QUdpSocket *socket = nullptr;
QString mode = "";
QString url = "";
QNetworkAccessManager *manager = nullptr;
QTimer *httpTimer = nullptr;
public:
explicit Worker(QObject *parent = nullptr);
signals:
@ -20,8 +30,11 @@ signals:
void socketError(QString error);
void datagramReceived(QByteArray data, QByteArray data2);
public slots:
void gotHttpReply(QNetworkReply *rep);
void httpTimerTimeout();
void stop();
void run();
void changeMode(QString mode, QString url);
private slots:
void socketDataReceived();
};
@ -44,11 +57,12 @@ signals:
void connected();
void disconnected();
void distanceReceived(double dist, QString timestamp);
void changeWorkerMode(QString mode, QString url);
public slots:
void continueDestruction();
void resetAverages();
void setFlipBits(bool flip_bits);
void setConfig(float distance, bool flip_bits);
void setConfig(float distance, bool flip_bits, QString mode, QString url);
void socketError(QString err);
void startService();
void datagramRecieved(QByteArray data, QByteArray data2);