Skip to content

Commit

Permalink
Deduplicate code for listening to Item changes
Browse files Browse the repository at this point in the history
Also add error handling.

Closes openhab#3654

Signed-off-by: mueller-ma <[email protected]>
  • Loading branch information
mueller-ma committed Jun 28, 2024
1 parent 2379297 commit c5ea011
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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/<item>/statechanged
// - openhab/items/<group item>/<item>/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()
}
}

Expand Down
43 changes: 8 additions & 35 deletions mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/<item>/statechanged
// - openhab/items/<group item>/<item>/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
}
}

Expand Down
45 changes: 9 additions & 36 deletions mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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/<item>/statechanged
// - openhab/items/<group item>/<item>/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)
}
}

Expand Down
45 changes: 45 additions & 0 deletions mobile/src/main/java/org/openhab/habdroid/util/ItemClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -100,4 +102,47 @@ object ItemClient {
}
}
}

suspend fun listenForItemChange(
scope: CoroutineScope,
connection: Connection,
item: String,
callback: (topicPath: List<String>, 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/<item>/statechanged
// - openhab/items/<group item>/<item>/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()
}
}
}

0 comments on commit c5ea011

Please sign in to comment.