diff --git a/CMakeLists.txt b/CMakeLists.txt index 45891ca..ddebcab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/Main.qml b/Main.qml index 3c6831a..e66168e 100644 --- a/Main.qml +++ b/Main.qml @@ -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; diff --git a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java index 8d2c64e..a1becb2 100644 --- a/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java +++ b/android/src/org/qtproject/example/appwaterLevelSensor/WaterLevelService.java @@ -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("", ""); + Log.d("UDP Service ", "Mode " + str); + mode = str.toLowerCase(); + } + else if(str.startsWith("", ""); + Log.d("UDP Service ", "Url " + str); + url = str.toLowerCase(); + } } } catch (IOException e) { @@ -108,14 +128,59 @@ 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]; - try { - DatagramPacket packet = new DatagramPacket(buffer, buffer.length); - socket_recv.receive(packet); - } catch(java.net.SocketTimeoutException e) { + 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; diff --git a/configinterface.cpp b/configinterface.cpp index 549b9f5..dcadda1 100644 --- a/configinterface.cpp +++ b/configinterface.cpp @@ -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(); +} diff --git a/configinterface.h b/configinterface.h index 3cada7a..840cc2c 100644 --- a/configinterface.h +++ b/configinterface.h @@ -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 diff --git a/udplistener.cpp b/udplistener.cpp index bbf4927..5eb7f3a 100644 --- a/udplistener.cpp +++ b/udplistener.cpp @@ -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 = ""; 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 = ""; 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 = ""; + while(m_data.toUtf8().size() != 1024) { + m_data += "="; + } + data3.setData(m_data.toUtf8()); + command_socket->writeDatagram(data3); + command_socket->flush(); + + QString u_data = ""; + 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,24 +262,45 @@ 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()) { - qDebug() << "Socket has pending data: " << socket->pendingDatagramSize(); - char buff[4] = {0x00, 0x00, 0x00, 0x00}; - socket->readDatagram(buff, 4); - auto data= QByteArray(buff,4); - char ch[4]; - for(int i=0;i<4;i++) { - ch[i] = buff[3-i]; + if(mode == "udp") { + qDebug() << "Socket has pending data: " << socket->pendingDatagramSize(); + char buff[4] = {0x00, 0x00, 0x00, 0x00}; + socket->readDatagram(buff, 4); + auto data= QByteArray(buff,4); + char ch[4]; + for(int i=0;i<4;i++) { + ch[i] = buff[3-i]; + } + auto data2 = QByteArray(ch,4); + std::string correct_order = ""; + qDebug() << "Distance received: " << correct_order; + emit datagramReceived(data, data2); + } + else if(mode == "http") { + qDebug() << "Socket data received in http mode ignoring it."; } - auto data2 = QByteArray(ch,4); - std::string correct_order = ""; - qDebug() << "Distance received: " << correct_order; - emit datagramReceived(data, data2); } } diff --git a/udplistener.h b/udplistener.h index b6cebd9..71f9f79 100644 --- a/udplistener.h +++ b/udplistener.h @@ -8,11 +8,21 @@ #include #include #include +#include +#include +#include +#include +#include +#include #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);