commit 02/10/2025
This commit is contained in:
11
src/Main.kt
11
src/Main.kt
@@ -1,4 +1,5 @@
|
||||
import audio.AudioPlayer
|
||||
import audio.UDPReceiver
|
||||
import barix.BarixConnection
|
||||
import barix.TCP_Barix_Command_Server
|
||||
import com.sun.jna.Platform
|
||||
@@ -20,6 +21,7 @@ import kotlin.concurrent.fixedRateTimer
|
||||
lateinit var db: MariaDB
|
||||
lateinit var audioPlayer: AudioPlayer
|
||||
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
|
||||
lateinit var udpreceiver: UDPReceiver
|
||||
const val version = "0.0.2 (23/09/2025)"
|
||||
|
||||
// dipakai untuk pilih voice type, bisa diganti via web nanti
|
||||
@@ -74,10 +76,16 @@ fun main() {
|
||||
))
|
||||
web.Start()
|
||||
|
||||
udpreceiver = UDPReceiver()
|
||||
if (udpreceiver.Start()) {
|
||||
Logger.info { "UDP Receiver started on port 5002" }
|
||||
} else {
|
||||
Logger.error { "Failed to start UDP Receiver on port 5002" }
|
||||
}
|
||||
|
||||
val androidserver = TCP_Android_Command_Server()
|
||||
androidserver.StartTcpServer(5003){
|
||||
Logger.info { it }
|
||||
|
||||
db.logDB.Add(Log.NewLog("ANDROID", it))
|
||||
}
|
||||
|
||||
@@ -123,6 +131,7 @@ fun main() {
|
||||
androidserver.StopTcpCommand()
|
||||
onlinechecker.cancel()
|
||||
web.Stop()
|
||||
udpreceiver.Stop()
|
||||
audioPlayer.Close()
|
||||
db.close()
|
||||
Logger.info { "All services stopped, exiting application." }
|
||||
|
||||
@@ -126,6 +126,14 @@ class AudioPlayer (var samplingrate: Int) {
|
||||
return result
|
||||
}
|
||||
|
||||
fun WavWriter(data: ByteArray, target: String, callback: BiConsumer<Boolean, String>) {
|
||||
val source = AudioFileInfo()
|
||||
source.bytes = data
|
||||
source.fileName = "In-Memory Data"
|
||||
val sources = listOf(source)
|
||||
WavWriter(sources, target, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the audio data from the sources to a WAV file.
|
||||
* @param sources List of AudioFileInfo objects containing the audio data to write.
|
||||
|
||||
79
src/audio/UDPReceiver.kt
Normal file
79
src/audio/UDPReceiver.kt
Normal file
@@ -0,0 +1,79 @@
|
||||
package audio
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.DatagramSocket
|
||||
import java.util.function.Consumer
|
||||
|
||||
@Suppress("unused")
|
||||
/**
|
||||
* UDPReceiver is a class that listens for UDP packets on a specified port.
|
||||
* It is designed to run in a separate thread and can be stopped when no longer needed.
|
||||
* @param portnumber The port to listen for incoming UDP packets (default is 5002
|
||||
*/
|
||||
class UDPReceiver(val portnumber: Int = 5002) {
|
||||
private lateinit var socket : DatagramSocket
|
||||
private var isRunning = false
|
||||
private val dataCallback = mutableMapOf<String, Consumer<ByteArray>>()
|
||||
|
||||
/**
|
||||
* Start listening for UDP packets on the specified port.
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
fun Start() : Boolean{
|
||||
return try {
|
||||
socket = DatagramSocket(portnumber)
|
||||
isRunning = true
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
while(isRunning){
|
||||
try {
|
||||
val buffer = ByteArray(2048)
|
||||
val packet = java.net.DatagramPacket(buffer, buffer.size)
|
||||
socket.receive(packet)
|
||||
val data = ByteArray(packet.length)
|
||||
System.arraycopy(packet.data, 0, data, 0, packet.length)
|
||||
dataCallback[packet.address.hostAddress].let {
|
||||
it?.accept(data)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
if (isRunning) {
|
||||
println("Error receiving UDP packet: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback function to be called when data is received from the specified IP address.
|
||||
* @param ipaddress The IP address to listen for incoming UDP packets.
|
||||
* @param callback A callback function that will be called when data is received from the specified IP address.
|
||||
*/
|
||||
fun RequestDataFrom(ipaddress: String, callback: Consumer<ByteArray>){
|
||||
dataCallback[ipaddress] = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the callback function for the specified IP address.
|
||||
* @param ipaddress The IP address to stop listening for incoming UDP packets.
|
||||
*/
|
||||
fun StopRequestDataFrom(ipaddress: String){
|
||||
dataCallback.remove(ipaddress)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening for UDP packets and close the socket.
|
||||
*/
|
||||
fun Stop(){
|
||||
if (isRunning){
|
||||
isRunning = false
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/commandServer/PagingJob.kt
Normal file
45
src/commandServer/PagingJob.kt
Normal file
@@ -0,0 +1,45 @@
|
||||
package commandServer
|
||||
|
||||
import codes.Somecodes.Companion.PagingResult_directory
|
||||
import codes.Somecodes.Companion.filenameformat
|
||||
import org.tinylog.Logger
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.file.Path
|
||||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* Class to handle a paging job, storing incoming audio data and metadata.
|
||||
* @param fromIP The IP address from which the paging data is received.
|
||||
* @param broadcastzones The zones to which the paging is broadcasted, is a semicolon-separated string.
|
||||
*/
|
||||
class PagingJob(val fromIP: String, val broadcastzones: String) {
|
||||
val filePath : Path = PagingResult_directory.resolve(LocalDateTime.now().format(filenameformat)+"_RAW.wav")
|
||||
private val bos : ByteArrayOutputStream = ByteArrayOutputStream()
|
||||
var totalBytesReceived = 0; private set
|
||||
var isRunning = true; private set
|
||||
|
||||
|
||||
/**
|
||||
* Adds incoming audio data to the job.
|
||||
* @param data The byte array containing audio data.
|
||||
* @param length The number of bytes to write from the data array.
|
||||
*/
|
||||
fun addData(data: ByteArray, length: Int) {
|
||||
Logger.info{"PagingJob from $fromIP, zones: $broadcastzones, received $length bytes"}
|
||||
bos.write(data, 0, length)
|
||||
totalBytesReceived += length
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the accumulated audio data as a byte array.
|
||||
* @return A byte array containing all received audio data.
|
||||
*/
|
||||
fun GetData(): ByteArray {
|
||||
return bos.toByteArray()
|
||||
}
|
||||
|
||||
fun Close(){
|
||||
bos.close()
|
||||
isRunning = false
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package commandServer
|
||||
|
||||
import audioPlayer
|
||||
import codes.Somecodes.Companion.ValidString
|
||||
import codes.Somecodes.Companion.datetimeformat1
|
||||
import content.Language
|
||||
import database.Messagebank
|
||||
import database.QueuePaging
|
||||
import database.QueueTable
|
||||
import database.Soundbank
|
||||
import db
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -11,11 +16,14 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.tinylog.Logger
|
||||
import udpreceiver
|
||||
import java.net.ServerSocket
|
||||
import java.net.Socket
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.time.LocalDateTime
|
||||
import java.util.function.Consumer
|
||||
import kotlin.io.path.absolutePathString
|
||||
|
||||
@Suppress("unused")
|
||||
class TCP_Android_Command_Server {
|
||||
@@ -24,6 +32,7 @@ class TCP_Android_Command_Server {
|
||||
private val socketMap = mutableMapOf<String, Socket>()
|
||||
lateinit var logcb: Consumer<String>
|
||||
private val listUserLogin = mutableListOf<userLogin>()
|
||||
private val listOnGoingPaging = mutableMapOf<String, PagingJob>()
|
||||
|
||||
/**
|
||||
* Start TCP Command Server
|
||||
@@ -45,7 +54,8 @@ class TCP_Android_Command_Server {
|
||||
{
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
if (socket != null) {
|
||||
val key: String = socket.inetAddress.hostAddress + ":" + socket.port
|
||||
// key is IP address only
|
||||
val key: String = socket.inetAddress.hostAddress
|
||||
socketMap[key] = socket
|
||||
Logger.info { "Start communicating with $key" }
|
||||
socket.getInputStream().let { din ->
|
||||
@@ -110,23 +120,31 @@ class TCP_Android_Command_Server {
|
||||
return ByteArray(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process command from Android client
|
||||
* @param key The client IP address
|
||||
* @param cmd The command string
|
||||
* @param cb Callback to send reply string
|
||||
*/
|
||||
private fun process_command(key: String, cmd: String, cb: Consumer<String>) {
|
||||
Logger.info { "Command from $key : $cmd" }
|
||||
val parts = cmd.split(";").map { it.trim() }.filter { it.isNotBlank() }.map { it.uppercase() }
|
||||
when (parts[0]) {
|
||||
"GETLOGIN" -> {
|
||||
// Android login request
|
||||
val username = parts.getOrElse(1) { "" }
|
||||
val password = parts.getOrElse(2) { "" }
|
||||
if (ValidString(username) && ValidString(password)) {
|
||||
if (db.userDB.List.any{it.username==username && it.password==password}) {
|
||||
cb.accept("LOGIN;TRUE@")
|
||||
val existing = listUserLogin.find { it.ip == key}
|
||||
if (existing!=null){
|
||||
existing.username = username
|
||||
} else{
|
||||
listUserLogin.add(userLogin(key, username))
|
||||
}
|
||||
cb.accept("LOGIN;TRUE@")
|
||||
logcb.accept("Android Login success from $key as $username")
|
||||
return
|
||||
} else {
|
||||
logcb.accept("Android Login failed from $key as $username")
|
||||
cb.accept("LOGIN;FALSE@")
|
||||
@@ -137,27 +155,85 @@ class TCP_Android_Command_Server {
|
||||
}
|
||||
}
|
||||
|
||||
"PCMFILE_START" -> {
|
||||
// TODO read coding here
|
||||
"PCMFILE_START","STARTPAGINGAND" -> {
|
||||
val zones = parts.getOrElse(3) { "" }.replace(",",";")
|
||||
if (ValidString(zones)){
|
||||
// create pagingjob
|
||||
val pj = PagingJob(key, zones)
|
||||
// masukin ke list
|
||||
listOnGoingPaging[key] = pj
|
||||
|
||||
// start minta data dari udpreceiver
|
||||
udpreceiver.RequestDataFrom(key){
|
||||
// push data ke paging job
|
||||
pj.addData(it, it.size)
|
||||
}
|
||||
logcb.accept("Paging started from Android $key")
|
||||
cb.accept(parts[0]+";OK@")
|
||||
return
|
||||
} else logcb.accept("Paging start from Android $key failed, empty zones")
|
||||
cb.accept(parts[0]+";NG@")
|
||||
}
|
||||
|
||||
"PCMFILE_STOP" -> {
|
||||
// TODO read coding here
|
||||
}
|
||||
"PCMFILE_STOP","STOPPAGINGAND" -> {
|
||||
val pj = listOnGoingPaging[key]
|
||||
if (pj!=null){
|
||||
listOnGoingPaging.remove(key)
|
||||
udpreceiver.StopRequestDataFrom(key)
|
||||
logcb.accept("Paging stopped from Android $key")
|
||||
cb.accept(parts[0]+";OK@")
|
||||
// get remaining data
|
||||
val data = pj.GetData()
|
||||
pj.Close()
|
||||
audioPlayer.WavWriter(data, pj.filePath.absolutePathString()){
|
||||
success, message ->
|
||||
if (success){
|
||||
// insert to paging queue
|
||||
val qp = QueuePaging(
|
||||
0u,
|
||||
LocalDateTime.now().format(datetimeformat1),
|
||||
"PAGING",
|
||||
"NORMAL",
|
||||
pj.filePath.absolutePathString(),
|
||||
pj.broadcastzones
|
||||
)
|
||||
if (db.queuepagingDB.Add(qp)){
|
||||
logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
|
||||
cb.accept(parts[0]+";OK@")
|
||||
} else {
|
||||
logcb.accept("Failed to insert paging audio to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
|
||||
cb.accept(parts[0]+";NG@")
|
||||
}
|
||||
|
||||
"STARTPAGINGAND" -> {
|
||||
// TODO read coding here
|
||||
}
|
||||
} else {
|
||||
logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : $message")
|
||||
cb.accept(parts[0]+";NG@")
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
logcb.accept("Paging stop from Android $key failed, no ongoing paging")
|
||||
cb.accept(parts[0]+";NG@")
|
||||
}
|
||||
|
||||
"STOPPAGINGAND" -> {
|
||||
// TODO read coding here
|
||||
}
|
||||
|
||||
"CANCELPAGINGAND" -> {
|
||||
// TODO read coding here
|
||||
val pj = listOnGoingPaging[key]
|
||||
if (pj!=null){
|
||||
pj.Close()
|
||||
listOnGoingPaging.remove(key)
|
||||
udpreceiver.StopRequestDataFrom(key)
|
||||
logcb.accept("Paging from Android $key cancelled")
|
||||
cb.accept("CANCELPAGINGAND;OK@")
|
||||
return
|
||||
} else logcb.accept("Paging cancel from Android $key failed, no ongoing paging")
|
||||
cb.accept("CANCELPAGINGAND;NG@")
|
||||
|
||||
}
|
||||
|
||||
"STARTINITIALIZE" -> {
|
||||
// pengiriman variabel ke Android
|
||||
val username = parts.getOrElse(1) { "" }
|
||||
if (ValidString(username)){
|
||||
val userlogin = listUserLogin.find { it.username == username }
|
||||
@@ -183,8 +259,11 @@ class TCP_Android_Command_Server {
|
||||
.filter { xx -> xx.all{it.isDigit()} }
|
||||
// iterasi setiap ANN_ID
|
||||
.forEach { annid ->
|
||||
// masukin ke VARMESSAGES yang unik secara ANN_ID dan Description
|
||||
VARMESSAGES.addAll(db.messageDB.List.distinctBy { it.ANN_ID }.distinctBy { it.Description })
|
||||
// masukin ke VARMESSAGES yang unik secara ANN_ID dan Language
|
||||
val xx = db.messageDB.List
|
||||
.filter{ it.ANN_ID == annid.toUInt() }
|
||||
.distinctBy { it.Language }
|
||||
VARMESSAGES.addAll(xx)
|
||||
}
|
||||
result.append("MSGTOTAL;").append(VARMESSAGES.size).append("@")
|
||||
// VAR AP TOTAL
|
||||
@@ -240,7 +319,16 @@ class TCP_Android_Command_Server {
|
||||
cb.accept(result.toString())
|
||||
|
||||
result.clear()
|
||||
//TODO append MSG
|
||||
|
||||
//Append MSG, for Android only Indonesia and English
|
||||
VARMESSAGES.groupBy { it.ANN_ID }.forEach { (ann_id, value) ->
|
||||
result.append("MSG;").append(ann_id)
|
||||
result.append(";")
|
||||
value.find { it.Language== Language.INDONESIA.name }?.let {result.append(it.Message_Detail)} ?: result.append("NA")
|
||||
result.append(";")
|
||||
value.find {it.Language== Language.ENGLISH.name }?.let {result.append(it.Message_Detail)} ?: result.append("NA")
|
||||
result.append("@")
|
||||
}
|
||||
|
||||
// append VARAP
|
||||
VARAPTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank ->
|
||||
@@ -274,7 +362,7 @@ class TCP_Android_Command_Server {
|
||||
cb.accept(result.toString())
|
||||
logcb.accept("All variables sent to $key with username $username")
|
||||
|
||||
|
||||
return
|
||||
} else logcb.accept("STARTINITIALIZE failed from $key with username $username not found in userDB")
|
||||
} else logcb.accept("STARTINITIALIZE failed from $key with unregistered username $username")
|
||||
} else logcb.accept("STARTINITIALIZE failed from $key with empty username")
|
||||
@@ -282,7 +370,37 @@ class TCP_Android_Command_Server {
|
||||
}
|
||||
|
||||
"BROADCASTAND" -> {
|
||||
// TODO read coding here
|
||||
// semi auto dari android, masukin ke queue table
|
||||
val desc = parts.getOrElse(1) { "" }
|
||||
val lang = parts.getOrElse(2) { "" }.replace(",",";")
|
||||
val tags = parts.getOrElse(3) { "" }.replace(",",";")
|
||||
val zone = parts.getOrElse(4) { "" }.replace(",",";")
|
||||
if (ValidString(desc)){
|
||||
if (ValidString(lang)){
|
||||
if (ValidString(tags)){
|
||||
if (ValidString(zone)){
|
||||
val qt = QueueTable(
|
||||
0u,
|
||||
LocalDateTime.now().format(datetimeformat1),
|
||||
"ANDROID",
|
||||
"SOUNDBANK",
|
||||
desc,
|
||||
tags,
|
||||
zone,
|
||||
1u,
|
||||
lang
|
||||
)
|
||||
if (db.queuetableDB.Add(qt)){
|
||||
logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} inserted. Message: $desc;$lang;$tags;$zone")
|
||||
cb.accept("BROADCASTAND;OK@")
|
||||
return
|
||||
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, cannot add to queue table")
|
||||
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty zone")
|
||||
} else logcb.accept("Broadcsast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty tags")
|
||||
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty language")
|
||||
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty description")
|
||||
cb.accept("NG@")
|
||||
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
Reference in New Issue
Block a user