From 2eec3e2dba82f69829ba451ae90d1fcb336216df Mon Sep 17 00:00:00 2001 From: ImUrX Date: Thu, 22 Jun 2023 01:24:28 -0300 Subject: [PATCH] make `SerialHandler`'s implementation platform-dependant --- server/core/build.gradle.kts | 12 - .../src/main/java/dev/slimevr/VRServer.kt | 8 +- .../protocol/rpc/serial/RPCSerialHandler.java | 2 +- .../slimevr/serial/ProvisioningHandler.java | 6 +- .../java/dev/slimevr/serial/SerialHandler.kt | 310 ++---------------- .../java/dev/slimevr/serial/SerialListener.kt | 25 +- server/desktop/build.gradle.kts | 12 +- .../src/main/java/dev/slimevr/desktop/Main.kt | 8 +- .../desktop/serial/DesktopSerialHandler.kt | 258 +++++++++++++++ 9 files changed, 316 insertions(+), 325 deletions(-) create mode 100644 server/desktop/src/main/java/dev/slimevr/desktop/serial/DesktopSerialHandler.kt diff --git a/server/core/build.gradle.kts b/server/core/build.gradle.kts index 5a890e6e90..8742d957b9 100644 --- a/server/core/build.gradle.kts +++ b/server/core/build.gradle.kts @@ -6,7 +6,6 @@ * User Manual available at https://docs.gradle.org/6.3/userguide/java_library_plugin.html */ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream plugins { kotlin("jvm") @@ -64,7 +63,6 @@ dependencies { implementation("org.apache.commons:commons-collections4:4.4") implementation("com.illposed.osc:javaosc-core:0.8") - implementation("com.fazecast:jSerialComm:2.+") implementation("org.java-websocket:Java-WebSocket:1.+") implementation("com.melloware:jintellitype:1.+") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") @@ -79,13 +77,3 @@ dependencies { tasks.test { useJUnitPlatform() } - -fun String.runCommand(currentWorkingDir: File = file("./")): String { - val byteOut = ByteArrayOutputStream() - project.exec { - workingDir = currentWorkingDir - commandLine = this@runCommand.split("\\s".toRegex()) - standardOutput = byteOut - } - return String(byteOut.toByteArray()).trim() -} diff --git a/server/core/src/main/java/dev/slimevr/VRServer.kt b/server/core/src/main/java/dev/slimevr/VRServer.kt index 0b9f5f0522..91657dd091 100644 --- a/server/core/src/main/java/dev/slimevr/VRServer.kt +++ b/server/core/src/main/java/dev/slimevr/VRServer.kt @@ -14,6 +14,7 @@ import dev.slimevr.protocol.ProtocolAPI import dev.slimevr.reset.ResetHandler import dev.slimevr.serial.ProvisioningHandler import dev.slimevr.serial.SerialHandler +import dev.slimevr.serial.SerialHandlerStub import dev.slimevr.setup.TapSetupHandler import dev.slimevr.status.StatusSystem import dev.slimevr.tracking.processor.HumanPoseManager @@ -41,8 +42,9 @@ typealias SteamBridgeProvider = ( ) -> ISteamVRBridge? class VRServer @JvmOverloads constructor( - driverBridgeProvider: SteamBridgeProvider = { _: VRServer, _: Tracker, _: List -> null }, - feederBridgeProvider: (VRServer) -> ISteamVRBridge? = { _: VRServer -> null }, + driverBridgeProvider: SteamBridgeProvider = { _, _, _ -> null }, + feederBridgeProvider: (VRServer) -> ISteamVRBridge? = { _ -> null }, + serialHandlerProvider: (VRServer) -> SerialHandler = { _ -> SerialHandlerStub() }, configPath: String, ) : Thread("VRServer") { @JvmField @@ -100,7 +102,7 @@ class VRServer @JvmOverloads constructor( configManager = ConfigManager(configPath) configManager.loadConfig() deviceManager = DeviceManager(this) - serialHandler = SerialHandler() + serialHandler = serialHandlerProvider(this) provisioningHandler = ProvisioningHandler(this) resetHandler = ResetHandler() tapSetupHandler = TapSetupHandler() diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCSerialHandler.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCSerialHandler.java index 7424c8a11d..08c57361ca 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCSerialHandler.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCSerialHandler.java @@ -1,11 +1,11 @@ package dev.slimevr.protocol.rpc.serial; -import com.fazecast.jSerialComm.SerialPort; import com.google.flatbuffers.FlatBufferBuilder; import dev.slimevr.protocol.GenericConnection; import dev.slimevr.protocol.ProtocolAPI; import dev.slimevr.protocol.rpc.RPCHandler; import dev.slimevr.serial.SerialListener; +import dev.slimevr.serial.SerialPort; import io.eiren.util.logging.LogManager; import solarxr_protocol.rpc.*; diff --git a/server/core/src/main/java/dev/slimevr/serial/ProvisioningHandler.java b/server/core/src/main/java/dev/slimevr/serial/ProvisioningHandler.java index f1ad23c888..83f50a434c 100644 --- a/server/core/src/main/java/dev/slimevr/serial/ProvisioningHandler.java +++ b/server/core/src/main/java/dev/slimevr/serial/ProvisioningHandler.java @@ -1,8 +1,8 @@ package dev.slimevr.serial; -import com.fazecast.jSerialComm.SerialPort; import dev.slimevr.VRServer; import io.eiren.util.logging.LogManager; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Timer; @@ -107,7 +107,7 @@ else if (this.provisioningStatus == ProvisioningStatus.LOOKING_FOR_SERVER) @Override - public void onSerialConnected(SerialPort port) { + public void onSerialConnected(@NotNull SerialPort port) { if (!isRunning) return; this.tryProvisioning(); @@ -121,7 +121,7 @@ public void onSerialDisconnected() { } @Override - public void onSerialLog(String str) { + public void onSerialLog(@NotNull String str) { if (!isRunning) return; diff --git a/server/core/src/main/java/dev/slimevr/serial/SerialHandler.kt b/server/core/src/main/java/dev/slimevr/serial/SerialHandler.kt index 60c7bd83c5..939bcfe88e 100644 --- a/server/core/src/main/java/dev/slimevr/serial/SerialHandler.kt +++ b/server/core/src/main/java/dev/slimevr/serial/SerialHandler.kt @@ -1,299 +1,41 @@ -package dev.slimevr.serial; +package dev.slimevr.serial -import com.fazecast.jSerialComm.SerialPort; -import com.fazecast.jSerialComm.SerialPortEvent; -import com.fazecast.jSerialComm.SerialPortMessageListener; -import io.eiren.util.logging.LogManager; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.Equator; +import java.util.stream.Stream -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; -import java.util.stream.Stream; +abstract class SerialHandler { + abstract val isConnected: Boolean + abstract val knownPorts: Stream + abstract fun addListener(channel: SerialListener) + abstract fun removeListener(channel: SerialListener) -public class SerialHandler implements SerialPortMessageListener { - - private final List listeners = new CopyOnWriteArrayList<>(); - private final Timer getDevicesTimer = new Timer("GetDevicesTimer"); - - private SerialPort currentPort = null; - - private boolean watchingNewDevices = false; - private SerialPort[] lastKnownPorts = new SerialPort[] {}; - - public SerialHandler() { - startWatchingNewDevices(); - } - - public void startWatchingNewDevices() { - if (this.watchingNewDevices) - return; - this.watchingNewDevices = true; - this.getDevicesTimer.scheduleAtFixedRate(new TimerTask() { - public void run() { - try { - detectNewPorts(); - } catch (Throwable t) { - LogManager - .severe( - "[SerialHandler] Error while watching for new devices, cancelling the \"getDevicesTimer\".", - t - ); - getDevicesTimer.cancel(); - } - } - }, 0, 3000); - } - - public void stopWatchingNewDevices() { - if (!this.watchingNewDevices) - return; - this.watchingNewDevices = false; - this.getDevicesTimer.cancel(); - this.getDevicesTimer.purge(); - } - - public void onNewDevice(SerialPort port) { - this.listeners.forEach((listener) -> listener.onNewSerialDevice(port)); - } - - - public void addListener(SerialListener channel) { - this.listeners.add(channel); - } - - public void removeListener(SerialListener l) { - listeners.removeIf(listener -> l == listener); - } - - public synchronized boolean openSerial(String portLocation, boolean auto) { - LogManager.info("[SerialHandler] Trying to open: " + portLocation + ", auto: " + auto); - - SerialPort[] ports = SerialPort.getCommPorts(); - lastKnownPorts = ports; - SerialPort newPort = null; - for (SerialPort port : ports) { - if (!auto && port.getPortLocation().equals(portLocation)) { - newPort = port; - break; - } - - if (auto && isKnownBoard(port.getDescriptivePortName())) { - newPort = port; - break; - } - } - if (newPort == null) { - LogManager - .info( - "[SerialHandler] No serial ports found to connect to (" - + ports.length - + ") total ports" - ); - return false; - } - if (this.isConnected()) { - if ( - !newPort.getPortLocation().equals(currentPort.getPortLocation()) - || !newPort - .getDescriptivePortName() - .equals(currentPort.getDescriptivePortName()) - ) { - LogManager - .info( - "[SerialHandler] Closing current serial port " - + currentPort.getDescriptivePortName() - ); - currentPort.removeDataListener(); - currentPort.closePort(); - } else { - LogManager.info("[SerialHandler] Reusing already open port"); - this.listeners.forEach((listener) -> listener.onSerialConnected(currentPort)); - return true; - } - } - currentPort = newPort; - LogManager - .info( - "[SerialHandler] Trying to connect to new serial port " - + currentPort.getDescriptivePortName() - ); - - currentPort.setBaudRate(115200); - currentPort.clearRTS(); - currentPort.clearDTR(); - if (!currentPort.openPort(1000)) { - LogManager - .warning( - "[SerialHandler] Can't open serial port " - + currentPort.getDescriptivePortName() - + ", last error: " - + currentPort.getLastErrorCode() - ); - currentPort = null; - return false; - } - - currentPort.addDataListener(this); - this.listeners.forEach((listener) -> listener.onSerialConnected(currentPort)); - LogManager - .info("[SerialHandler] Serial port " + newPort.getDescriptivePortName() + " is open"); - return true; - } - - public void rebootRequest() { - this.writeSerial("REBOOT"); - } - - public void factoryResetRequest() { - this.writeSerial("FRST"); - } - - public void infoRequest() { - this.writeSerial("GET INFO"); - } - - public synchronized void closeSerial() { - try { - if (currentPort != null) - currentPort.closePort(); - this.listeners.forEach(SerialListener::onSerialDisconnected); - LogManager - .info( - "[SerialHandler] Port " - + (currentPort != null ? currentPort.getDescriptivePortName() : "null") - + " closed okay" - ); - currentPort = null; - } catch (Exception e) { - LogManager - .warning( - "[SerialHandler] Error closing port " - + (currentPort != null ? currentPort.getDescriptivePortName() : "null"), - e - ); - } - } - - private synchronized void writeSerial(String serialText) { - if (currentPort == null) - return; - OutputStream os = currentPort.getOutputStream(); - OutputStreamWriter writer = new OutputStreamWriter(os); - try { - writer.append(serialText).append("\n"); - writer.flush(); - this.addLog("-> " + serialText + "\n"); - } catch (IOException e) { - addLog("[!] Serial error: " + e.getMessage() + "\n"); - LogManager.warning("[SerialHandler] Serial port write error", e); - } - } - - public synchronized void setWifi(String ssid, String passwd) { - if (currentPort == null) - return; - OutputStream os = currentPort.getOutputStream(); - OutputStreamWriter writer = new OutputStreamWriter(os); - try { - writer.append("SET WIFI \"").append(ssid).append("\" \"").append(passwd).append("\"\n"); - writer.flush(); - this.addLog("-> SET WIFI \"" + ssid + "\" \"" + passwd.replaceAll(".", "*") + "\"\n"); - } catch (IOException e) { - addLog(e + "\n"); - LogManager.warning("[SerialHandler] Serial port write error", e); - } - } - - public void addLog(String str) { - LogManager.info("[Serial] " + str); - this.listeners.forEach(listener -> listener.onSerialLog(str)); - } + abstract fun openSerial(portLocation: String?, auto: Boolean): Boolean + abstract fun rebootRequest() + abstract fun factoryResetRequest() + abstract fun infoRequest() + abstract fun closeSerial() + abstract fun setWifi(ssid: String, passwd: String) +} - @Override - public int getListeningEvents() { - return SerialPort.LISTENING_EVENT_PORT_DISCONNECTED - | SerialPort.LISTENING_EVENT_DATA_RECEIVED; - } +class SerialHandlerStub() : SerialHandler() { + override val isConnected: Boolean = false + override val knownPorts: Stream = Stream.empty() - @Override - public void serialEvent(SerialPortEvent event) { - if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_RECEIVED) { - byte[] newData = event.getReceivedData(); - String s = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(newData)).toString(); - this.addLog(s); - } else if (event.getEventType() == SerialPort.LISTENING_EVENT_PORT_DISCONNECTED) { - this.closeSerial(); - } - } + override fun addListener(channel: SerialListener) {} - public synchronized boolean isConnected() { - return this.currentPort != null && this.currentPort.isOpen(); - } + override fun removeListener(channel: SerialListener) {} - @Override - public byte[] getMessageDelimiter() { - return new byte[] { (byte) 0x0A }; + override fun openSerial(portLocation: String?, auto: Boolean): Boolean { + return false } - @Override - public boolean delimiterIndicatesEndOfMessage() { - return true; - } + override fun rebootRequest() {} - public Stream getKnownPorts() { - return Arrays - .stream(SerialPort.getCommPorts()) - .filter((port) -> isKnownBoard(port.getDescriptivePortName())); + override fun factoryResetRequest() {} - } - - private boolean isKnownBoard(String com) { - String lowerCom = com.toLowerCase(); + override fun infoRequest() {} - return lowerCom.contains("ch340") - || lowerCom.contains("cp21") - || lowerCom.contains("ch910") - || (lowerCom.contains("usb") - && lowerCom.contains("seri")); - } + override fun closeSerial() {} - private void detectNewPorts() { - try { - List differences = new ArrayList<>( - CollectionUtils - .removeAll( - this.getKnownPorts().collect(Collectors.toList()), - Arrays.asList(lastKnownPorts), - new Equator<>() { - @Override - public boolean equate(SerialPort o1, SerialPort o2) { - return o1.getPortLocation().equals(o2.getPortLocation()) - && o1 - .getDescriptivePortName() - .equals(o1.getDescriptivePortName()); - } - - @Override - public int hash(SerialPort o) { - return 0; - } - } - ) - ); - lastKnownPorts = SerialPort.getCommPorts(); - differences.forEach(this::onNewDevice); - } catch (Throwable e) { - LogManager - .severe("[SerialHandler] Using serial ports is not supported on this platform", e); - throw new RuntimeException("Serial unsupported"); - } - } + override fun setWifi(ssid: String, passwd: String) {} } diff --git a/server/core/src/main/java/dev/slimevr/serial/SerialListener.kt b/server/core/src/main/java/dev/slimevr/serial/SerialListener.kt index c2626914ac..c05ad64d14 100644 --- a/server/core/src/main/java/dev/slimevr/serial/SerialListener.kt +++ b/server/core/src/main/java/dev/slimevr/serial/SerialListener.kt @@ -1,15 +1,20 @@ -package dev.slimevr.serial; +package dev.slimevr.serial -import com.fazecast.jSerialComm.SerialPort; +abstract class SerialPort { + abstract val portLocation: String + abstract val descriptivePortName: String + override fun equals(other: Any?): Boolean { + val other: SerialPort = other as? SerialPort ?: return super.equals(other) -public interface SerialListener { - - void onSerialConnected(SerialPort port); - - void onSerialDisconnected(); - - void onSerialLog(String str); + return this.portLocation == other.portLocation && + this.descriptivePortName == other.descriptivePortName + } +} - void onNewSerialDevice(SerialPort port); +interface SerialListener { + fun onSerialConnected(port: SerialPort) + fun onSerialDisconnected() + fun onSerialLog(str: String) + fun onNewSerialDevice(port: SerialPort) } diff --git a/server/desktop/build.gradle.kts b/server/desktop/build.gradle.kts index 4fee279822..896a378412 100644 --- a/server/desktop/build.gradle.kts +++ b/server/desktop/build.gradle.kts @@ -6,7 +6,6 @@ * User Manual available at https://docs.gradle.org/6.3/userguide/java_library_plugin.html */ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream plugins { kotlin("jvm") @@ -58,6 +57,7 @@ dependencies { implementation("com.google.protobuf:protobuf-java:3.21.12") implementation("net.java.dev.jna:jna:5.+") implementation("net.java.dev.jna:jna-platform:5.+") + implementation("com.fazecast:jSerialComm:2.+") } tasks.shadowJar { @@ -76,16 +76,6 @@ application { mainClass.set("dev.slimevr.desktop.Main") } -fun String.runCommand(currentWorkingDir: File = file("./")): String { - val byteOut = ByteArrayOutputStream() - project.exec { - workingDir = currentWorkingDir - commandLine = this@runCommand.split("\\s".toRegex()) - standardOutput = byteOut - } - return String(byteOut.toByteArray()).trim() -} - buildConfig { useKotlinOutput { topLevelConstants = true } packageName("dev.slimevr.desktop") diff --git a/server/desktop/src/main/java/dev/slimevr/desktop/Main.kt b/server/desktop/src/main/java/dev/slimevr/desktop/Main.kt index fe45b5bd42..f57748e96f 100644 --- a/server/desktop/src/main/java/dev/slimevr/desktop/Main.kt +++ b/server/desktop/src/main/java/dev/slimevr/desktop/Main.kt @@ -8,6 +8,7 @@ import dev.slimevr.bridge.ISteamVRBridge import dev.slimevr.desktop.platform.SteamVRBridge import dev.slimevr.desktop.platform.linux.UnixSocketBridge import dev.slimevr.desktop.platform.windows.WindowsNamedPipeBridge +import dev.slimevr.desktop.serial.DesktopSerialHandler import dev.slimevr.tracking.trackers.Tracker import io.eiren.util.OperatingSystem import io.eiren.util.collections.FastList @@ -107,7 +108,12 @@ fun main(args: Array) { return } try { - val vrServer = VRServer(::provideSteamVRBridge, ::provideFeederBridge, "vrconfig.yml") + val vrServer = VRServer( + ::provideSteamVRBridge, + ::provideFeederBridge, + { _ -> DesktopSerialHandler() }, + "vrconfig.yml" + ) vrServer.start() Keybinding(vrServer) val scanner = thread { diff --git a/server/desktop/src/main/java/dev/slimevr/desktop/serial/DesktopSerialHandler.kt b/server/desktop/src/main/java/dev/slimevr/desktop/serial/DesktopSerialHandler.kt new file mode 100644 index 0000000000..9dd0966e5c --- /dev/null +++ b/server/desktop/src/main/java/dev/slimevr/desktop/serial/DesktopSerialHandler.kt @@ -0,0 +1,258 @@ +package dev.slimevr.desktop.serial + +import com.fazecast.jSerialComm.SerialPort +import com.fazecast.jSerialComm.SerialPortEvent +import com.fazecast.jSerialComm.SerialPortMessageListener +import dev.slimevr.serial.SerialHandler +import dev.slimevr.serial.SerialListener +import io.eiren.util.logging.LogManager +import java.io.IOException +import java.io.OutputStreamWriter +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets +import java.util.* +import java.util.concurrent.CopyOnWriteArrayList +import java.util.stream.Stream +import kotlin.concurrent.timerTask +import kotlin.streams.asSequence +import kotlin.streams.asStream +import dev.slimevr.serial.SerialPort as SlimeSerialPort + +class SerialPortWrapper(val port: SerialPort) : SlimeSerialPort() { + override val portLocation: String + get() = port.portLocation + override val descriptivePortName: String + get() = port.descriptivePortName +} + +class DesktopSerialHandler : SerialHandler(), SerialPortMessageListener { + private val listeners: MutableList = CopyOnWriteArrayList() + private val getDevicesTimer = Timer("GetDevicesTimer") + private var currentPort: SerialPort? = null + private var watchingNewDevices = false + private var lastKnownPorts = setOf() + + init { + startWatchingNewDevices() + } + + fun startWatchingNewDevices() { + if (watchingNewDevices) return + watchingNewDevices = true + getDevicesTimer.scheduleAtFixedRate( + timerTask { + try { + detectNewPorts() + } catch (t: Throwable) { + LogManager.severe( + "[SerialHandler] Error while watching for new devices, cancelling the \"getDevicesTimer\".", + t + ) + getDevicesTimer.cancel() + } + }, + 0, + 3000 + ) + } + + fun stopWatchingNewDevices() { + if (!watchingNewDevices) return + watchingNewDevices = false + getDevicesTimer.cancel() + getDevicesTimer.purge() + } + + fun onNewDevice(port: SerialPort) { + listeners.forEach { it.onNewSerialDevice(SerialPortWrapper(port)) } + } + + override fun addListener(channel: SerialListener) { + listeners.add(channel) + } + + override fun removeListener(l: SerialListener) { + listeners.removeIf { l === it } + } + + @Synchronized + override fun openSerial(portLocation: String?, auto: Boolean): Boolean { + LogManager.info("[SerialHandler] Trying to open: $portLocation, auto: $auto") + val ports = SerialPort.getCommPorts() + lastKnownPorts = ports.toSet() + val newPort: SerialPort? = ports.find { + return (!auto && it.portLocation == portLocation) || + (auto && isKnownBoard(it.descriptivePortName)) + } + if (newPort == null) { + LogManager.info( + "[SerialHandler] No serial ports found to connect to (${ports.size}) total ports" + ) + return false + } + if (isConnected) { + if (newPort.portLocation != currentPort!!.portLocation || + newPort.descriptivePortName != currentPort!!.descriptivePortName + ) { + LogManager.info( + "[SerialHandler] Closing current serial port " + + currentPort!!.descriptivePortName + ) + currentPort!!.removeDataListener() + currentPort!!.closePort() + } else { + LogManager.info("[SerialHandler] Reusing already open port") + listeners.forEach { it.onSerialConnected(SerialPortWrapper(currentPort!!)) } + return true + } + } + currentPort = newPort + LogManager.info( + "[SerialHandler] Trying to connect to new serial port " + + currentPort!!.descriptivePortName + ) + currentPort!!.setBaudRate(115200) + currentPort!!.clearRTS() + currentPort!!.clearDTR() + if (!currentPort!!.openPort(1000)) { + LogManager.warning( + "[SerialHandler] Can't open serial port ${currentPort!!.descriptivePortName}, last error: " + + currentPort!!.lastErrorCode + ) + currentPort = null + return false + } + currentPort!!.addDataListener(this) + listeners.forEach { it.onSerialConnected(SerialPortWrapper(currentPort!!)) } + LogManager.info("[SerialHandler] Serial port ${newPort.descriptivePortName} is open") + return true + } + + override fun rebootRequest() { + writeSerial("REBOOT") + } + + override fun factoryResetRequest() { + writeSerial("FRST") + } + + override fun infoRequest() { + writeSerial("GET INFO") + } + + @Synchronized + override fun closeSerial() { + try { + if (currentPort != null) currentPort!!.closePort() + listeners.forEach { it.onSerialDisconnected() } + LogManager.info( + "[SerialHandler] Port ${currentPort?.descriptivePortName} closed okay" + ) + currentPort = null + } catch (e: Exception) { + LogManager.warning( + "[SerialHandler] Error closing port ${currentPort?.descriptivePortName}", + e + ) + } + } + + @Synchronized + private fun writeSerial(serialText: String) { + if (currentPort == null) return + val os = currentPort!!.outputStream + val writer = OutputStreamWriter(os) + try { + writer.append(serialText).append("\n") + writer.flush() + addLog("-> $serialText\n") + } catch (e: IOException) { + addLog("[!] Serial error: ${e.message}\n") + LogManager.warning("[SerialHandler] Serial port write error", e) + } + } + + @Synchronized + override fun setWifi(ssid: String, passwd: String) { + if (currentPort == null) return + val os = currentPort!!.outputStream + val writer = OutputStreamWriter(os) + try { + writer.append("SET WIFI \"").append(ssid).append("\" \"").append(passwd).append("\"\n") + writer.flush() + addLog("-> SET WIFI \"$ssid\" \"${passwd.replace(".".toRegex(), "*")}\"\n") + } catch (e: IOException) { + addLog("$e\n") + LogManager.warning("[SerialHandler] Serial port write error", e) + } + } + + fun addLog(str: String) { + LogManager.info("[Serial] $str") + listeners.forEach { it.onSerialLog(str) } + } + + override fun getListeningEvents(): Int { + return ( + SerialPort.LISTENING_EVENT_PORT_DISCONNECTED + or SerialPort.LISTENING_EVENT_DATA_RECEIVED + ) + } + + override fun serialEvent(event: SerialPortEvent) { + when (event.eventType) { + SerialPort.LISTENING_EVENT_DATA_RECEIVED -> { + val newData = event.receivedData + val s = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(newData)).toString() + addLog(s) + } + SerialPort.LISTENING_EVENT_PORT_DISCONNECTED -> { + closeSerial() + } + } + } + + @get:Synchronized + override val isConnected: Boolean + get() = currentPort?.isOpen ?: false + + override fun getMessageDelimiter(): ByteArray { + return byteArrayOf(0x0A.toByte()) + } + + override fun delimiterIndicatesEndOfMessage(): Boolean { + return true + } + + override val knownPorts: Stream + get() = SerialPort.getCommPorts() + .asSequence() + .filter { isKnownBoard(it.descriptivePortName) } + .map { SerialPortWrapper(it) } + .asStream() + + private fun isKnownBoard(com: String): Boolean { + val lowerCom = com.lowercase(Locale.getDefault()) + return ( + lowerCom.contains("ch340") || + lowerCom.contains("cp21") || + lowerCom.contains("ch910") || + ( + lowerCom.contains("usb") && + lowerCom.contains("seri") + ) + ) + } + + private fun detectNewPorts() { + try { + val differences = knownPorts.asSequence().map { it.port } - lastKnownPorts + lastKnownPorts = SerialPort.getCommPorts().toSet() + differences.forEach { onNewDevice(it) } + } catch (e: Throwable) { + LogManager + .severe("[SerialHandler] Using serial ports is not supported on this platform", e) + throw RuntimeException("Serial unsupported") + } + } +}