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}" }