commit 06/10/2025

This commit is contained in:
2025-10-06 08:30:09 +07:00
parent 20dbc12b02
commit cf69c72f3c
9 changed files with 97 additions and 81 deletions

View File

@@ -23,6 +23,8 @@ lateinit var audioPlayer: AudioPlayer
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap() val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
lateinit var udpreceiver: UDPReceiver lateinit var udpreceiver: UDPReceiver
const val version = "0.0.2 (23/09/2025)" const val version = "0.0.2 (23/09/2025)"
// AAS 64 channels
const val max_channel = 64
// dipakai untuk pilih voice type, bisa diganti via web nanti // dipakai untuk pilih voice type, bisa diganti via web nanti
var selected_voice = VoiceType.VOICE_1.name var selected_voice = VoiceType.VOICE_1.name
@@ -91,7 +93,7 @@ fun main() {
val barixserver = TCP_Barix_Command_Server() val barixserver = TCP_Barix_Command_Server()
barixserver.StartTcpServer { cmd -> barixserver.StartTcpServer { cmd ->
Logger.info { cmd } //Logger.info { cmd }
val _streamer = StreamerOutputs[cmd.ipaddress] val _streamer = StreamerOutputs[cmd.ipaddress]
val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress } val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress }
if (_streamer == null) { if (_streamer == null) {
@@ -103,7 +105,8 @@ fun main() {
_bc.bufferRemain = cmd.buffremain _bc.bufferRemain = cmd.buffremain
_bc.statusData = cmd.statusdata _bc.statusData = cmd.statusdata
StreamerOutputs[cmd.ipaddress] = _bc StreamerOutputs[cmd.ipaddress] = _bc
} Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" }
} else Logger.warn { "soundChannelDB doesn't have soundchannel with IP ${cmd.ipaddress}" }
} else { } else {
// sudah ada, update data // sudah ada, update data

View File

@@ -39,16 +39,24 @@ class MainExtension01 {
*/ */
fun AllBroadcastZonesValid(bz: List<String>): Boolean { fun AllBroadcastZonesValid(bz: List<String>): Boolean {
if (bz.isNotEmpty()) { if (bz.isNotEmpty()) {
println("StreamerOutputs: $StreamerOutputs")
val validchannels = bz val validchannels = bz
// check apakah tiap zone ada di database broadcast zones // check apakah tiap zone ada di database broadcast zones
.filter { z1 -> .filter { z1 ->
db.broadcastDB.List.find { z2 -> z2.SoundChannel == z1 } != null println("Checking broadcast zone $z1")
val xx = db.broadcastDB.List.find { z2 -> z2.description == z1 }
println("Found in DB: $xx")
xx != null
} }
// check apakah tiap zone ada di SoundChannelList dan Online // check apakah tiap zone ada di SoundChannelList dan Online
.filter { z3 -> .filter { z3 ->
StreamerOutputs.any { sc -> sc.value.channel == z3 && sc.value.isOnline() } println("Checking if zone $z3 is in StreamerOutputs and online")
val xx = StreamerOutputs.values.find { it.channel == z3 }
println("Is online: $xx")
xx!= null
} }
println("Valid channels: $validchannels")
// kalau jumlah valid channel sama dengan jumlah broadcast zone, berarti semua valid // kalau jumlah valid channel sama dengan jumlah broadcast zone, berarti semua valid
return validchannels.size == bz.size return validchannels.size == bz.size
} }
@@ -484,6 +492,7 @@ class MainExtension01 {
fun Read_Queue_Paging(){ fun Read_Queue_Paging(){
db.queuepagingDB.Get() db.queuepagingDB.Get()
for (qp in db.queuepagingDB.List) { for (qp in db.queuepagingDB.List) {
println("Processing QueuePaging $qp")
if (qp.BroadcastZones.isNotBlank()) { if (qp.BroadcastZones.isNotBlank()) {
val zz = qp.BroadcastZones.split(";") val zz = qp.BroadcastZones.split(";")
if (AllBroadcastZonesValid(zz)) { if (AllBroadcastZonesValid(zz)) {
@@ -615,10 +624,14 @@ class MainExtension01 {
fun Read_Queue_Table(){ fun Read_Queue_Table(){
db.queuetableDB.Get() db.queuetableDB.Get()
db.queuetableDB.List.forEach { qa -> db.queuetableDB.List.forEach { qa ->
println("Processing QueueTable $qa")
if (qa.BroadcastZones.isNotEmpty()) { if (qa.BroadcastZones.isNotEmpty()) {
val zz = qa.BroadcastZones.split(";") val zz = qa.BroadcastZones.split(";")
println("Broadcast zones: $zz")
if (AllBroadcastZonesValid(zz)) { if (AllBroadcastZonesValid(zz)) {
println("All broadcast zones valid")
if (AllBroadcastZoneIdle(zz)) { if (AllBroadcastZoneIdle(zz)) {
println("All broadcast zones idle")
if (qa.Type == "SOUNDBANK") { if (qa.Type == "SOUNDBANK") {
val variables = Get_Soundbank_Data(qa.SB_TAGS) val variables = Get_Soundbank_Data(qa.SB_TAGS)
val languages = qa.Language.split(";") val languages = qa.Language.split(";")

View File

@@ -9,11 +9,11 @@ import java.util.function.Consumer
@Suppress("unused") @Suppress("unused")
class TCP_Barix_Command_Server { class TCP_Barix_Command_Server {
private var tcpserver: ServerSocket? = null lateinit var tcpserver: ServerSocket
private var job: Job? = null lateinit var job: Job
private val socketMap = mutableMapOf<String, Socket>() private val socketMap = mutableMapOf<String, Socket>()
private val regex = """\$\\"STATUSBARIX;(\d+);(\d+);?(\d)?\\"$""" private val regex = """STATUSBARIX;(\d+);(\d+);?(\d)?"""
private val pattern = Regex(regex) private val pattern = Regex(regex)
/** /**
@@ -27,51 +27,44 @@ class TCP_Barix_Command_Server {
val tcp = ServerSocket(port) val tcp = ServerSocket(port)
tcpserver = tcp tcpserver = tcp
job = CoroutineScope(Dispatchers.IO).launch { job = CoroutineScope(Dispatchers.IO).launch {
Logger.info { "TCP server started" } Logger.info { "TCP StreamerOutput server started on port $port" }
while (isActive) { while (isActive) {
if (tcpserver?.isClosed == true) break if (tcpserver.isClosed) break
try { try {
tcpserver?.accept().use { socket ->
{
CoroutineScope(Dispatchers.Main).launch {
if (socket != null) {
val key : String = socket.inetAddress.hostAddress+":"+socket.port
socketMap[key] = socket
Logger.info { "Start communicating with $key" }
socket.getInputStream().use { din ->
{
while (isActive) {
if (din.available()>0){
val bb = ByteArray(din.available())
din.read(bb)
// B4A format, 4 bytes di depan adalah size
val str = String(bb)
if (ValidString(str)) {
// Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$
pattern.find(str)?.let { matchResult ->
val (vu, buffremain, statusdata) = matchResult.destructured
val status = BarixStatus(
socket.inetAddress.hostAddress,
vu.toInt(),
buffremain.toInt(),
statusdata.toIntOrNull() ?: 0
)
Logger.info { "Received valid command from $key : $status" }
cb.accept(status)
} ?: run {
Logger.warn { "Invalid command format from $key : $str" }
}
}
}
}
}
}
Logger.info { "Finished communicating with $key" }
socketMap.remove(key)
}
val socket = tcpserver.accept()
CoroutineScope(Dispatchers.IO).launch {
val key : String = socket.inetAddress.hostAddress
socketMap[key] = socket
Logger.info { "Start communicating with Streamer Output with IP : $key" }
val din = socket.getInputStream()
while (isActive) {
if (din.available()>0){
val bb = ByteArray(din.available())
din.read(bb)
// B4A format, 4 bytes di depan adalah size
val str = String(bb, 4, bb.size - 4)
if (ValidString(str)) {
// Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$
pattern.find(str)?.let { matchResult ->
val (vu, buffremain, statusdata) = matchResult.destructured
val status = BarixStatus(
socket.inetAddress.hostAddress,
vu.toInt(),
buffremain.toInt(),
statusdata.toIntOrNull() ?: 0
)
//Logger.info { "Received valid command from $key : $status" }
cb.accept(status)
} ?: run {
Logger.warn { "Invalid command format from $key : $str" }
}
}
} }
} }
Logger.info { "Finished communicating with Streamer Output with IP $key" }
socketMap.remove(key)
} }
} catch (ex: Exception) { } catch (ex: Exception) {
@@ -94,20 +87,18 @@ class TCP_Barix_Command_Server {
*/ */
fun StopTcpCommand(): Boolean { fun StopTcpCommand(): Boolean {
try { try {
tcpserver?.close() tcpserver.close()
runBlocking { runBlocking {
socketMap.values.forEach { socketMap.values.forEach {
it.close() it.close()
} }
socketMap.clear() socketMap.clear()
job?.join() job.join()
} }
Logger.info { "StopTcpCommand success" } Logger.info { "StopTcpCommand success" }
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" } Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
} finally {
tcpserver = null
} }
return false return false
} }

View File

@@ -27,8 +27,8 @@ import kotlin.io.path.absolutePathString
@Suppress("unused") @Suppress("unused")
class TCP_Android_Command_Server { class TCP_Android_Command_Server {
private var tcpserver: ServerSocket? = null lateinit var tcpserver: ServerSocket
private var job: Job? = null lateinit var job: Job
private val socketMap = mutableMapOf<String, Socket>() private val socketMap = mutableMapOf<String, Socket>()
lateinit var logcb: Consumer<String> lateinit var logcb: Consumer<String>
private val listUserLogin = mutableListOf<userLogin>() private val listUserLogin = mutableListOf<userLogin>()
@@ -46,13 +46,13 @@ class TCP_Android_Command_Server {
val tcp = ServerSocket(port) val tcp = ServerSocket(port)
tcpserver = tcp tcpserver = tcp
job = CoroutineScope(Dispatchers.IO).launch { job = CoroutineScope(Dispatchers.IO).launch {
Logger.info { "TCP server started" } Logger.info { "TCP Android server started on port $port" }
while (isActive) { while (isActive) {
if (tcpserver?.isClosed == true) break if (tcpserver.isClosed) break
try { try {
tcpserver?.accept().use { socket -> tcpserver.accept().use { socket ->
{ {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
if (socket != null) { if (socket != null) {
// key is IP address only // key is IP address only
val key: String = socket.inetAddress.hostAddress val key: String = socket.inetAddress.hostAddress
@@ -416,20 +416,18 @@ class TCP_Android_Command_Server {
*/ */
fun StopTcpCommand(): Boolean { fun StopTcpCommand(): Boolean {
try { try {
tcpserver?.close() tcpserver.close()
runBlocking { runBlocking {
socketMap.values.forEach { socketMap.values.forEach {
it.close() it.close()
} }
socketMap.clear() socketMap.clear()
job?.join() job.join()
} }
Logger.info { "StopTcpCommand success" } Logger.info { "StopTcpCommand success" }
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" } Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
} finally {
tcpserver = null
} }
return false return false
} }

View File

@@ -14,8 +14,8 @@ import java.util.function.Consumer
@Suppress("unused") @Suppress("unused")
class TCP_PC_Command_Server { class TCP_PC_Command_Server {
private var tcpserver: ServerSocket? = null lateinit var tcpserver: ServerSocket
private var job: Job? = null lateinit var job: Job
private val socketMap = mutableMapOf<String, Socket>() private val socketMap = mutableMapOf<String, Socket>()
/** /**
@@ -31,11 +31,11 @@ class TCP_PC_Command_Server {
job = CoroutineScope(Dispatchers.IO).launch { job = CoroutineScope(Dispatchers.IO).launch {
Logger.info { "TCP server started" } Logger.info { "TCP server started" }
while (isActive) { while (isActive) {
if (tcpserver?.isClosed == true) break if (tcpserver.isClosed) break
try { try {
tcpserver?.accept().use { socket -> tcpserver.accept().use { socket ->
{ {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
if (socket != null) { if (socket != null) {
val key : String = socket.inetAddress.hostAddress+":"+socket.port val key : String = socket.inetAddress.hostAddress+":"+socket.port
socketMap[key] = socket socketMap[key] = socket
@@ -81,20 +81,18 @@ class TCP_PC_Command_Server {
*/ */
fun StopTcpCommand(): Boolean { fun StopTcpCommand(): Boolean {
try { try {
tcpserver?.close() tcpserver.close()
runBlocking { runBlocking {
socketMap.values.forEach { socketMap.values.forEach {
it.close() it.close()
} }
socketMap.clear() socketMap.clear()
job?.join() job.join()
} }
Logger.info { "StopTcpCommand success" } Logger.info { "StopTcpCommand success" }
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" } Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
} finally {
tcpserver = null
} }
return false return false
} }

View File

@@ -20,4 +20,8 @@ data class Log(
return Log(0u, date, time, machine, description) return Log(0u, date, time, machine, description)
} }
} }
override fun toString() : String {
return "$datenya $timenya [$machine] $description"
}
} }

View File

@@ -6,11 +6,13 @@ import content.Category
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import max_channel
import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.tinylog.Logger import org.tinylog.Logger
import java.sql.Connection import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import java.util.function.Consumer import java.util.function.Consumer
import kotlin.math.max
/** /**
* A class to manage a connection to a MariaDB database. * A class to manage a connection to a MariaDB database.
@@ -1464,7 +1466,7 @@ class MariaDB(
val countResult = statement?.executeQuery("SELECT COUNT(*) AS count FROM ${super.dbName}") val countResult = statement?.executeQuery("SELECT COUNT(*) AS count FROM ${super.dbName}")
if (countResult?.next() == true) { if (countResult?.next() == true) {
val count = countResult.getInt("count") val count = countResult.getInt("count")
if (count == 0) { if (count < max_channel) {
Logger.info("SoundChannel table is empty, populating with default channels" as Any) Logger.info("SoundChannel table is empty, populating with default channels" as Any)
Clear() Clear()
} }
@@ -1578,9 +1580,9 @@ class MariaDB(
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
Logger.info("${super.dbName} table cleared" as Any) Logger.info("${super.dbName} table cleared" as Any)
List.clear() List.clear()
// create new rows from 1 to 64 with description "Channel 01" to "Channel 64" and empty ip // create new rows from 1 to 64 with description "Channel 1" to "Channel 64" and empty ip
for (i in 1..64) { for (i in 1..max_channel) {
val channel = String.format("Channel %02d", i) val channel = String.format("Channel %d", i)
val insertStatement = val insertStatement =
connection.prepareStatement("INSERT INTO ${super.dbName} (channel, ip) VALUES (?, ?)") connection.prepareStatement("INSERT INTO ${super.dbName} (channel, ip) VALUES (?, ?)")
insertStatement?.setString(1, channel) insertStatement?.setString(1, channel)
@@ -1728,13 +1730,13 @@ class MariaDB(
statement?.setString(4, data.description) statement?.setString(4, data.description)
val rowsAffected = statement?.executeUpdate() val rowsAffected = statement?.executeUpdate()
if (rowsAffected != null && rowsAffected > 0) { if (rowsAffected != null && rowsAffected > 0) {
Logger.info("Log added: [$data.datenya $data.timenya] [$data.machine] $data.description" as Any) Logger.info{"Log added : $data"}
return true return true
} else { } else {
Logger.warn("No log entry added for: [$data.datenya $data.timenya] [$data.machine] $data.description" as Any) Logger.warn{"Failed to add log entry : $data"}
} }
} catch (e: Exception) { } catch (e: Exception) {
Logger.error("Error adding log entry: ${e.message}" as Any) Logger.error{"Error adding log entry: ${e.message}"}
} }
return false return false
} }

View File

@@ -1,4 +1,7 @@
package database package database
@Suppress("unused") data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String){
data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String) override fun toString(): String {
return "QueuePaging(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', BroadcastZones='$BroadcastZones')"
}
}

View File

@@ -1,4 +1,8 @@
package database package database
@Suppress("unused") data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String){
data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String)
override fun toString(): String {
return "QueueTable(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', SB_TAGS='$SB_TAGS', BroadcastZones='$BroadcastZones', Repeat=$Repeat, Language='$Language')"
}
}