From bd72f557c09e9a4c5c5c1f69c871e11648addd24 Mon Sep 17 00:00:00 2001
From: Oleg Bilovus <68842946+olegbilovus@users.noreply.github.com>
Date: Sat, 8 Nov 2025 16:35:58 +0100
Subject: [PATCH 1/6] feat: add dedicated topics support for MQTT configuration
---
.../data/repositories/settings/Settings.kt | 37 ++++++++-------
.../settings/SettingsRepositoryImp.kt | 39 ++++++++-------
.../data/sensorpublisher/MqttConfig.kt | 3 ++
.../data/sensorpublisher/SensorPublisher.kt | 11 ++++-
.../ui/screens/settings/SettingsScreen.kt | 47 ++++++++++++-------
.../screens/settings/SettingsScreenEvent.kt | 33 ++++++-------
.../settings/SettingsScreenViewModel.kt | 7 +++
7 files changed, 105 insertions(+), 72 deletions(-)
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/repositories/settings/Settings.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/repositories/settings/Settings.kt
index 03f085d..394269a 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/repositories/settings/Settings.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/repositories/settings/Settings.kt
@@ -1,21 +1,21 @@
-/*
- * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
- * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
- *
- * SensorSpot is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * SensorSpot is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with SensorSpot. If not, see .
- *
- */
+/*
+ * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
+ * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
+ *
+ * SensorSpot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SensorSpot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SensorSpot. If not, see .
+ *
+ */
package com.github.umercodez.sensorspot.data.repositories.settings
@@ -29,6 +29,7 @@ data class Settings(
val brokerPort: Int,
val qos: Int,
val topic: String,
+ val dedicatedTopics: Boolean,
val connectionTimeoutSecs: Int,
val useCredentials: Boolean,
val userName: String,
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/repositories/settings/SettingsRepositoryImp.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/repositories/settings/SettingsRepositoryImp.kt
index f0f2cf0..cfbff21 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/repositories/settings/SettingsRepositoryImp.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/repositories/settings/SettingsRepositoryImp.kt
@@ -1,21 +1,21 @@
-/*
- * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
- * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
- *
- * SensorSpot is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * SensorSpot is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with SensorSpot. If not, see .
- *
- */
+/*
+ * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
+ * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
+ *
+ * SensorSpot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SensorSpot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SensorSpot. If not, see .
+ *
+ */
package com.github.umercodez.sensorspot.data.repositories.settings
import android.content.Context
@@ -53,6 +53,7 @@ class SettingsRepositoryImp(
val USE_CREDENTIALS = booleanPreferencesKey("use_credentials")
val QOS = intPreferencesKey("qos")
val TOPIC = stringPreferencesKey("topic")
+ val DEDICATED_TOPICS = booleanPreferencesKey("dedicated_topics")
val CONNECTION_TIMEOUT_SECS = intPreferencesKey("connection_timeout_secs")
val SENSOR_SAMPLING_RATE = intPreferencesKey("sensor_sampling_rate")
}
@@ -69,6 +70,7 @@ class SettingsRepositoryImp(
useCredentials = pref[Key.USE_CREDENTIALS] ?: MqttConfigDefaults.USE_CREDENTIALS,
qos = pref[Key.QOS] ?: MqttConfigDefaults.QOS,
topic = pref[Key.TOPIC] ?: MqttConfigDefaults.TOPIC,
+ dedicatedTopics = pref[Key.DEDICATED_TOPICS] ?: MqttConfigDefaults.DEDICATED_TOPICS,
connectionTimeoutSecs = pref[Key.CONNECTION_TIMEOUT_SECS]
?: MqttConfigDefaults.CONNECTION_TIMEOUT_SECS,
sensorSamplingRate = pref[Key.SENSOR_SAMPLING_RATE]
@@ -94,6 +96,7 @@ class SettingsRepositoryImp(
pref[Key.USE_CREDENTIALS] = settings.useCredentials
pref[Key.QOS] = settings.qos
pref[Key.TOPIC] = settings.topic
+ pref[Key.DEDICATED_TOPICS] = settings.dedicatedTopics
pref[Key.CONNECTION_TIMEOUT_SECS] = settings.connectionTimeoutSecs
}
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/MqttConfig.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/MqttConfig.kt
index 12c9984..477878a 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/MqttConfig.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/MqttConfig.kt
@@ -26,6 +26,7 @@ object MqttConfigDefaults{
const val BROKER_PORT = 1883
const val QOS = 0
const val TOPIC = "android/sensor"
+ const val DEDICATED_TOPICS = false
const val CONNECTION_TIMEOUT_SECS = 5
const val USE_CREDENTIALS = false
const val USER_NAME = ""
@@ -40,6 +41,7 @@ data class MqttConfig(
val brokerPort: Int = MqttConfigDefaults.BROKER_PORT,
val qos: Int = MqttConfigDefaults.QOS,
val topic: String = MqttConfigDefaults.TOPIC,
+ val dedicatedTopics: Boolean = MqttConfigDefaults.DEDICATED_TOPICS,
val connectionTimeoutSecs: Int = MqttConfigDefaults.CONNECTION_TIMEOUT_SECS,
val useCredentials: Boolean = MqttConfigDefaults.USE_CREDENTIALS,
val userName: String = MqttConfigDefaults.USER_NAME,
@@ -54,6 +56,7 @@ data class MqttConfig(
brokerPort = settings.brokerPort,
qos = settings.qos,
topic = settings.topic,
+ dedicatedTopics = settings.dedicatedTopics,
connectionTimeoutSecs = settings.connectionTimeoutSecs,
useCredentials = settings.useCredentials,
userName = settings.userName,
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
index bc9ee81..30df098 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
@@ -82,6 +82,13 @@ class SensorPublisher(
val mqttConnectionState = _mqttConnectionState.asSharedFlow()
+ private fun getTopic(mqttConfig: MqttConfig, sensorType: String ) : String {
+ return if(mqttConfig.dedicatedTopics){
+ "${mqttConfig.topic}/$sensorType"
+ } else {
+ mqttConfig.topic
+ }
+ }
suspend fun connectAndPublish(mqttConfig: MqttConfig) = withContext(ioDispatcher){
scope.launch {
@@ -93,7 +100,7 @@ class SensorPublisher(
val message = MqttMessage(sensorEvent.toJson().toByteArray()).apply {
qos = mqttConfig.qos
}
- mqttAsyncClient?.publish(mqttConfig.topic, message)
+ mqttAsyncClient?.publish(getTopic(mqttConfig, sensorEvent.type), message)
}
} catch (e: MqttException) {
@@ -111,7 +118,7 @@ class SensorPublisher(
val message = MqttMessage(gpsData.toJson().toByteArray()).apply {
qos = mqttConfig.qos
}
- mqttAsyncClient?.publish(mqttConfig.topic, message)
+ mqttAsyncClient?.publish(getTopic(mqttConfig, gpsData.type), message)
}
} catch (e: MqttException) {
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreen.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreen.kt
index cf00999..499b131 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreen.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreen.kt
@@ -1,21 +1,21 @@
-/*
- * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
- * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
- *
- * SensorSpot is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * SensorSpot is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with SensorSpot. If not, see .
- *
- */
+/*
+ * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
+ * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
+ *
+ * SensorSpot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SensorSpot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SensorSpot. If not, see .
+ *
+ */
package com.github.umercodez.sensorspot.ui.screens.settings
import androidx.compose.animation.AnimatedVisibility
@@ -95,6 +95,17 @@ fun SettingsScreen(
onUpdateClick = { onEvent(SettingsScreenEvent.OnBrokerPortChange(it.toInt())) },
)
+ ListItem(
+ headlineContent = { Text("Dedicated topics") },
+ supportingContent = { Text("use different topic for each sensor") },
+ trailingContent = {
+ Switch(
+ checked = state.mqttConfig.dedicatedTopics,
+ onCheckedChange = { onEvent(SettingsScreenEvent.OnDedicatedTopicsChange(it)) }
+ )
+ }
+ )
+
ListItem(
headlineContent = { Text("Qos") },
trailingContent = {
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreenEvent.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreenEvent.kt
index 08cf96c..936afad 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreenEvent.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreenEvent.kt
@@ -16,20 +16,21 @@
* along with SensorSpot. If not, see .
*
*/
-package com.github.umercodez.sensorspot.ui.screens.settings
-
-sealed interface SettingsScreenEvent {
- data class OnBrokerAddressChange(val brokerIp: String) : SettingsScreenEvent
- data class OnBrokerPortChange(val brokerPort: Int) : SettingsScreenEvent
- data class OnTopicChange(val topic: String) : SettingsScreenEvent
- data class OnQosChange(val qos: Int) : SettingsScreenEvent
- data class OnUseSSLChange(val useSSL: Boolean) : SettingsScreenEvent
- data class OnUseWebsocketChange(val useWebsocket: Boolean) : SettingsScreenEvent
- data class OnUseCredentialsChange(val useCredentials: Boolean) : SettingsScreenEvent
- data class OnConnectionTimeoutChange(val connectionTimeout: Int) : SettingsScreenEvent
- data class OnPasswordChange(val password: String) : SettingsScreenEvent
- data class OnUserNameChange(val userName: String) : SettingsScreenEvent
- data class OnSensorSamplingRateChange(val sensorSamplingRate: Int) : SettingsScreenEvent
-
- object OnBackClick: SettingsScreenEvent
+package com.github.umercodez.sensorspot.ui.screens.settings
+
+sealed interface SettingsScreenEvent {
+ data class OnBrokerAddressChange(val brokerIp: String) : SettingsScreenEvent
+ data class OnBrokerPortChange(val brokerPort: Int) : SettingsScreenEvent
+ data class OnTopicChange(val topic: String) : SettingsScreenEvent
+ data class OnDedicatedTopicsChange(val dedicatedTopics: Boolean) : SettingsScreenEvent
+ data class OnQosChange(val qos: Int) : SettingsScreenEvent
+ data class OnUseSSLChange(val useSSL: Boolean) : SettingsScreenEvent
+ data class OnUseWebsocketChange(val useWebsocket: Boolean) : SettingsScreenEvent
+ data class OnUseCredentialsChange(val useCredentials: Boolean) : SettingsScreenEvent
+ data class OnConnectionTimeoutChange(val connectionTimeout: Int) : SettingsScreenEvent
+ data class OnPasswordChange(val password: String) : SettingsScreenEvent
+ data class OnUserNameChange(val userName: String) : SettingsScreenEvent
+ data class OnSensorSamplingRateChange(val sensorSamplingRate: Int) : SettingsScreenEvent
+
+ object OnBackClick: SettingsScreenEvent
}
\ No newline at end of file
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreenViewModel.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreenViewModel.kt
index 9bc5a62..8c312d8 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreenViewModel.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/settings/SettingsScreenViewModel.kt
@@ -86,6 +86,13 @@ class SettingsScreenViewModel @Inject constructor(
}
}
}
+ is SettingsScreenEvent.OnDedicatedTopicsChange -> {
+ viewModelScope.launch {
+ settingsRepository.updateSettings {
+ it.copy(dedicatedTopics = event.dedicatedTopics)
+ }
+ }
+ }
is SettingsScreenEvent.OnUseCredentialsChange -> {
viewModelScope.launch {
settingsRepository.updateSettings {
From 6e7764f7f009c8f350227bfc3befc5c3ef66e145 Mon Sep 17 00:00:00 2001
From: Oleg Bilovus <68842946+olegbilovus@users.noreply.github.com>
Date: Mon, 17 Nov 2025 17:46:42 +0100
Subject: [PATCH 2/6] fix: do not send "type" in the message if Dedicated Topic
is enabled
---
.../data/gpsdataprovider/GpsDataProvider.kt | 17 +-
.../SensorEventProvider.kt | 11 +-
.../data/sensorpublisher/SensorPublisher.kt | 546 +++++++++---------
3 files changed, 298 insertions(+), 276 deletions(-)
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/gpsdataprovider/GpsDataProvider.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/gpsdataprovider/GpsDataProvider.kt
index a9524ae..5a3a858 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/gpsdataprovider/GpsDataProvider.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/gpsdataprovider/GpsDataProvider.kt
@@ -22,11 +22,14 @@ package com.github.umercodez.sensorspot.data.gpsdataprovider
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
+import org.json.JSONObject
private val jsonConf = Json {
encodeDefaults = true
}
+
+
@Serializable
data class GpsData(
val type: String = "android.gps",
@@ -38,7 +41,19 @@ data class GpsData(
val time: Long,
val bearing: Float
){
- fun toJson() = jsonConf.encodeToString(this)
+ fun toJson(includeType: Boolean = true) :String {
+ val json = mapOf(
+ "type" to if (includeType) type else null,
+ "latitude" to latitude,
+ "longitude" to longitude,
+ "altitude" to altitude,
+ "accuracy" to accuracy,
+ "speed" to speed,
+ "time" to time,
+ "bearing" to bearing
+ ).filterValues { it != null }
+ return JSONObject(json).toString()
+ }
}
interface GpsDataProvider {
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt
index c42fd33..7051b34 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt
@@ -20,7 +20,7 @@ package com.github.umercodez.sensorspot.data.sensoreventprovider
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.Serializable
-import kotlinx.serialization.json.Json
+import org.json.JSONObject
@Serializable
data class SensorEvent(
@@ -28,7 +28,14 @@ data class SensorEvent(
val values: List,
val timestamp: Long
){
- fun toJson() = Json.encodeToString(this)
+ fun toJson(includeType: Boolean = true): String {
+ val json = mapOf(
+ "type" to if (includeType) type else null,
+ "values" to values,
+ "timestamp" to timestamp
+ ).filterValues { it != null }
+ return JSONObject(json).toString()
+ }
}
interface SensorEventProvider {
val events: Flow
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
index 30df098..5cdbd05 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
@@ -1,273 +1,273 @@
-/*
- * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
- * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
- *
- * SensorSpot is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * SensorSpot is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with SensorSpot. If not, see .
- *
- */
-package com.github.umercodez.sensorspot.data.sensorpublisher
-
-import android.util.Log
-import com.github.umercodez.sensorspot.data.clock.Clock
-import com.github.umercodez.sensorspot.data.clock.ElapsedTime
-import com.github.umercodez.sensorspot.data.gpsdataprovider.GpsDataProvider
-import com.github.umercodez.sensorspot.data.sensoreventprovider.SensorEventProvider
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import org.eclipse.paho.mqttv5.client.IMqttToken
-import org.eclipse.paho.mqttv5.client.MqttActionListener
-import org.eclipse.paho.mqttv5.client.MqttAsyncClient
-import org.eclipse.paho.mqttv5.client.MqttCallback
-import org.eclipse.paho.mqttv5.client.MqttConnectionOptions
-import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse
-import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence
-import org.eclipse.paho.mqttv5.common.MqttException
-import org.eclipse.paho.mqttv5.common.MqttMessage
-import org.eclipse.paho.mqttv5.common.packet.MqttProperties
-import java.net.SocketTimeoutException
-import java.util.UUID
-
-
-sealed interface MqttConnectionState{
- data object Connecting: MqttConnectionState
- data object Connected: MqttConnectionState
- data object Disconnected: MqttConnectionState
- data class ConnectionError(val exception: Throwable? = null): MqttConnectionState
- data object ConnectionTimeout: MqttConnectionState
-}
-
-class SensorPublisher(
- private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
- private val scope: CoroutineScope,
- private val sensorEventProvider: SensorEventProvider,
- private val gpsDataProvider: GpsDataProvider
-) : MqttCallback {
-
- private val tag = "SensorPublisher"
-
-
- private var mqttAsyncClient: MqttAsyncClient? = null
- private var memoryPersistence: MemoryPersistence? = null
- private var connectionOptions: MqttConnectionOptions? = null
-
- private val clock = Clock()
- val elapsedTime : SharedFlow get() = clock.time
- var sensorSamplingRate: Int = 200000
-
- var sensorIntTypes: List
- set(sensorTypes) {
- sensorEventProvider.stopProvidingEvents()
- sensorEventProvider.provideEventsFor(sensorTypes, sensorSamplingRate)
- }
- get() = sensorIntTypes
-
- private val _mqttConnectionState = MutableSharedFlow(replay = 1)
- val mqttConnectionState = _mqttConnectionState.asSharedFlow()
-
-
- private fun getTopic(mqttConfig: MqttConfig, sensorType: String ) : String {
- return if(mqttConfig.dedicatedTopics){
- "${mqttConfig.topic}/$sensorType"
- } else {
- mqttConfig.topic
- }
- }
- suspend fun connectAndPublish(mqttConfig: MqttConfig) = withContext(ioDispatcher){
-
- scope.launch {
- sensorEventProvider.events.collect{ sensorEvent ->
-
- try {
-
- if(mqttAsyncClient?.isConnected == true) {
- val message = MqttMessage(sensorEvent.toJson().toByteArray()).apply {
- qos = mqttConfig.qos
- }
- mqttAsyncClient?.publish(getTopic(mqttConfig, sensorEvent.type), message)
- }
-
- } catch (e: MqttException) {
- e.printStackTrace()
- }
- }
- }
-
- scope.launch {
- gpsDataProvider.gpsData.collect { gpsData ->
-
- try {
-
- if(mqttAsyncClient?.isConnected == true) {
- val message = MqttMessage(gpsData.toJson().toByteArray()).apply {
- qos = mqttConfig.qos
- }
- mqttAsyncClient?.publish(getTopic(mqttConfig, gpsData.type), message)
- }
-
- } catch (e: MqttException) {
- e.printStackTrace()
- }
-
- }
- }
-
- _mqttConnectionState.emit(MqttConnectionState.Connecting)
-
- val broker = if (mqttConfig.useSSL) {
- if (mqttConfig.useWebsocket)
- "wss://${mqttConfig.brokerAddress}:${mqttConfig.brokerPort}"
- else
- "ssl://${mqttConfig.brokerAddress}:${mqttConfig.brokerPort}"
- } else {
- if (mqttConfig.useWebsocket)
- "ws://${mqttConfig.brokerAddress}:${mqttConfig.brokerPort}"
- else
- "tcp://${mqttConfig.brokerAddress}:${mqttConfig.brokerPort}"
- }
- memoryPersistence = MemoryPersistence()
- connectionOptions = MqttConnectionOptions()
-
- try {
-
- mqttAsyncClient = MqttAsyncClient(broker, UUID.randomUUID().toString(), memoryPersistence)
-
-
- connectionOptions?.apply {
- isAutomaticReconnect = false
- isCleanStart = false
- connectionTimeout = mqttConfig.connectionTimeoutSecs
-
-
- if(mqttConfig.useCredentials){
- userName = mqttConfig.userName
- password = mqttConfig.password.toByteArray()
- }
-
- }
-
- mqttAsyncClient?.setCallback(this@SensorPublisher)
- mqttAsyncClient?.connect(connectionOptions,null,object: MqttActionListener {
- override fun onSuccess(asyncActionToken: IMqttToken?) {
- println(asyncActionToken)
- }
-
- override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
-
- if(exception is SocketTimeoutException) {
- scope.launch {
- _mqttConnectionState.emit(MqttConnectionState.ConnectionTimeout)
- }
- }
-
- else {
- scope.launch {
- _mqttConnectionState.emit(MqttConnectionState.ConnectionError(exception))
- }
- }
- exception?.printStackTrace()
-
- }
-
- })
-
- } catch (e: MqttException) {
- e.printStackTrace()
- scope.launch {
- _mqttConnectionState.emit(MqttConnectionState.ConnectionError(e))
- }
- }
-
- }
-
- override fun connectComplete(reconnect: Boolean, serverURI: String?) {
- Log.d(tag, "connectComplete()")
- clock.start()
- scope.launch {
- _mqttConnectionState.emit(MqttConnectionState.Connected)
- }
- sensorEventProvider.provideEventsFor(sensorIntTypes,sensorSamplingRate)
- }
-
- override fun disconnected(disconnectResponse: MqttDisconnectResponse?) {
- Log.d(tag, "disconnected()")
- clock.reset()
- scope.launch {
- _mqttConnectionState.emit(MqttConnectionState.Disconnected)
- }
- sensorEventProvider.stopProvidingEvents()
- disconnectResponse?.reasonString?.also { println(it) }
- }
-
- override fun mqttErrorOccurred(exception: MqttException?) {
- Log.d(tag, "mqttErrorOccurred()")
- clock.reset()
- scope.launch {
- _mqttConnectionState.emit(MqttConnectionState.ConnectionError(exception))
- }
- exception?.printStackTrace()
- }
-
- override fun messageArrived(topic: String?, message: MqttMessage?) {
-
- }
-
- override fun deliveryComplete(token: IMqttToken?) {
-
- }
-
- override fun authPacketArrived(reasonCode: Int, properties: MqttProperties?) {
-
- }
-
- suspend fun disconnect() : Unit = withContext(ioDispatcher) {
- try {
-
- mqttAsyncClient?.disconnect()
- mqttAsyncClient?.close()
- _mqttConnectionState.emit(MqttConnectionState.Disconnected)
- sensorEventProvider.stopProvidingEvents()
- gpsDataProvider.stopProvidingGpsData()
- clock.reset()
-
- } catch (e: Exception) {
- e.printStackTrace()
- _mqttConnectionState.emit(MqttConnectionState.ConnectionError(e))
- }
- }
-
- fun provideGpsData(){
- gpsDataProvider.startProvidingGpsData()
- }
-
- fun stopProvidingGpsData(){
- gpsDataProvider.stopProvidingGpsData()
- }
-
- fun cleanUp(){
- sensorEventProvider.stopProvidingEvents()
- sensorEventProvider.cleanUp()
-
- gpsDataProvider.startProvidingGpsData()
- gpsDataProvider.cleanUp()
- }
-
-}
-
-
+/*
+ * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
+ * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
+ *
+ * SensorSpot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SensorSpot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SensorSpot. If not, see .
+ *
+ */
+package com.github.umercodez.sensorspot.data.sensorpublisher
+
+import android.util.Log
+import com.github.umercodez.sensorspot.data.clock.Clock
+import com.github.umercodez.sensorspot.data.clock.ElapsedTime
+import com.github.umercodez.sensorspot.data.gpsdataprovider.GpsDataProvider
+import com.github.umercodez.sensorspot.data.sensoreventprovider.SensorEventProvider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.eclipse.paho.mqttv5.client.IMqttToken
+import org.eclipse.paho.mqttv5.client.MqttActionListener
+import org.eclipse.paho.mqttv5.client.MqttAsyncClient
+import org.eclipse.paho.mqttv5.client.MqttCallback
+import org.eclipse.paho.mqttv5.client.MqttConnectionOptions
+import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse
+import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence
+import org.eclipse.paho.mqttv5.common.MqttException
+import org.eclipse.paho.mqttv5.common.MqttMessage
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties
+import java.net.SocketTimeoutException
+import java.util.UUID
+
+
+sealed interface MqttConnectionState{
+ data object Connecting: MqttConnectionState
+ data object Connected: MqttConnectionState
+ data object Disconnected: MqttConnectionState
+ data class ConnectionError(val exception: Throwable? = null): MqttConnectionState
+ data object ConnectionTimeout: MqttConnectionState
+}
+
+class SensorPublisher(
+ private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
+ private val scope: CoroutineScope,
+ private val sensorEventProvider: SensorEventProvider,
+ private val gpsDataProvider: GpsDataProvider
+) : MqttCallback {
+
+ private val tag = "SensorPublisher"
+
+
+ private var mqttAsyncClient: MqttAsyncClient? = null
+ private var memoryPersistence: MemoryPersistence? = null
+ private var connectionOptions: MqttConnectionOptions? = null
+
+ private val clock = Clock()
+ val elapsedTime : SharedFlow get() = clock.time
+ var sensorSamplingRate: Int = 200000
+
+ var sensorIntTypes: List
+ set(sensorTypes) {
+ sensorEventProvider.stopProvidingEvents()
+ sensorEventProvider.provideEventsFor(sensorTypes, sensorSamplingRate)
+ }
+ get() = sensorIntTypes
+
+ private val _mqttConnectionState = MutableSharedFlow(replay = 1)
+ val mqttConnectionState = _mqttConnectionState.asSharedFlow()
+
+
+ private fun getTopic(mqttConfig: MqttConfig, sensorType: String ) : String {
+ return if(mqttConfig.dedicatedTopics){
+ "${mqttConfig.topic}/$sensorType"
+ } else {
+ mqttConfig.topic
+ }
+ }
+ suspend fun connectAndPublish(mqttConfig: MqttConfig) = withContext(ioDispatcher){
+
+ scope.launch {
+ sensorEventProvider.events.collect{ sensorEvent ->
+
+ try {
+
+ if(mqttAsyncClient?.isConnected == true) {
+ val message = MqttMessage(sensorEvent.toJson(!mqttConfig.dedicatedTopics).toByteArray()).apply {
+ qos = mqttConfig.qos
+ }
+ mqttAsyncClient?.publish(getTopic(mqttConfig, sensorEvent.type), message)
+ }
+
+ } catch (e: MqttException) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ scope.launch {
+ gpsDataProvider.gpsData.collect { gpsData ->
+
+ try {
+
+ if(mqttAsyncClient?.isConnected == true) {
+ val message = MqttMessage(gpsData.toJson(!mqttConfig.dedicatedTopics).toByteArray()).apply {
+ qos = mqttConfig.qos
+ }
+ mqttAsyncClient?.publish(getTopic(mqttConfig, gpsData.type), message)
+ }
+
+ } catch (e: MqttException) {
+ e.printStackTrace()
+ }
+
+ }
+ }
+
+ _mqttConnectionState.emit(MqttConnectionState.Connecting)
+
+ val broker = if (mqttConfig.useSSL) {
+ if (mqttConfig.useWebsocket)
+ "wss://${mqttConfig.brokerAddress}:${mqttConfig.brokerPort}"
+ else
+ "ssl://${mqttConfig.brokerAddress}:${mqttConfig.brokerPort}"
+ } else {
+ if (mqttConfig.useWebsocket)
+ "ws://${mqttConfig.brokerAddress}:${mqttConfig.brokerPort}"
+ else
+ "tcp://${mqttConfig.brokerAddress}:${mqttConfig.brokerPort}"
+ }
+ memoryPersistence = MemoryPersistence()
+ connectionOptions = MqttConnectionOptions()
+
+ try {
+
+ mqttAsyncClient = MqttAsyncClient(broker, UUID.randomUUID().toString(), memoryPersistence)
+
+
+ connectionOptions?.apply {
+ isAutomaticReconnect = false
+ isCleanStart = false
+ connectionTimeout = mqttConfig.connectionTimeoutSecs
+
+
+ if(mqttConfig.useCredentials){
+ userName = mqttConfig.userName
+ password = mqttConfig.password.toByteArray()
+ }
+
+ }
+
+ mqttAsyncClient?.setCallback(this@SensorPublisher)
+ mqttAsyncClient?.connect(connectionOptions,null,object: MqttActionListener {
+ override fun onSuccess(asyncActionToken: IMqttToken?) {
+ println(asyncActionToken)
+ }
+
+ override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
+
+ if(exception is SocketTimeoutException) {
+ scope.launch {
+ _mqttConnectionState.emit(MqttConnectionState.ConnectionTimeout)
+ }
+ }
+
+ else {
+ scope.launch {
+ _mqttConnectionState.emit(MqttConnectionState.ConnectionError(exception))
+ }
+ }
+ exception?.printStackTrace()
+
+ }
+
+ })
+
+ } catch (e: MqttException) {
+ e.printStackTrace()
+ scope.launch {
+ _mqttConnectionState.emit(MqttConnectionState.ConnectionError(e))
+ }
+ }
+
+ }
+
+ override fun connectComplete(reconnect: Boolean, serverURI: String?) {
+ Log.d(tag, "connectComplete()")
+ clock.start()
+ scope.launch {
+ _mqttConnectionState.emit(MqttConnectionState.Connected)
+ }
+ sensorEventProvider.provideEventsFor(sensorIntTypes,sensorSamplingRate)
+ }
+
+ override fun disconnected(disconnectResponse: MqttDisconnectResponse?) {
+ Log.d(tag, "disconnected()")
+ clock.reset()
+ scope.launch {
+ _mqttConnectionState.emit(MqttConnectionState.Disconnected)
+ }
+ sensorEventProvider.stopProvidingEvents()
+ disconnectResponse?.reasonString?.also { println(it) }
+ }
+
+ override fun mqttErrorOccurred(exception: MqttException?) {
+ Log.d(tag, "mqttErrorOccurred()")
+ clock.reset()
+ scope.launch {
+ _mqttConnectionState.emit(MqttConnectionState.ConnectionError(exception))
+ }
+ exception?.printStackTrace()
+ }
+
+ override fun messageArrived(topic: String?, message: MqttMessage?) {
+
+ }
+
+ override fun deliveryComplete(token: IMqttToken?) {
+
+ }
+
+ override fun authPacketArrived(reasonCode: Int, properties: MqttProperties?) {
+
+ }
+
+ suspend fun disconnect() : Unit = withContext(ioDispatcher) {
+ try {
+
+ mqttAsyncClient?.disconnect()
+ mqttAsyncClient?.close()
+ _mqttConnectionState.emit(MqttConnectionState.Disconnected)
+ sensorEventProvider.stopProvidingEvents()
+ gpsDataProvider.stopProvidingGpsData()
+ clock.reset()
+
+ } catch (e: Exception) {
+ e.printStackTrace()
+ _mqttConnectionState.emit(MqttConnectionState.ConnectionError(e))
+ }
+ }
+
+ fun provideGpsData(){
+ gpsDataProvider.startProvidingGpsData()
+ }
+
+ fun stopProvidingGpsData(){
+ gpsDataProvider.stopProvidingGpsData()
+ }
+
+ fun cleanUp(){
+ sensorEventProvider.stopProvidingEvents()
+ sensorEventProvider.cleanUp()
+
+ gpsDataProvider.startProvidingGpsData()
+ gpsDataProvider.cleanUp()
+ }
+
+}
+
+
From 9593ba0bda353ddef60547cda89d3fb1a7f6f05d Mon Sep 17 00:00:00 2001
From: Oleg Bilovus <68842946+olegbilovus@users.noreply.github.com>
Date: Mon, 17 Nov 2025 18:31:16 +0100
Subject: [PATCH 3/6] fix: in the sensors list show "topic =
android/sensor/{type}" if Dedicated Topics is enabled. Show "type = {type}"
otherwise
---
.../screens/sensors/SensorScreenViewModel.kt | 215 ++++++++-------
.../ui/screens/sensors/SensorsScreen.kt | 238 +++++++++--------
.../ui/screens/sensors/SensorsScreenState.kt | 3 +-
.../ui/screens/sensors/components/GpsItem.kt | 4 +-
.../screens/sensors/components/SensorItem.kt | 252 +++++++++---------
5 files changed, 364 insertions(+), 348 deletions(-)
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorScreenViewModel.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorScreenViewModel.kt
index 6bc5c50..c3728dc 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorScreenViewModel.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorScreenViewModel.kt
@@ -1,104 +1,113 @@
-/*
- * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
- * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
- *
- * SensorSpot is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * SensorSpot is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with SensorSpot. If not, see .
- *
- */
-package com.github.umercodez.sensorspot.ui.screens.sensors
-
-import android.util.Log
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.github.umercodez.sensorspot.data.repositories.settings.sensor.SensorsRepository
-import com.github.umercodez.sensorspot.data.utils.LocationPermissionUtil
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-
-@HiltViewModel
-class SensorScreenViewModel @Inject constructor(
- private val sensorsRepository: SensorsRepository,
- private val locationPermissionUtil: LocationPermissionUtil
-): ViewModel(){
-
- private val _uiState = MutableStateFlow(SensorsScreenState())
- val uiState = _uiState.asStateFlow()
- val tag = "SensorScreenViewModel"
-
- init {
-
- Log.d(tag, "init() ${hashCode()}")
-
-
- viewModelScope.launch{
- _uiState.value.allSensors.clear()
- _uiState.value.allSensors.addAll(sensorsRepository.getAvailableSensors())
-
- sensorsRepository.getSelectedSensors().forEach { sensor ->
- _uiState.value.sensorSelectionState[sensor] = true
- }
-
- _uiState.update {
- it.copy(locationPermissionGranted = locationPermissionUtil.isLocationPermissionGranted())
- }
-
- }
-
- }
-
- fun onEvent(event: SensorsScreenEvent) {
- when (event) {
- is SensorsScreenEvent.OnSensorItemCheckedChange -> {
- if (event.checked) {
- _uiState.value.sensorSelectionState[event.sensor] = true
- } else {
- _uiState.value.sensorSelectionState[event.sensor] = false
- }
-
- viewModelScope.launch {
- val selectedSensors = _uiState.value.sensorSelectionState.filterValues { it == true }.keys.toList()
- sensorsRepository.saveSelectedSensors(selectedSensors)
- }
- }
-
- is SensorsScreenEvent.OnGpsItemCheckedChange -> {
- viewModelScope.launch {
-
- val gpsChecked = event.checked && locationPermissionUtil.isLocationPermissionGranted()
-
- _uiState.update {
- it.copy(gpsChecked = gpsChecked)
- }
- sensorsRepository.saveGpsSelectionState(gpsChecked)
- }
- }
- is SensorsScreenEvent.OnGrantLocationPermissionClick -> {
-
- }
- }
- }
-
- fun onLocationPermissionStateChange() {
- _uiState.update {
- it.copy(locationPermissionGranted = locationPermissionUtil.isLocationPermissionGranted())
- }
-
- }
-
+/*
+ * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
+ * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
+ *
+ * SensorSpot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SensorSpot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SensorSpot. If not, see .
+ *
+ */
+package com.github.umercodez.sensorspot.ui.screens.sensors
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.github.umercodez.sensorspot.data.repositories.settings.SettingsRepository
+import com.github.umercodez.sensorspot.data.repositories.settings.sensor.SensorsRepository
+import com.github.umercodez.sensorspot.data.utils.LocationPermissionUtil
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+
+@HiltViewModel
+class SensorScreenViewModel @Inject constructor(
+ private val sensorsRepository: SensorsRepository,
+ private val locationPermissionUtil: LocationPermissionUtil,
+ private val settingsRepository: SettingsRepository
+): ViewModel(){
+
+ private val _uiState = MutableStateFlow(SensorsScreenState())
+ val uiState = _uiState.asStateFlow()
+ val tag = "SensorScreenViewModel"
+
+ init {
+
+ Log.d(tag, "init() ${hashCode()}")
+
+
+ viewModelScope.launch{
+ _uiState.value.allSensors.clear()
+ _uiState.value.allSensors.addAll(sensorsRepository.getAvailableSensors())
+
+ sensorsRepository.getSelectedSensors().forEach { sensor ->
+ _uiState.value.sensorSelectionState[sensor] = true
+ }
+
+ _uiState.update {
+ it.copy(locationPermissionGranted = locationPermissionUtil.isLocationPermissionGranted())
+ }
+ }
+
+ viewModelScope.launch {
+ settingsRepository.settings.collect { settings ->
+ _uiState.update {
+ it.copy(dedicatedTopics = settings.dedicatedTopics)
+ }
+ }
+ }
+
+ }
+
+ fun onEvent(event: SensorsScreenEvent) {
+ when (event) {
+ is SensorsScreenEvent.OnSensorItemCheckedChange -> {
+ if (event.checked) {
+ _uiState.value.sensorSelectionState[event.sensor] = true
+ } else {
+ _uiState.value.sensorSelectionState[event.sensor] = false
+ }
+
+ viewModelScope.launch {
+ val selectedSensors = _uiState.value.sensorSelectionState.filterValues { it == true }.keys.toList()
+ sensorsRepository.saveSelectedSensors(selectedSensors)
+ }
+ }
+
+ is SensorsScreenEvent.OnGpsItemCheckedChange -> {
+ viewModelScope.launch {
+
+ val gpsChecked = event.checked && locationPermissionUtil.isLocationPermissionGranted()
+
+ _uiState.update {
+ it.copy(gpsChecked = gpsChecked)
+ }
+ sensorsRepository.saveGpsSelectionState(gpsChecked)
+ }
+ }
+ is SensorsScreenEvent.OnGrantLocationPermissionClick -> {
+
+ }
+ }
+ }
+
+ fun onLocationPermissionStateChange() {
+ _uiState.update {
+ it.copy(locationPermissionGranted = locationPermissionUtil.isLocationPermissionGranted())
+ }
+
+ }
+
}
\ No newline at end of file
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreen.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreen.kt
index e771af0..bf4f984 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreen.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreen.kt
@@ -1,118 +1,120 @@
-/*
- * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
- * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
- *
- * SensorSpot is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * SensorSpot is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with SensorSpot. If not, see .
- *
- */
-package com.github.umercodez.sensorspot.ui.screens.sensors
-
-import android.Manifest
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.toMutableStateList
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.github.umercodez.sensorspot.data.repositories.settings.sensor.fakeSensors
-import com.github.umercodez.sensorspot.ui.SensorSpotTheme
-import com.github.umercodez.sensorspot.ui.screens.sensors.components.GpsItem
-import com.github.umercodez.sensorspot.ui.screens.sensors.components.SensorItem
-import com.google.accompanist.permissions.ExperimentalPermissionsApi
-import com.google.accompanist.permissions.rememberPermissionState
-
-
-@OptIn(ExperimentalPermissionsApi::class)
-@Composable
-fun SensorsScreen(
- viewModel: SensorScreenViewModel = hiltViewModel()
-) {
- val state by viewModel.uiState.collectAsStateWithLifecycle()
- val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
-
- LaunchedEffect(locationPermissionState.status) {
- viewModel.onLocationPermissionStateChange()
- }
-
- SensorsScreen(
- state = state,
- onEvent = { event ->
-
- if(event is SensorsScreenEvent.OnGrantLocationPermissionClick) {
- locationPermissionState.launchPermissionRequest()
- }
-
- viewModel.onEvent(event)
- }
- )
-
-}
-
-
-@Composable
-fun SensorsScreen(
- state: SensorsScreenState,
- onEvent: (SensorsScreenEvent) -> Unit
-) {
- LazyColumn(
- modifier = Modifier.fillMaxWidth(),
- ) {
- items(
- items = state.allSensors,
- key = { it.hashCode() }
- ) { sensor ->
- SensorItem(
- modifier = Modifier.animateItem(),
- sensor = sensor,
- checked = state.sensorSelectionState[sensor] == true,
- onCheckedChange = {
- onEvent(SensorsScreenEvent.OnSensorItemCheckedChange(sensor, it))
- }
- )
- }
- item {
- GpsItem(
- checked = state.gpsChecked,
- onCheckedChange = {
- onEvent(SensorsScreenEvent.OnGpsItemCheckedChange(it))
- },
- locationPermissionGranted = state.locationPermissionGranted,
- onGrantLocationPermissionClick = {
- onEvent(SensorsScreenEvent.OnGrantLocationPermissionClick)
- }
- )
- }
- }
-
-}
-
-
-@Preview(showBackground = true)
-@Composable
-fun SensorsScreenContentPreview() {
- SensorSpotTheme {
- SensorsScreen(
- state = SensorsScreenState(
- allSensors = fakeSensors.toMutableStateList(),
- ),
- onEvent = {}
- )
- }
-}
-
+/*
+ * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
+ * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
+ *
+ * SensorSpot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SensorSpot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SensorSpot. If not, see .
+ *
+ */
+package com.github.umercodez.sensorspot.ui.screens.sensors
+
+import android.Manifest
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.github.umercodez.sensorspot.data.repositories.settings.sensor.fakeSensors
+import com.github.umercodez.sensorspot.ui.SensorSpotTheme
+import com.github.umercodez.sensorspot.ui.screens.sensors.components.GpsItem
+import com.github.umercodez.sensorspot.ui.screens.sensors.components.SensorItem
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.rememberPermissionState
+
+
+@OptIn(ExperimentalPermissionsApi::class)
+@Composable
+fun SensorsScreen(
+ viewModel: SensorScreenViewModel = hiltViewModel()
+) {
+ val state by viewModel.uiState.collectAsStateWithLifecycle()
+ val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
+
+ LaunchedEffect(locationPermissionState.status) {
+ viewModel.onLocationPermissionStateChange()
+ }
+
+ SensorsScreen(
+ state = state,
+ onEvent = { event ->
+
+ if(event is SensorsScreenEvent.OnGrantLocationPermissionClick) {
+ locationPermissionState.launchPermissionRequest()
+ }
+
+ viewModel.onEvent(event)
+ }
+ )
+
+}
+
+
+@Composable
+fun SensorsScreen(
+ state: SensorsScreenState,
+ onEvent: (SensorsScreenEvent) -> Unit
+) {
+ LazyColumn(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ items(
+ items = state.allSensors,
+ key = { it.hashCode() }
+ ) { sensor ->
+ SensorItem(
+ modifier = Modifier.animateItem(),
+ sensor = sensor,
+ dedicatedTopics = state.dedicatedTopics,
+ checked = state.sensorSelectionState[sensor] == true,
+ onCheckedChange = {
+ onEvent(SensorsScreenEvent.OnSensorItemCheckedChange(sensor, it))
+ }
+ )
+ }
+ item {
+ GpsItem(
+ checked = state.gpsChecked,
+ dedicatedTopics = state.dedicatedTopics,
+ onCheckedChange = {
+ onEvent(SensorsScreenEvent.OnGpsItemCheckedChange(it))
+ },
+ locationPermissionGranted = state.locationPermissionGranted,
+ onGrantLocationPermissionClick = {
+ onEvent(SensorsScreenEvent.OnGrantLocationPermissionClick)
+ }
+ )
+ }
+ }
+
+}
+
+
+@Preview(showBackground = true)
+@Composable
+fun SensorsScreenContentPreview() {
+ SensorSpotTheme {
+ SensorsScreen(
+ state = SensorsScreenState(
+ allSensors = fakeSensors.toMutableStateList(),
+ ),
+ onEvent = {}
+ )
+ }
+}
+
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreenState.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreenState.kt
index 80e38c7..471cf0d 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreenState.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreenState.kt
@@ -28,5 +28,6 @@ data class SensorsScreenState(
val allSensors: SnapshotStateList = mutableStateListOf(),
val sensorSelectionState: SnapshotStateMap = mutableStateMapOf(),
val locationPermissionGranted: Boolean = false,
- val gpsChecked: Boolean = false
+ val gpsChecked: Boolean = false,
+ val dedicatedTopics: Boolean = false
)
\ No newline at end of file
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/GpsItem.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/GpsItem.kt
index c04b07a..8b376f8 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/GpsItem.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/GpsItem.kt
@@ -25,6 +25,7 @@ import com.github.umercodez.sensorspot.ui.SensorSpotTheme
@Composable
fun GpsItem(
checked: Boolean,
+ dedicatedTopics: Boolean = false,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
locationPermissionGranted: Boolean = false,
@@ -55,8 +56,9 @@ fun GpsItem(
}
},
supportingContent = {
+ val prefix = if (dedicatedTopics) "topic = android/sensor/" else "type = "
Text(
- text = "type = android.gps",
+ text = prefix + "android.gps",
color = MaterialTheme.colorScheme.onSurface
)
},
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
index ede9e9f..6175f1a 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
@@ -1,125 +1,127 @@
-/*
- * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
- * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
- *
- * SensorSpot is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * SensorSpot is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with SensorSpot. If not, see .
- *
- */
-package com.github.umercodez.sensorspot.ui.screens.sensors.components
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.text.TextAutoSize
-import androidx.compose.material3.ListItem
-import androidx.compose.material3.ListItemDefaults
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Switch
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import com.github.umercodez.sensorspot.data.repositories.settings.sensor.DeviceSensor
-import com.github.umercodez.sensorspot.data.repositories.settings.sensor.accelerometer
-import com.github.umercodez.sensorspot.ui.SensorSpotTheme
-
-@Composable
-fun SensorItem(
- sensor: DeviceSensor,
- modifier: Modifier = Modifier,
- checked: Boolean = false,
- onCheckedChange: (Boolean) -> Unit
-) {
- var showDetails by remember { mutableStateOf(false) }
- Column(
- modifier = modifier
- .fillMaxWidth()
- .padding(5.dp)
- .clip(RoundedCornerShape(16.dp))
- .clickable { showDetails = !showDetails }
- ) {
- ListItem(
- headlineContent = { Text(text = sensor.name) },
- supportingContent = {
- BasicText(
- text = "type = ${sensor.stringType}",
- style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- autoSize = TextAutoSize.StepBased(
- minFontSize = 8.sp,
- maxFontSize = 12.sp,
- )
- )
- },
- trailingContent = {
- Switch(
- checked = checked,
- onCheckedChange = onCheckedChange
- )
- },
- colors = ListItemDefaults.colors(
- containerColor = MaterialTheme.colorScheme.surfaceContainer
- )
- )
- AnimatedVisibility(showDetails) {
- Column(
- modifier = Modifier
- .background(MaterialTheme.colorScheme.surfaceContainer)
- .fillMaxWidth()
- .padding(10.dp),
- horizontalAlignment = Alignment.Start
- ) {
- Text("maxRange = ${sensor.maximumRange}")
- Text("minDelay = ${sensor.minDelay}")
- Text("maxDelay = ${sensor.maxDelay}")
- Text("reportingMode = ${sensor.reportingModeString}")
- Text("wakeUpSensor = ${sensor.isWakeUpSensor}")
- Text("power = ${sensor.power}")
- Text("resolution = ${sensor.resolution}")
- Text("vendor = ${sensor.vendor}")
-
- }
- }
- }
-}
-
-@Preview
-@Composable
-private fun SensorItemPreview() {
- SensorSpotTheme {
- Surface {
- SensorItem(
- sensor = accelerometer.copy(stringType = "android.sensor.accelerometer"),
- checked = true,
- onCheckedChange = {}
- )
- }
- }
-}
+/*
+ * This file is a part of SensorSpot (https://www.github.com/UmerCodez/SensorSpot)
+ * Copyright (C) 2025 Umer Farooq (umerfarooq2383@gmail.com)
+ *
+ * SensorSpot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SensorSpot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SensorSpot. If not, see .
+ *
+ */
+package com.github.umercodez.sensorspot.ui.screens.sensors.components
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.TextAutoSize
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.ListItemDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.github.umercodez.sensorspot.data.repositories.settings.sensor.DeviceSensor
+import com.github.umercodez.sensorspot.data.repositories.settings.sensor.accelerometer
+import com.github.umercodez.sensorspot.ui.SensorSpotTheme
+
+@Composable
+fun SensorItem(
+ sensor: DeviceSensor,
+ dedicatedTopics: Boolean = false,
+ modifier: Modifier = Modifier,
+ checked: Boolean = false,
+ onCheckedChange: (Boolean) -> Unit
+) {
+ var showDetails by remember { mutableStateOf(false) }
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(5.dp)
+ .clip(RoundedCornerShape(16.dp))
+ .clickable { showDetails = !showDetails }
+ ) {
+ ListItem(
+ headlineContent = { Text(text = sensor.name) },
+ supportingContent = {
+ val prefix = if (dedicatedTopics) "topic = android/sensor/" else "type = "
+ BasicText(
+ text = "$prefix${sensor.stringType}",
+ style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ autoSize = TextAutoSize.StepBased(
+ minFontSize = 8.sp,
+ maxFontSize = 12.sp,
+ )
+ )
+ },
+ trailingContent = {
+ Switch(
+ checked = checked,
+ onCheckedChange = onCheckedChange
+ )
+ },
+ colors = ListItemDefaults.colors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainer
+ )
+ )
+ AnimatedVisibility(showDetails) {
+ Column(
+ modifier = Modifier
+ .background(MaterialTheme.colorScheme.surfaceContainer)
+ .fillMaxWidth()
+ .padding(10.dp),
+ horizontalAlignment = Alignment.Start
+ ) {
+ Text("maxRange = ${sensor.maximumRange}")
+ Text("minDelay = ${sensor.minDelay}")
+ Text("maxDelay = ${sensor.maxDelay}")
+ Text("reportingMode = ${sensor.reportingModeString}")
+ Text("wakeUpSensor = ${sensor.isWakeUpSensor}")
+ Text("power = ${sensor.power}")
+ Text("resolution = ${sensor.resolution}")
+ Text("vendor = ${sensor.vendor}")
+
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun SensorItemPreview() {
+ SensorSpotTheme {
+ Surface {
+ SensorItem(
+ sensor = accelerometer.copy(stringType = "android.sensor.accelerometer"),
+ checked = true,
+ onCheckedChange = {}
+ )
+ }
+ }
+}
From fb01fa377f9bc0f46da4bd9d0e81bb17bb4149da Mon Sep 17 00:00:00 2001
From: Oleg Bilovus <68842946+olegbilovus@users.noreply.github.com>
Date: Mon, 17 Nov 2025 19:12:24 +0100
Subject: [PATCH 4/6] fix: remove redundant android/sensor topic prefix. Do not
hardcode mqtt topic, use the one from mqttConfig
---
.../sensorspot/data/sensorpublisher/SensorPublisher.kt | 3 ++-
.../ui/screens/sensors/SensorScreenViewModel.kt | 5 ++++-
.../sensorspot/ui/screens/sensors/SensorsScreen.kt | 2 ++
.../sensorspot/ui/screens/sensors/SensorsScreenState.kt | 3 ++-
.../sensorspot/ui/screens/sensors/components/GpsItem.kt | 4 ++--
.../ui/screens/sensors/components/SensorItem.kt | 9 +++++++--
6 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
index 5cdbd05..5076cee 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
@@ -43,6 +43,7 @@ import org.eclipse.paho.mqttv5.common.MqttMessage
import org.eclipse.paho.mqttv5.common.packet.MqttProperties
import java.net.SocketTimeoutException
import java.util.UUID
+import kotlin.text.split
sealed interface MqttConnectionState{
@@ -84,7 +85,7 @@ class SensorPublisher(
private fun getTopic(mqttConfig: MqttConfig, sensorType: String ) : String {
return if(mqttConfig.dedicatedTopics){
- "${mqttConfig.topic}/$sensorType"
+ "${mqttConfig.topic}/${sensorType.split('.').last()}"
} else {
mqttConfig.topic
}
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorScreenViewModel.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorScreenViewModel.kt
index c3728dc..aae0e62 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorScreenViewModel.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorScreenViewModel.kt
@@ -64,7 +64,10 @@ class SensorScreenViewModel @Inject constructor(
viewModelScope.launch {
settingsRepository.settings.collect { settings ->
_uiState.update {
- it.copy(dedicatedTopics = settings.dedicatedTopics)
+ it.copy(
+ dedicatedTopics = settings.dedicatedTopics,
+ mqttTopic = settings.topic
+ )
}
}
}
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreen.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreen.kt
index bf4f984..ea03059 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreen.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreen.kt
@@ -81,6 +81,7 @@ fun SensorsScreen(
modifier = Modifier.animateItem(),
sensor = sensor,
dedicatedTopics = state.dedicatedTopics,
+ mqttTopic = state.mqttTopic,
checked = state.sensorSelectionState[sensor] == true,
onCheckedChange = {
onEvent(SensorsScreenEvent.OnSensorItemCheckedChange(sensor, it))
@@ -91,6 +92,7 @@ fun SensorsScreen(
GpsItem(
checked = state.gpsChecked,
dedicatedTopics = state.dedicatedTopics,
+ mqttTopic = state.mqttTopic,
onCheckedChange = {
onEvent(SensorsScreenEvent.OnGpsItemCheckedChange(it))
},
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreenState.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreenState.kt
index 471cf0d..ee6d53c 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreenState.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/SensorsScreenState.kt
@@ -29,5 +29,6 @@ data class SensorsScreenState(
val sensorSelectionState: SnapshotStateMap = mutableStateMapOf(),
val locationPermissionGranted: Boolean = false,
val gpsChecked: Boolean = false,
- val dedicatedTopics: Boolean = false
+ val dedicatedTopics: Boolean = false,
+ val mqttTopic: String = "android/sensor",
)
\ No newline at end of file
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/GpsItem.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/GpsItem.kt
index 8b376f8..1880567 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/GpsItem.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/GpsItem.kt
@@ -26,6 +26,7 @@ import com.github.umercodez.sensorspot.ui.SensorSpotTheme
fun GpsItem(
checked: Boolean,
dedicatedTopics: Boolean = false,
+ mqttTopic: String = "android/sensor",
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
locationPermissionGranted: Boolean = false,
@@ -56,9 +57,8 @@ fun GpsItem(
}
},
supportingContent = {
- val prefix = if (dedicatedTopics) "topic = android/sensor/" else "type = "
Text(
- text = prefix + "android.gps",
+ text = if (dedicatedTopics) "topic = ${mqttTopic}/gps" else "type = android.gps",
color = MaterialTheme.colorScheme.onSurface
)
},
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
index 6175f1a..9b04db0 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
@@ -53,6 +53,7 @@ import com.github.umercodez.sensorspot.ui.SensorSpotTheme
fun SensorItem(
sensor: DeviceSensor,
dedicatedTopics: Boolean = false,
+ mqttTopic: String = "android/sensor",
modifier: Modifier = Modifier,
checked: Boolean = false,
onCheckedChange: (Boolean) -> Unit
@@ -68,9 +69,13 @@ fun SensorItem(
ListItem(
headlineContent = { Text(text = sensor.name) },
supportingContent = {
- val prefix = if (dedicatedTopics) "topic = android/sensor/" else "type = "
+ val subText = if (dedicatedTopics) {
+ "topic = ${mqttTopic}/${sensor.stringType.split('.').last()}"
+ } else {
+ "type = ${sensor.stringType}"
+ }
BasicText(
- text = "$prefix${sensor.stringType}",
+ text = subText,
style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
From 654cc555c5c8ae4957c2ffb726295001a9728879 Mon Sep 17 00:00:00 2001
From: UmerCodez
Date: Wed, 19 Nov 2025 20:01:58 +0500
Subject: [PATCH 5/6] refactor: Remove @Serializable from SensorEvent
---
.../sensorspot/data/sensoreventprovider/SensorEventProvider.kt | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt
index 7051b34..a11d16a 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt
@@ -19,10 +19,9 @@
package com.github.umercodez.sensorspot.data.sensoreventprovider
import kotlinx.coroutines.flow.Flow
-import kotlinx.serialization.Serializable
import org.json.JSONObject
-@Serializable
+
data class SensorEvent(
val type: String,
val values: List,
From 9a0800d4d99a917d61e7dc432f77194537254c98 Mon Sep 17 00:00:00 2001
From: UmerCodez
Date: Wed, 19 Nov 2025 20:28:15 +0500
Subject: [PATCH 6/6] refactor: Use full sensor type for topic if no dot is
present
---
.../sensorspot/data/sensorpublisher/SensorPublisher.kt | 2 +-
.../sensorspot/ui/screens/sensors/components/SensorItem.kt | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
index 5076cee..eb86c8b 100644
--- a/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
+++ b/data/src/main/java/com/github/umercodez/sensorspot/data/sensorpublisher/SensorPublisher.kt
@@ -85,7 +85,7 @@ class SensorPublisher(
private fun getTopic(mqttConfig: MqttConfig, sensorType: String ) : String {
return if(mqttConfig.dedicatedTopics){
- "${mqttConfig.topic}/${sensorType.split('.').last()}"
+ "${mqttConfig.topic}/${ if (sensorType.contains(".")) sensorType.split('.').last() else sensorType}"
} else {
mqttConfig.topic
}
diff --git a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
index 9b04db0..c86d6a5 100644
--- a/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
+++ b/ui/src/main/java/com/github/umercodez/sensorspot/ui/screens/sensors/components/SensorItem.kt
@@ -70,7 +70,7 @@ fun SensorItem(
headlineContent = { Text(text = sensor.name) },
supportingContent = {
val subText = if (dedicatedTopics) {
- "topic = ${mqttTopic}/${sensor.stringType.split('.').last()}"
+ "topic = ${mqttTopic}/${ if (sensor.stringType.contains(".")) sensor.stringType.split('.').last() else sensor.stringType}"
} else {
"type = ${sensor.stringType}"
}