diff --git a/build.gradle b/build.gradle index be4a15c..c1e115b 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { kotlinVersion = '1.9.24' slf4jVersion = '1.7.30' sonarqubeVersion = '3.2.0' - websocketJettyClientVersion = '11.0.4' + ktorVersion = '2.3.11' } } @@ -110,7 +110,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" // WebSocket client - implementation "org.eclipse.jetty.websocket:websocket-jetty-client:$websocketJettyClientVersion" + implementation "io.ktor:ktor-client-core:$ktorVersion" + implementation "io.ktor:ktor-client-cio:$ktorVersion" + implementation "io.ktor:ktor-client-websockets:$ktorVersion" // Logging implementation "org.slf4j:slf4j-simple:$slf4jVersion" diff --git a/src/main/java/de/stefan_oltmann/smarthome/vallox/ValloxClientImpl.kt b/src/main/java/de/stefan_oltmann/smarthome/vallox/ValloxClientImpl.kt index 9f2c833..771c9d5 100644 --- a/src/main/java/de/stefan_oltmann/smarthome/vallox/ValloxClientImpl.kt +++ b/src/main/java/de/stefan_oltmann/smarthome/vallox/ValloxClientImpl.kt @@ -10,13 +10,15 @@ */ package de.stefan_oltmann.smarthome.vallox -import org.eclipse.jetty.websocket.api.Session -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest -import org.eclipse.jetty.websocket.client.WebSocketClient -import java.net.URI +import io.ktor.client.HttpClient +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.websocket.WebSockets +import io.ktor.client.plugins.websocket.webSocket +import io.ktor.http.HttpMethod +import io.ktor.util.moveToByteArray +import io.ktor.websocket.send +import kotlinx.coroutines.runBlocking import java.nio.ByteBuffer -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit /** * The [ValloxClientImpl] is responsible for socket communication with the vallox ventilation unit @@ -24,40 +26,44 @@ import java.util.concurrent.TimeUnit * @author Björn Brings - Initial contribution * @author Stefan Oltmann - Refactorings and Kotlin conversion */ -class ValloxClientImpl(private val uri: URI) : ValloxClient { +class ValloxClientImpl( + val host: String +) : ValloxClient { - private val webSocketClient = WebSocketClient() + private val httpClient = HttpClient { - private val messageHandler = ValloxMessageHandler + install(HttpTimeout) { + this.requestTimeoutMillis = 5000 + } - init { - webSocketClient.start() + install(WebSockets) } override fun readStatus(): ValloxStatus { - val requestBytes = messageHandler.generateReadRequestBytes() - - return sendBytesToService(requestBytes, ValloxDataMode.READ_TABLES)!! + return sendBytesToService( + ValloxMessageHandler.generateReadRequestBytes(), + ValloxDataMode.READ_TABLES + )!! } override fun turnOn() { sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesOnOff(1), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesOnOff(1), dataMode = ValloxDataMode.WRITE_DATA ) } override fun turnOff() { sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesOnOff(0), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesOnOff(0), dataMode = ValloxDataMode.WRITE_DATA ) } override fun switchProfile(profile: Profile) { sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesSwitchToProfile(profile), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesSwitchToProfile(profile), dataMode = ValloxDataMode.WRITE_DATA ) } @@ -67,7 +73,9 @@ class ValloxClientImpl(private val uri: URI) : ValloxClient { require(fanSpeed in 0..100) sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesExtractFanBalanceBase(fanSpeed), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesExtractFanBalanceBase( + fanSpeed + ), dataMode = ValloxDataMode.WRITE_DATA ) } @@ -77,7 +85,9 @@ class ValloxClientImpl(private val uri: URI) : ValloxClient { require(fanSpeed in 0..100) sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesSupplyFanBalanceBase(fanSpeed), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesSupplyFanBalanceBase( + fanSpeed + ), dataMode = ValloxDataMode.WRITE_DATA ) } @@ -87,7 +97,10 @@ class ValloxClientImpl(private val uri: URI) : ValloxClient { require(fanSpeed in 0..100) sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesFanSpeed(profile, fanSpeed), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesFanSpeed( + profile, + fanSpeed + ), dataMode = ValloxDataMode.WRITE_DATA ) } @@ -97,7 +110,9 @@ class ValloxClientImpl(private val uri: URI) : ValloxClient { require(fanSpeed in 0..100) sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesFireplaceExtractFanSpeed(fanSpeed), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesFireplaceExtractFanSpeed( + fanSpeed + ), dataMode = ValloxDataMode.WRITE_DATA ) } @@ -107,7 +122,9 @@ class ValloxClientImpl(private val uri: URI) : ValloxClient { require(fanSpeed in 0..100) sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesFireplaceSupplyFanSpeed(fanSpeed), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesFireplaceSupplyFanSpeed( + fanSpeed + ), dataMode = ValloxDataMode.WRITE_DATA ) } @@ -117,7 +134,7 @@ class ValloxClientImpl(private val uri: URI) : ValloxClient { require(targetTemperature in 5..25) sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesTargetTemperature( + requestBytes = ValloxMessageHandler.generateWriteRequestBytesTargetTemperature( profile, targetTemperature ), @@ -127,35 +144,41 @@ class ValloxClientImpl(private val uri: URI) : ValloxClient { override fun setBytesBoostTime(boostTimeInMinutes: Int) { sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesBoostTime(boostTimeInMinutes), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesBoostTime( + boostTimeInMinutes + ), dataMode = ValloxDataMode.WRITE_DATA ) } override fun setBoostTimerEnabled(enabled: Boolean) { sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesBoostTimerEnabled(enabled), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesBoostTimerEnabled(enabled), dataMode = ValloxDataMode.WRITE_DATA ) } override fun setFireplaceTime(fireplaceTimeInMinutes: Int) { sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesFireplaceTime(fireplaceTimeInMinutes), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesFireplaceTime( + fireplaceTimeInMinutes + ), dataMode = ValloxDataMode.WRITE_DATA ) } override fun setFireplaceTimerEnabled(enabled: Boolean) { sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesFireplaceTimerEnabled(enabled), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesFireplaceTimerEnabled( + enabled + ), dataMode = ValloxDataMode.WRITE_DATA ) } override fun setWeeklyTimerEnabled(enabled: Boolean) { sendBytesToService( - requestBytes = messageHandler.generateWriteRequestBytesWeeklyTimerEnabled(enabled), + requestBytes = ValloxMessageHandler.generateWriteRequestBytesWeeklyTimerEnabled(enabled), dataMode = ValloxDataMode.WRITE_DATA ) } @@ -163,36 +186,24 @@ class ValloxClientImpl(private val uri: URI) : ValloxClient { private fun sendBytesToService( requestBytes: ByteBuffer, dataMode: ValloxDataMode - ): ValloxStatus? { - - var sessionFuture: Future? = null - - var valloxStatus: ValloxStatus? = null - - try { - - val socket = ValloxWebSocketListener { bytes -> - valloxStatus = messageHandler.readMessage(dataMode, bytes) - } + ): ValloxStatus? = runBlocking { - val request = ClientUpgradeRequest() + var status: ValloxStatus? = null - sessionFuture = webSocketClient.connect(socket, uri, request) + httpClient.webSocket( + method = HttpMethod.Get, + host = host, + port = 80, + path = "/" + ) { - /* Blocked wait until connection is established. */ - val session = sessionFuture.get() + send(requestBytes.moveToByteArray()) - session.remote.sendBytes(requestBytes) + val responseBytes = incoming.receive().data - /* Wait that the request has finished. */ - socket.awaitClose(2, TimeUnit.SECONDS) - - return valloxStatus - - } finally { - - if (sessionFuture != null && !sessionFuture.isDone) - sessionFuture.cancel(true) + status = ValloxMessageHandler.readMessage(dataMode, responseBytes) } + + return@runBlocking status } } diff --git a/src/main/java/de/stefan_oltmann/smarthome/vallox/ValloxWebSocketListener.kt b/src/main/java/de/stefan_oltmann/smarthome/vallox/ValloxWebSocketListener.kt deleted file mode 100644 index 2dbc21d..0000000 --- a/src/main/java/de/stefan_oltmann/smarthome/vallox/ValloxWebSocketListener.kt +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021 Stefan Oltmann - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package de.stefan_oltmann.smarthome.vallox - -import org.eclipse.jetty.websocket.api.Session -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage -import org.eclipse.jetty.websocket.api.annotations.WebSocket -import org.slf4j.LoggerFactory -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.InputStream -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -@WebSocket -class ValloxWebSocketListener(val callback: (ByteArray) -> Unit) { - - private val closeLatch = CountDownLatch(1) - - @OnWebSocketConnect - fun onConnect(session: Session) = logger.info("Connected to: {}", session.remoteAddress) - - @OnWebSocketMessage - fun onMessage(message: String?) = logger.info("Message from Server: {}", message) - - @OnWebSocketError - fun onError(cause: Throwable) = logger.info("Connection failed: {}", cause.message) - - @OnWebSocketMessage - fun onBinary(inputStream: InputStream) { - - logger.info("Got binary message") - - try { - - val bytes = readInputStream(inputStream) - - callback(bytes) - - } catch (ex: IOException) { - logger.error("Error receiving binary from Socket.", ex) - } - } - - @Throws(IOException::class) - private fun readInputStream(inputStream: InputStream): ByteArray { - - val buffer = ByteArrayOutputStream() - - val data = ByteArray(16384) - - var read: Int - - while (inputStream.read(data, 0, data.size).also { read = it } != -1) - buffer.write(data, 0, read) - - buffer.flush() - - return buffer.toByteArray() - } - - @OnWebSocketClose - fun onClose(statusCode: Int, reason: String?) { - - logger.info("WebSocket closed with code {} because of {}", statusCode, reason) - - closeLatch.countDown() - } - - @Throws(InterruptedException::class) - fun awaitClose(duration: Int, unit: TimeUnit) = - closeLatch.await(duration.toLong(), unit) - - companion object { - - private val logger = LoggerFactory.getLogger(ValloxWebSocketListener::class.java) - - } -}