feat: listen to websocket

This commit is contained in:
Fritz Heiden 2025-04-01 08:06:09 +02:00
parent 5db2caeaa4
commit 160c0ba7b0
5 changed files with 38 additions and 28 deletions

View File

@ -52,6 +52,7 @@ dependencies {
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
implementation(libs.ktor.client.core) implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio) implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.websockets)
implementation(libs.androidx.camera.core) implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2) implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle) implementation(libs.androidx.camera.lifecycle)
@ -60,7 +61,6 @@ dependencies {
implementation(libs.androidx.camera.mlkit.vision) implementation(libs.androidx.camera.mlkit.vision)
implementation(libs.androidx.camera.extensions) implementation(libs.androidx.camera.extensions)
implementation(libs.material3) implementation(libs.material3)
implementation(libs.opencsv)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)

View File

@ -2,9 +2,7 @@ package com.example.tvcontroller.services
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.example.tvcontroller.R
import com.example.tvcontroller.data.RemoteCommand import com.example.tvcontroller.data.RemoteCommand
import com.opencsv.CSVReader
import org.json.JSONObject import org.json.JSONObject
@ -38,21 +36,6 @@ class ControllerService(
} }
fun loadCommands() { fun loadCommands() {
Log.i("ControllerService", "Loading commands");
var inputStream = context.resources.openRawResource(R.raw.samsung)
var csvReader = CSVReader(inputStream.reader())
csvReader.forEach { nextLine ->
if (nextLine.size < 5) return@forEach
if (nextLine[0] == "functionname") return@forEach
var remoteCommand = RemoteCommand()
remoteCommand.functionName = nextLine[0]
remoteCommand.protocol = "samsung"
remoteCommand.device = nextLine[2]
remoteCommand.subdevice = nextLine[3]
remoteCommand.function = nextLine[4]
samsungCommands[remoteCommand.functionName!!] = remoteCommand
}
Log.i("ControllerService", "Commands loaded: ${samsungCommands.size}")
} }
companion object { companion object {

View File

@ -8,18 +8,25 @@ import io.ktor.client.engine.cio.*
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.plugins.cookies.HttpCookies import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.plugins.websocket.webSocket
import io.ktor.client.request.headers import io.ktor.client.request.headers
import io.ktor.client.request.request import io.ktor.client.request.request
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.coroutines.runBlocking
import org.json.JSONObject import org.json.JSONObject
private const val SHARED_PREFERENCES_NAME = "devices"; private const val SHARED_PREFERENCES_NAME = "devices";
private const val TAG = "DeviceService"
class DeviceService(private val context: Context) { class DeviceService(private val context: Context) {
private var client = HttpClient(CIO) { private var client = HttpClient(CIO) {
install(HttpCookies) install(HttpCookies)
install(WebSockets)
} }
private var serverAddress: String = "" private var serverAddress: String = ""
private var token: String = "" private var token: String = ""
@ -30,7 +37,7 @@ class DeviceService(private val context: Context) {
} }
suspend fun registerIntegration(name: String, code: String) { suspend fun registerIntegration(name: String, code: String) {
Log.i("DeviceService", "Creating integration for $name with code $code at $serverAddress") Log.i(TAG, "Creating integration for $name with code $code at $serverAddress")
val requestJson = JSONObject() val requestJson = JSONObject()
requestJson.put("name", name) requestJson.put("name", name)
requestJson.put("code", code) requestJson.put("code", code)
@ -49,21 +56,21 @@ class DeviceService(private val context: Context) {
val responseJson = JSONObject(body) val responseJson = JSONObject(body)
if (response.status.value != 200) { if (response.status.value != 200) {
val error = responseJson.getString("error") val error = responseJson.getString("error")
Log.e("DeviceService", "Error getting integration: ${response.status.value} $error") Log.e(TAG, "Error getting integration: ${response.status.value} $error")
return return
} }
token = responseJson.getString("token") token = responseJson.getString("token")
deviceId = responseJson.getString("id") deviceId = responseJson.getString("id")
savePreferences() savePreferences()
Log.i("DeviceService", "Response: ${response.status.value} $body") Log.i(TAG, "Response: ${response.status.value} $body")
} catch (e: Exception) { } catch (e: Exception) {
Log.e("DeviceService", "Error registering integration", e) Log.e(TAG, "Error registering integration", e)
} }
} }
suspend fun getIntegration(): Integration? { suspend fun getIntegration(): Integration? {
Log.i("DeviceService", "Getting integration $deviceId at $serverAddress") Log.i(TAG, "Getting integration $deviceId at $serverAddress")
try { try {
val response: HttpResponse = val response: HttpResponse =
client.request("http://$serverAddress/api/integrations/$deviceId") { client.request("http://$serverAddress/api/integrations/$deviceId") {
@ -77,7 +84,7 @@ class DeviceService(private val context: Context) {
val responseJson = JSONObject(body) val responseJson = JSONObject(body)
if (response.status.value != 200) { if (response.status.value != 200) {
val error = responseJson.getString("error") val error = responseJson.getString("error")
Log.e("DeviceService", "Error getting integration: ${response.status.value} $error") Log.e(TAG, "Error getting integration: ${response.status.value} $error")
return null return null
} }
val integration = Integration( val integration = Integration(
@ -86,7 +93,7 @@ class DeviceService(private val context: Context) {
) )
return integration return integration
} catch (e: Exception) { } catch (e: Exception) {
Log.e("DeviceService", "Error getting integration", e) Log.e(TAG, "Error getting integration", e)
} }
return null return null
} }
@ -96,7 +103,7 @@ class DeviceService(private val context: Context) {
serverAddress = sharedPreferences.getString("server_address", "")!! serverAddress = sharedPreferences.getString("server_address", "")!!
token = sharedPreferences.getString("token", "")!! token = sharedPreferences.getString("token", "")!!
deviceId = sharedPreferences.getString("device_id", "")!! deviceId = sharedPreferences.getString("device_id", "")!!
Log.i("DeviceService", "Loaded preferences: $serverAddress $token") Log.i(TAG, "Loaded preferences: $serverAddress $token")
} }
private fun savePreferences() { private fun savePreferences() {
@ -110,6 +117,26 @@ class DeviceService(private val context: Context) {
} }
} }
fun connect() {
Log.i(TAG, "Connecting to websocket at $serverAddress")
runBlocking {
// split server address into host and port
val (host, port) = serverAddress.split(":")
// if no port is specified, assume 80
val portInt = if (port.isEmpty()) 80 else port.toInt()
client.webSocket(method = HttpMethod.Get, host = host, port = portInt, path = "/ws") {
Log.i(TAG, "Listening for incoming websocket messages")
while (true) {
val frame = incoming.receive()
if (frame is Frame.Text) {
val message = frame.readText()
Log.i(TAG, "Received message: $message")
}
}
}
}
}
fun setServerAddress(url: String) { fun setServerAddress(url: String) {
serverAddress = url serverAddress = url
} }

View File

@ -54,6 +54,7 @@ class SettingsViewModel(
deviceService.registerIntegration(deviceName, registrationCode) deviceService.registerIntegration(deviceName, registrationCode)
updateConnectionState() updateConnectionState()
updateDeviceInfo() updateDeviceInfo()
deviceService.connect()
} }
} }

View File

@ -12,7 +12,6 @@ activityCompose = "1.8.0"
composeBom = "2024.04.01" composeBom = "2024.04.01"
material3 = "1.4.0-alpha10" material3 = "1.4.0-alpha10"
navigationCompose = "2.8.4" navigationCompose = "2.8.4"
opencsv = "4.6"
[libraries] [libraries]
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" }
@ -39,8 +38,8 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
opencsv = { module = "com.opencsv:opencsv", version.ref = "opencsv" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }