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/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/sensoreventprovider/SensorEventProvider.kt b/data/src/main/java/com/github/umercodez/sensorspot/data/sensoreventprovider/SensorEventProvider.kt
index c42fd33..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,16 +19,22 @@
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(
val type: String,
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/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..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
@@ -1,266 +1,274 @@
-/*
- * 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()
-
-
- 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(mqttConfig.topic, 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(mqttConfig.topic, 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
+import kotlin.text.split
+
+
+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}/${ if (sensorType.contains(".")) sensorType.split('.').last() else 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()
+ }
+
+}
+
+
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..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
@@ -1,104 +1,116 @@
-/*
- * 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,
+ mqttTopic = settings.topic
+ )
+ }
+ }
+ }
+
+ }
+
+ 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..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
@@ -1,118 +1,122 @@
-/*
- * 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,
+ mqttTopic = state.mqttTopic,
+ checked = state.sensorSelectionState[sensor] == true,
+ onCheckedChange = {
+ onEvent(SensorsScreenEvent.OnSensorItemCheckedChange(sensor, it))
+ }
+ )
+ }
+ item {
+ GpsItem(
+ checked = state.gpsChecked,
+ dedicatedTopics = state.dedicatedTopics,
+ mqttTopic = state.mqttTopic,
+ 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..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
@@ -28,5 +28,7 @@ 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,
+ 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 c04b07a..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
@@ -25,6 +25,8 @@ import com.github.umercodez.sensorspot.ui.SensorSpotTheme
@Composable
fun GpsItem(
checked: Boolean,
+ dedicatedTopics: Boolean = false,
+ mqttTopic: String = "android/sensor",
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
locationPermissionGranted: Boolean = false,
@@ -56,7 +58,7 @@ fun GpsItem(
},
supportingContent = {
Text(
- text = "type = 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 ede9e9f..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
@@ -1,125 +1,132 @@
-/*
- * 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,
+ mqttTopic: String = "android/sensor",
+ 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 subText = if (dedicatedTopics) {
+ "topic = ${mqttTopic}/${ if (sensor.stringType.contains(".")) sensor.stringType.split('.').last() else sensor.stringType}"
+ } else {
+ "type = ${sensor.stringType}"
+ }
+ BasicText(
+ text = subText,
+ 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 = {}
+ )
+ }
+ }
+}
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 b270666..f0c7c44 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
@@ -95,6 +95,17 @@ fun SettingsScreen(
onUpdateClick = { onEvent(SettingsScreenEvent.OnTopicChange(it)) }
)
+ 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 {