From c5ea011d7956b8dcddac3579ba652443299371e8 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Fri, 28 Jun 2024 17:03:27 +0200 Subject: [PATCH] Deduplicate code for listening to Item changes Also add error handling. Closes #3654 Signed-off-by: mueller-ma --- .../ItemsControlsProviderService.kt | 39 +++------------- .../java/org/openhab/habdroid/ui/DayDream.kt | 43 ++++-------------- .../org/openhab/habdroid/ui/MainActivity.kt | 45 ++++--------------- .../org/openhab/habdroid/util/ItemClient.kt | 45 +++++++++++++++++++ 4 files changed, 68 insertions(+), 104 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/background/ItemsControlsProviderService.kt b/mobile/src/main/java/org/openhab/habdroid/background/ItemsControlsProviderService.kt index 03772588caf..fae707406a3 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/ItemsControlsProviderService.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/ItemsControlsProviderService.kt @@ -82,41 +82,14 @@ class ItemsControlsProviderService : ControlsProviderService() { .mapNotNull { factory.maybeCreateControl(it.value) } .forEach { control -> send(control) } - val eventSubscription = connection.httpClient.makeSse( - // Support for both the "openhab" and the older "smarthome" root topic by using a wildcard - connection.httpClient.buildUrl("rest/events?topics=*/items/*/statechanged") - ) - - try { - while (isActive) { - try { - val event = JSONObject(eventSubscription.getNextEvent()) - if (event.optString("type") == "ALIVE") { - Log.d(TAG, "Got ALIVE event") - continue - } - val topic = event.getString("topic") - val topicPath = topic.split('/') - // Possible formats: - // - openhab/items//statechanged - // - openhab/items///statechanged - // When an update for a group is sent, there's also one for the individual item. - // Therefore always take the element on index two. - if (topicPath.size !in 4..5) { - throw JSONException("Unexpected topic path $topic") - } - val item = allItems[topicPath[2]] - if (item != null) { - val payload = JSONObject(event.getString("payload")) - val newItem = item.copy(state = payload.getString("value").toParsedState()) - factory.maybeCreateControl(newItem)?.let { control -> send(control) } - } - } catch (e: JSONException) { - Log.e(TAG, "Failed parsing JSON of state change event", e) + ItemClient.listenForItemChange(this, connection, "*") { topicPath, payload -> + val item = allItems[topicPath[2]] + if (item != null) { + val newItem = item.copy(state = payload.getString("value").toParsedState()) + launch { + factory.maybeCreateControl(newItem)?.let { control -> send(control) } } } - } finally { - eventSubscription.cancel() } } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt b/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt index 5d297d7522c..a1a8aec4119 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt @@ -27,10 +27,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import org.json.JSONException -import org.json.JSONObject import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.ConnectionFactory import org.openhab.habdroid.util.HttpClient @@ -74,44 +71,20 @@ class DayDream : DreamService(), CoroutineScope { ConnectionFactory.waitForInitialization() val connection = ConnectionFactory.primaryUsableConnection?.connection ?: return - val eventSubscription = connection.httpClient.makeSse( - // Support for both the "openhab" and the older "smarthome" root topic by using a wildcard - connection.httpClient.buildUrl("rest/events?topics=*/items/$item/command") - ) - textView.text = try { ItemClient.loadItem(connection, item)?.state?.asString.orEmpty() } catch (e: HttpClient.HttpException) { getString(R.string.screensaver_error_loading_item, item) } - try { - while (isActive) { - try { - val event = JSONObject(eventSubscription.getNextEvent()) - if (event.optString("type") == "ALIVE") { - Log.d(TAG, "Got ALIVE event") - continue - } - val topic = event.getString("topic") - val topicPath = topic.split('/') - // Possible formats: - // - openhab/items//statechanged - // - openhab/items///statechanged - // When an update for a group is sent, there's also one for the individual item. - // Therefore always take the element on index two. - if (topicPath.size !in 4..5) { - throw JSONException("Unexpected topic path $topic") - } - val state = JSONObject(event.getString("payload")).getString("value") - Log.d(TAG, "Got state by event: $state") - textView.text = state - } catch (e: JSONException) { - Log.e(TAG, "Failed parsing JSON of state change event", e) - } - } - } finally { - eventSubscription.cancel() + ItemClient.listenForItemChange( + this, + connection, + item + ) { _, payload -> + val state = payload.getString("value") + Log.d(TAG, "Got state by event: $state") + textView.text = state } } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index 367a8b18a1b..309d533e388 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -75,12 +75,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import org.json.JSONException -import org.json.JSONObject import org.openhab.habdroid.BuildConfig import org.openhab.habdroid.R import org.openhab.habdroid.background.BackgroundTasksManager @@ -116,6 +113,7 @@ import org.openhab.habdroid.util.CrashReportingHelper import org.openhab.habdroid.util.HttpClient import org.openhab.habdroid.util.IconBackground import org.openhab.habdroid.util.ImageConversionPolicy +import org.openhab.habdroid.util.ItemClient import org.openhab.habdroid.util.PendingIntent_Immutable import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.ScreenLockMode @@ -1489,39 +1487,14 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } private suspend fun listenUiCommandItem(item: String) { - val connection = connection ?: return - val eventSubscription = connection.httpClient.makeSse( - // Support for both the "openhab" and the older "smarthome" root topic by using a wildcard - connection.httpClient.buildUrl("rest/events?topics=*/items/$item/command") - ) - - try { - while (isActive) { - try { - val event = JSONObject(eventSubscription.getNextEvent()) - if (event.optString("type") == "ALIVE") { - Log.d(TAG, "Got ALIVE event") - continue - } - val topic = event.getString("topic") - val topicPath = topic.split('/') - // Possible formats: - // - openhab/items//statechanged - // - openhab/items///statechanged - // When an update for a group is sent, there's also one for the individual item. - // Therefore always take the element on index two. - if (topicPath.size !in 4..5) { - throw JSONException("Unexpected topic path $topic") - } - val state = JSONObject(event.getString("payload")).getString("value") - Log.d(TAG, "Got state by event: $state") - handleUiCommand(state) - } catch (e: JSONException) { - Log.e(TAG, "Failed parsing JSON of state change event", e) - } - } - } finally { - eventSubscription.cancel() + ItemClient.listenForItemChange( + this, + connection ?: return, + item + ) { _, payload -> + val state = payload.getString("value") + Log.d(TAG, "Got state by event: $state") + handleUiCommand(state) } } diff --git a/mobile/src/main/java/org/openhab/habdroid/util/ItemClient.kt b/mobile/src/main/java/org/openhab/habdroid/util/ItemClient.kt index eccb90222eb..28b9ccfbbac 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/ItemClient.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/ItemClient.kt @@ -18,6 +18,8 @@ import java.io.IOException import java.io.StringReader import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.ParserConfigurationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.isActive import org.json.JSONArray import org.json.JSONException import org.json.JSONObject @@ -100,4 +102,47 @@ object ItemClient { } } } + + suspend fun listenForItemChange( + scope: CoroutineScope, + connection: Connection, + item: String, + callback: (topicPath: List, payload: JSONObject) -> Unit + ) { + val eventSubscription = connection.httpClient.makeSse( + // Support for both the "openhab" and the older "smarthome" root topic by using a wildcard + connection.httpClient.buildUrl("rest/events?topics=*/items/$item/command") + ) + + try { + while (scope.isActive) { + try { + val event = JSONObject(eventSubscription.getNextEvent()) + if (event.optString("type") == "ALIVE") { + Log.d(TAG, "Got ALIVE event for item $item") + continue + } + val topic = event.getString("topic") + val topicPath = topic.split('/') + // Possible formats: + // - openhab/items//statechanged + // - openhab/items///statechanged + // When an update for a group is sent, there's also one for the individual item. + // Therefore always take the element on index two. + if (topicPath.size !in 4..5) { + throw JSONException("Unexpected topic path $topic for item $item") + } + val payload = JSONObject(event.getString("payload")) + Log.d(TAG, "Got payload: $payload") + callback(topicPath, payload) + } catch (e: JSONException) { + Log.e(TAG, "Failed parsing JSON of state change event for item $item", e) + } catch (e: HttpClient.SseFailureException) { + Log.e(TAG, "SSE failure for item $item", e) + } + } + } finally { + eventSubscription.cancel() + } + } }