diff --git a/src/codes/Somecodes.kt b/src/codes/Somecodes.kt index 3964c2c..7264fec 100644 --- a/src/codes/Somecodes.kt +++ b/src/codes/Somecodes.kt @@ -185,6 +185,14 @@ class Somecodes { return Path.of(path).fileName.toString() } + fun ToByteArray(vararg values: Int) : ByteArray { + val byteArray = ByteArray(values.size) + for (i in values.indices){ + byteArray[i] = values[i].toByte() + } + return byteArray + } + fun ExtractFilesFromClassPath(resourcePath: String, outputDir: Path) { try { val resource = Somecodes::class.java.getResource(resourcePath) diff --git a/src/toa/SX2KBroadcastFromAI.kt b/src/toa/SX2KBroadcastFromAI.kt new file mode 100644 index 0000000..4fa0c9d --- /dev/null +++ b/src/toa/SX2KBroadcastFromAI.kt @@ -0,0 +1,24 @@ +package toa + +@Suppress("unused") +data class SX2KBroadcastFromAI(val AIAddress: Int, val AIChannel: Int, val Zone: ByteArray = ByteArray(32)){ + init { + require(AIAddress in 1..8 ) { "AIAddress must be in the range 1-8" } + require(AIChannel in 1..8) { "AIChannel must be in the range 8-8" } + } + fun ZoneSelect(AO: Int, ZoneNumber: Int, Broadcast: Boolean){ + if (AO in 1..32 && ZoneNumber in 1..8){ + val index = (AO - 1) + val bit = ZoneNumber - 1 + if (Broadcast){ + Zone[index] = (Zone[index].toInt() or (1 shl bit)).toByte() + } else { + Zone[index] = (Zone[index].toInt() and (1 shl bit).inv()).toByte() + } + } + } + + override fun toString(): String { + return "SX2KBroadcastFromAI(AISlot=$AIAddress, AIChannel=$AIChannel)" + } +} diff --git a/src/toa/SX2KCIN.kt b/src/toa/SX2KCIN.kt new file mode 100644 index 0000000..5cd6c27 --- /dev/null +++ b/src/toa/SX2KCIN.kt @@ -0,0 +1,28 @@ +package toa + +data class SX2KCIN(val device: SX2KDevice, val address: Int, val Cin: Int, val state: Boolean) { + init{ + when(device){ + SX2KDevice.SX2000SM -> { + require(Cin in 1..8) { "Cin must be between 1 and 8 for SX2000SM" } + } + SX2KDevice.SX2000AO -> { + require(address in 1..32) { "Address must be between 1 and 32 for SX2000AO" } + require(Cin in 1..8) { "Cin must be between 1 and 8 for SX2000AO" } + } + SX2KDevice.SX2000AI -> { + require(address in 1..8) { "Address must be between 1 and 8 for SX2000AI" } + require(Cin in 1..16) { "Cin must be between 1 and 16 for SX2000AI" } + } + else -> throw IllegalArgumentException("Invalid device type") + } + } + override fun toString(): String { + return when(device){ + SX2KDevice.SX2000SM -> "SX2000SM - Cin: $Cin, State: $state" + SX2KDevice.SX2000AO -> "SX2000AO - Address: $address, Cin: $Cin, State: $state" + SX2KDevice.SX2000AI -> "SX2000AI - Address: $address, Cin: $Cin, State: $state" + else -> "Unknown Device" + } + } +} diff --git a/src/toa/SX2KDevice.kt b/src/toa/SX2KDevice.kt new file mode 100644 index 0000000..7af5c42 --- /dev/null +++ b/src/toa/SX2KDevice.kt @@ -0,0 +1,10 @@ +package toa + +@Suppress("unused") +enum class SX2KDevice(description: String) { + SX2000SM("SX2000SM"), + SX2000AI("SX2000AI"), + SX2000AO("SX2000AO"), + SX2000CI("SX2000CI"), + SX2000CO("SX2000CO") +} \ No newline at end of file diff --git a/src/toa/Sx2K.kt b/src/toa/Sx2K.kt new file mode 100644 index 0000000..e4f1c87 --- /dev/null +++ b/src/toa/Sx2K.kt @@ -0,0 +1,159 @@ +package toa + +import codes.Somecodes.Companion.ToByteArray +import org.tinylog.Logger +import java.net.InetSocketAddress +import java.net.Socket + +/** + * Created by a Sx2K device. + * @param ipAddress The IP address of the Sx2K device. Default is 192.168.14.1 + */ +@Suppress("unused") +class Sx2K(val ipAddress: String = "192.168.14.1") { + private var remotesocket : InetSocketAddress? = null + private val timeout = 2000 + init { + try{ + val inet = InetSocketAddress(ipAddress, 2005) + // connect trial + val socket = Socket() + socket.soTimeout = 2000 + socket.connect(inet, 2000) + socket.close() + remotesocket = inet + } catch (e: Exception) { + Logger.error { "Invalid IP address: $ipAddress , exception : ${e.message}" } + } + } + + /** + * Start a BroadcastFromAI on the Sx2K device. + * @param Broadcast Vararg of SX2KBroadcastFromAI objects to start the broadcast + */ + fun StartBroadcastFromAI(vararg Broadcast: SX2KBroadcastFromAI){ + if (remotesocket == null) { + Logger.error { "Cannot create BroadcastFromAI, invalid Sx2K device IP address: $ipAddress" } + return + } + try{ + Socket().use { socket -> + socket.connect(remotesocket, timeout) + socket.soTimeout = timeout + val outputStream = socket.getOutputStream() + val inputStream = socket.getInputStream() + Broadcast.forEach { bc -> + val cmd : ByteArray = ToByteArray(0xA0, 0xAD, bc.AIAddress, bc.AIChannel) + bc.Zone + outputStream.write(cmd) + outputStream.flush() + val reply = ByteArray(2) + inputStream.read(reply) + if (reply[0]==0.toByte() && reply[1]==1.toByte()){ + // DONE TRUE + Logger.info { "BroadcastFromAI succeeded for $bc" } + } else { + Logger.error { "BroadcastFromAI failed for $bc" } + } + } + } + } catch (e: Exception) { + Logger.error { "BroadcastFromAI failed , exception : ${e.message}" } + } + } + + /** + * Stop a BroadcastFromAI on the Sx2K device. + * @param Broadcast Vararg of SX2KBroadcastFromAI objects to stop the broadcast + */ + fun StopBroadcastFromAI(vararg Broadcast: SX2KBroadcastFromAI){ + if (remotesocket == null) { + Logger.error { "Cannot create StopBroadcastFromAI, invalid Sx2K device IP address: $ipAddress" } + return + } + try{ + Socket().use { socket -> + socket.connect(remotesocket, timeout) + socket.soTimeout = timeout + val outputStream = socket.getOutputStream() + val inputStream = socket.getInputStream() + Broadcast.forEach { bc -> + val cmd = ToByteArray(0xA1, 0xAD, bc.AIAddress, bc.AIChannel) + outputStream.write(cmd) + outputStream.flush() + val reply = ByteArray(2) + inputStream.read(reply) + if (reply[0]==0.toByte() && reply[1]==1.toByte()){ + // DONE TRUE + Logger.info { "StopBroadcastFromAI succeeded for $bc" } + } else { + Logger.error { "StopBroadcastFromAI failed for $bc" } + } + } + } + } catch (e: Exception) { + Logger.error { "StopBroadcastFromAI failed , exception : ${e.message}" } + } + } + + /** + * Set the state of virtual contact inputs on the Sx2K device. + * @param cin Vararg of SX2KCIN objects to set the state + */ + fun VirtualContactInput(vararg cin : SX2KCIN){ + if (remotesocket == null) { + Logger.error { "Cannot create VirtualContactInput, invalid Sx2K device IP address: $ipAddress" } + return + } + try { + Socket().use { socket -> + socket.connect(remotesocket, timeout) + socket.soTimeout = timeout + val outputStream = socket.getOutputStream() + val inputStream = socket.getInputStream() + cin.forEach { cc -> + val cmd : ByteArray = when(cc.device){ + SX2KDevice.SX2000SM -> { + if (cc.Cin in 1..8){ + ToByteArray(0xA0, 0xA0, cc.Cin, if (cc.state) 0x01 else 0x00) + } else ByteArray(0) + } + SX2KDevice.SX2000AO -> { + if (cc.address in 1..32){ + if (cc.Cin in 1..8){ + ToByteArray(0xA0, 0xA1, cc.address, cc.Cin, if (cc.state) 0x01 else 0x00) + } else ByteArray(0) + } else ByteArray(0) + } + SX2KDevice.SX2000AI -> { + if (cc.address in 1..8){ + if (cc.Cin in 1..16){ + ToByteArray(0xA0, 0xA2, cc.address, cc.Cin, if (cc.state) 0x01 else 0x00) + } else ByteArray(0) + } else ByteArray(0) + } + else -> ByteArray(0) + } + if (cmd.isNotEmpty()){ + outputStream.write(cmd) + outputStream.flush() + val reply = ByteArray(2) + inputStream.read(reply) + if (reply[0]==0.toByte() && reply[1]==1.toByte()){ + // DONE TRUE + Logger.info { "VirtualContactInput succeeded for $cc" } + } else { + Logger.error { "VirtualContactInput failed for $cc" } + } + } + } + } + + + } catch (e : Exception){ + Logger.error { "VirtualContactInput failed , exception : ${e.message}" } + } + + } + + +} \ No newline at end of file diff --git a/src/toa/Vx3K.kt b/src/toa/Vx3K.kt index fe713fd..53d1263 100644 --- a/src/toa/Vx3K.kt +++ b/src/toa/Vx3K.kt @@ -4,7 +4,6 @@ import codes.Somecodes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.tinylog.Logger import java.net.Inet4Address import java.net.InetSocketAddress import java.net.Socket @@ -17,6 +16,7 @@ import java.util.function.BiConsumer * @param ipaddress IP address of the VX3K device, default to 192.168.14.1 * @param port Port number of the VX3K device, from 50050-50053 default to 50053 */ +@Suppress("unused") class Vx3K(val ipaddress : String = "192.168.14.1", val port : Int = 50053) { private val remotesocket : InetSocketAddress init{ @@ -31,21 +31,6 @@ class Vx3K(val ipaddress : String = "192.168.14.1", val port : Int = 50053) { } } - /** - * Connect to the VX3K device - * @param timeout Connection timeout in milliseconds, default to 30000 ms - */ - fun Connect(timeout: Int = 30000){ - try{ - val socket = Socket() - // read timeout 5 seconds - socket.soTimeout = 5000 - socket.connect(remotesocket, timeout) - } catch (e : Exception){ - Logger.error { "Failed to connect with ${remotesocket.hostName}:${remotesocket.port}, Message: ${e.message}" } - } - } - /** * Virtual Contact Input (Commmand 0x1001) * @param ID : Device ID for VX3K, range 0 - 31 @@ -226,7 +211,7 @@ class Vx3K(val ipaddress : String = "192.168.14.1", val port : Int = 50053) { try{ val tcp = Socket() tcp.soTimeout = 5000 - tcp.connect(remotesocket, 30000) + tcp.connect(remotesocket, 5000) val outstream = tcp.getOutputStream() val instream = tcp.getInputStream() outstream.write(command)