Commit 12/08/2025
This commit is contained in:
20
.idea/artifacts/EWS_Nanopi_Duo2.xml
generated
Normal file
20
.idea/artifacts/EWS_Nanopi_Duo2.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact name="EWS Nanopi Duo2">
|
||||
<output-path>$PROJECT_DIR$/out/artifacts/EWS_Nanopi_Duo2</output-path>
|
||||
<root id="root">
|
||||
<element id="archive" name="EWS_POC.jar">
|
||||
<element id="directory" name="META-INF">
|
||||
<element id="file-copy" path="$PROJECT_DIR$/meta/nanopi duo2/META-INF/MANIFEST.MF" />
|
||||
</element>
|
||||
<element id="module-output" name="EWS_POC" />
|
||||
</element>
|
||||
<element id="library" level="project" name="jetbrains.kotlinx.coroutines.core" />
|
||||
<element id="library" level="project" name="java.websocket.Java.WebSocket" />
|
||||
<element id="library" level="project" name="KotlinJavaRuntime" />
|
||||
<element id="library" level="project" name="net.java.dev.jna" />
|
||||
<element id="library" level="project" name="io.javalin.bundle" />
|
||||
<element id="dir-copy" path="$PROJECT_DIR$/html" />
|
||||
<element id="dir-copy" path="$PROJECT_DIR$/libs/linux-arm" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
||||
68
.idea/artifacts/EWS_POC_jar.xml
generated
68
.idea/artifacts/EWS_POC_jar.xml
generated
@@ -1,68 +0,0 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="EWS_POC:jar">
|
||||
<output-path>$PROJECT_DIR$/out/artifacts/EWS_POC_jar</output-path>
|
||||
<root id="archive" name="EWS_POC.jar">
|
||||
<element id="module-output" name="EWS_POC" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.10.2/kotlinx-coroutines-core-1.10.2.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.10.2/kotlinx-coroutines-core-jvm-1.10.2.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.0/kotlin-stdlib-2.1.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.17.0/jna-5.17.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/javalin/javalin-bundle/6.7.0/javalin-bundle-6.7.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/javalin/javalin/6.7.0/javalin-6.7.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-server/11.0.25/jetty-server-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-http/11.0.25/jetty-http-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-server/11.0.25/websocket-jetty-server-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-servlet/11.0.25/jetty-servlet-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-security/11.0.25/jetty-security-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-webapp/11.0.25/jetty-webapp-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-xml/11.0.25/jetty-xml-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-api/11.0.25/websocket-jetty-api-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-common/11.0.25/websocket-jetty-common-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-core-common/11.0.25/websocket-core-common-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-servlet/11.0.25/websocket-servlet-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-core-server/11.0.25/websocket-core-server-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/javalin/javalin-context-mock/6.7.0/javalin-context-mock-6.7.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/toolchain/jetty-jakarta-servlet-api/5.0.2/jetty-jakarta-servlet-api-5.0.2.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/javalin/javalin-testtools/6.7.0/javalin-testtools-6.7.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/squareup/okhttp3/okhttp/4.12.0/okhttp-4.12.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/squareup/okio/okio/3.6.0/okio-3.6.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/squareup/okio/okio-jvm/3.6.0/okio-jvm-3.6.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.10/kotlin-stdlib-common-1.9.10.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/javalin/javalin-micrometer/6.7.0/javalin-micrometer-6.7.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/micrometer/micrometer-core/1.14.5/micrometer-core-1.14.5.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/micrometer/micrometer-commons/1.14.5/micrometer-commons-1.14.5.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/micrometer/micrometer-observation/1.14.5/micrometer-observation-1.14.5.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/hdrhistogram/HdrHistogram/2.2.2/HdrHistogram-2.2.2.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/latencyutils/LatencyUtils/2.0.3/LatencyUtils-2.0.3.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/io/micrometer/micrometer-jetty11/1.14.5/micrometer-jetty11-1.14.5.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-databind/2.18.3/jackson-databind-2.18.3.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-annotations/2.18.3/jackson-annotations-2.18.3.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-core/2.18.3/jackson-core-2.18.3.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/module/jackson-module-kotlin/2.18.3/jackson-module-kotlin-2.18.3.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.18.3/jackson-datatype-jsr310-2.18.3.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/aayushatharva/brotli4j/brotli4j/1.18.0/brotli4j-1.18.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/aayushatharva/brotli4j/service/1.18.0/service-1.18.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/aayushatharva/brotli4j/native-windows-x86_64/1.18.0/native-windows-x86_64-1.18.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/http2/http2-server/11.0.25/http2-server-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/http2/http2-common/11.0.25/http2-common-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/http2/http2-hpack/11.0.25/http2-hpack-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-alpn-conscrypt-server/11.0.25/jetty-alpn-conscrypt-server-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/conscrypt/conscrypt-openjdk-uber/2.5.2/conscrypt-openjdk-uber-2.5.2.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-alpn-server/11.0.25/jetty-alpn-server-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-io/11.0.25/jetty-io-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-util/11.0.25/jetty-util-11.0.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/ch/qos/logback/logback-classic/1.5.18/logback-classic-1.5.18.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/ch/qos/logback/logback-core/1.5.18/logback-core-1.5.18.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.25/kotlin-stdlib-jdk8-1.9.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.25/kotlin-stdlib-1.9.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.25/kotlin-stdlib-jdk7-1.9.25.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/java-websocket/Java-WebSocket/1.6.0/Java-WebSocket-1.6.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.13/slf4j-api-2.0.13.jar" path-in-jar="/" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
||||
3
.idea/deployment.xml
generated
3
.idea/deployment.xml
generated
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PublishConfigData" serverName="nanopi gtcdevice01" remoteFilesAllowedToDisappearOnAutoupload="false">
|
||||
<component name="PublishConfigData" serverName="nanopi gtcdevice01" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="false">
|
||||
<option name="confirmBeforeUploading" value="false" />
|
||||
<serverData>
|
||||
<paths name="nanopi gtcdevice01">
|
||||
<serverdata>
|
||||
|
||||
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -4,6 +4,7 @@
|
||||
<inspection_tool class="AiaStyle" enabled="false" level="TYPO" enabled_by_default="false" />
|
||||
<inspection_tool class="AssignedValueIsNeverRead" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ClassName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ConstPropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="FunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: MainKt
|
||||
|
||||
27
meta/nanopi duo2/META-INF/MANIFEST.MF
Normal file
27
meta/nanopi duo2/META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,27 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: MainKt
|
||||
Class-Path: kotlinx-coroutines-core-1.10.2.jar kotlinx-coroutines-core-j
|
||||
vm-1.10.2.jar annotations-23.0.0.jar kotlin-stdlib-2.1.0.jar Java-WebSo
|
||||
cket-1.6.0.jar slf4j-api-2.0.13.jar kotlin-stdlib-2.2.0.jar annotations
|
||||
-13.0.jar jna-5.17.0.jar javalin-bundle-6.7.0.jar javalin-6.7.0.jar slf
|
||||
4j-api-2.0.17.jar jetty-server-11.0.25.jar jetty-http-11.0.25.jar webso
|
||||
cket-jetty-server-11.0.25.jar jetty-servlet-11.0.25.jar jetty-security-
|
||||
11.0.25.jar jetty-webapp-11.0.25.jar jetty-xml-11.0.25.jar websocket-je
|
||||
tty-api-11.0.25.jar websocket-jetty-common-11.0.25.jar websocket-core-c
|
||||
ommon-11.0.25.jar websocket-servlet-11.0.25.jar websocket-core-server-1
|
||||
1.0.25.jar javalin-context-mock-6.7.0.jar jetty-jakarta-servlet-api-5.0
|
||||
.2.jar javalin-testtools-6.7.0.jar okhttp-4.12.0.jar okio-3.6.0.jar oki
|
||||
o-jvm-3.6.0.jar kotlin-stdlib-common-1.9.10.jar javalin-micrometer-6.7.
|
||||
0.jar micrometer-core-1.14.5.jar micrometer-commons-1.14.5.jar micromet
|
||||
er-observation-1.14.5.jar HdrHistogram-2.2.2.jar LatencyUtils-2.0.3.jar
|
||||
micrometer-jetty11-1.14.5.jar jackson-databind-2.18.3.jar jackson-anno
|
||||
tations-2.18.3.jar jackson-core-2.18.3.jar jackson-module-kotlin-2.18.3
|
||||
.jar kotlin-reflect-1.8.10.jar jackson-datatype-jsr310-2.18.3.jar brotl
|
||||
i4j-1.18.0.jar service-1.18.0.jar native-windows-x86_64-1.18.0.jar http
|
||||
2-server-11.0.25.jar http2-common-11.0.25.jar http2-hpack-11.0.25.jar j
|
||||
etty-alpn-conscrypt-server-11.0.25.jar conscrypt-openjdk-uber-2.5.2.jar
|
||||
jetty-alpn-server-11.0.25.jar jetty-io-11.0.25.jar jetty-util-11.0.25.
|
||||
jar logback-classic-1.5.18.jar logback-core-1.5.18.jar kotlin-stdlib-jd
|
||||
k8-1.9.25.jar kotlin-stdlib-1.9.25.jar annotations-13.0.jar kotlin-stdl
|
||||
ib-jdk7-1.9.25.jar
|
||||
|
||||
68
src/Main.kt
68
src/Main.kt
@@ -14,7 +14,10 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.slf4j.LoggerFactory
|
||||
import sbc.DigitalOutput
|
||||
import sbc.NanopiDuo2
|
||||
import somecodes.Codes
|
||||
import web.WsCommand
|
||||
import java.util.function.BiFunction
|
||||
@@ -24,11 +27,32 @@ import java.util.function.BiFunction
|
||||
fun main() {
|
||||
val logger = LoggerFactory.getLogger("Main")
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
logger.info("Application started, version 0.0.8")
|
||||
|
||||
val cfg = configFile()
|
||||
cfg.Load()
|
||||
|
||||
var relay1 : DigitalOutput? = null
|
||||
var relay2 : DigitalOutput? = null
|
||||
var commandLED : DigitalOutput? = null
|
||||
var streamingLED : DigitalOutput? = null
|
||||
|
||||
runBlocking {
|
||||
relay1 = DigitalOutput(NanopiDuo2.CLK.linuxGpio, "Relay1", true)
|
||||
relay2 = DigitalOutput(NanopiDuo2.MISO.linuxGpio, "Relay2", true)
|
||||
commandLED = DigitalOutput(NanopiDuo2.TX1.linuxGpio, "CommandLED", true)
|
||||
streamingLED = DigitalOutput(NanopiDuo2.RX1.linuxGpio, "StreamingLED", true)
|
||||
relay1.setOFF()
|
||||
relay2.setOFF()
|
||||
commandLED.setOFF()
|
||||
streamingLED.setOFF()
|
||||
}
|
||||
|
||||
|
||||
var audioID = 0
|
||||
val preferedAudioDevice = "Speakers"
|
||||
val preferedAudioDevice = "USB Audio"
|
||||
AudioUtility.LoadLibraries()
|
||||
AudioUtility.PrintVersion()
|
||||
|
||||
AudioUtility.DetectPlaybackDevices().forEach { pair ->
|
||||
logger.info("Device ID: ${pair.first}, Name: ${pair.second}")
|
||||
@@ -36,10 +60,9 @@ fun main() {
|
||||
audioID = pair.first
|
||||
}
|
||||
}
|
||||
if (audioID!=0){
|
||||
val initsuccess = AudioUtility.InitDevice(audioID,44100)
|
||||
logger.info("Audio Device $audioID initialized: $initsuccess")
|
||||
}
|
||||
if (audioID==0) audioID = 1 // fallback to first device if preferred not found
|
||||
val initsuccess = AudioUtility.InitDevice(audioID,44100)
|
||||
logger.info("Audio Device $audioID initialized: $initsuccess")
|
||||
|
||||
// for Zello Client
|
||||
val o = OpusStreamReceiver(audioID)
|
||||
@@ -113,12 +136,16 @@ fun main() {
|
||||
|
||||
override fun onConnected() {
|
||||
logger.info("Connected to Zello server.")
|
||||
relay1?.setOFF()
|
||||
relay2?.setOFF()
|
||||
}
|
||||
|
||||
override fun onDisconnected(reason: String) {
|
||||
logger.info("Disconnected from Zello Server, reason: $reason")
|
||||
logger.info("Reconnecting after 10 seconds...")
|
||||
z.Stop()
|
||||
relay1?.setOFF()
|
||||
relay2?.setOFF()
|
||||
val e = this
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
|
||||
@@ -136,8 +163,10 @@ fun main() {
|
||||
// stop any previous playback
|
||||
afp?.Stop()
|
||||
afp = null
|
||||
|
||||
commandLED?.Blink()
|
||||
if (o.Start()){
|
||||
relay1?.setON()
|
||||
relay2?.setON()
|
||||
logger.info("Opus Receiver ready for streaming from $from for $For on channel $channel")
|
||||
} else {
|
||||
logger.info("Failed to start Opus Receiver for streaming from $from for $For on channel $channel")
|
||||
@@ -146,6 +175,9 @@ fun main() {
|
||||
|
||||
override fun onStopStreaming(from: String, For: String, channel: String) {
|
||||
o.Stop()
|
||||
relay1?.setON()
|
||||
relay2?.setOFF()
|
||||
commandLED?.Blink()
|
||||
logger.info("Opus Receiver stopped streaming from $from for $For on channel $channel")
|
||||
}
|
||||
|
||||
@@ -156,6 +188,7 @@ fun main() {
|
||||
data: ByteArray
|
||||
) {
|
||||
if (o.isPlaying) o.PushData(data)
|
||||
streamingLED?.Blink()
|
||||
}
|
||||
}
|
||||
z.Start(z_event)
|
||||
@@ -166,6 +199,7 @@ fun main() {
|
||||
when (source) {
|
||||
"setting" -> when(cmd.command){
|
||||
"getConfig" ->{
|
||||
commandLED?.Blink()
|
||||
val data = mapOf(
|
||||
"zelloUsername" to cfg.ZelloUsername,
|
||||
"zelloPassword" to cfg.ZelloPassword,
|
||||
@@ -187,6 +221,7 @@ fun main() {
|
||||
WsReply(cmd.command, objectMapper.writeValueAsString(data).trim())
|
||||
}
|
||||
"setZelloConfig" -> {
|
||||
commandLED?.Blink()
|
||||
try{
|
||||
val xx = objectMapper.readValue(cmd.data, object: TypeReference<Map<String, String>>() {})
|
||||
var changed = false
|
||||
@@ -220,6 +255,7 @@ fun main() {
|
||||
z = CreateZelloFromConfig()
|
||||
z.Start(z_event)
|
||||
|
||||
|
||||
WsReply(cmd.command,"success")
|
||||
} else WsReply(cmd.command,"No changes made")
|
||||
} catch (e: Exception){
|
||||
@@ -228,6 +264,7 @@ fun main() {
|
||||
|
||||
}
|
||||
"setMessageConfig"-> {
|
||||
commandLED?.Blink()
|
||||
try{
|
||||
|
||||
val xx = objectMapper.readValue(cmd.data, object : TypeReference<Map<String, String>>() {})
|
||||
@@ -278,6 +315,7 @@ fun main() {
|
||||
|
||||
"prerecordedbroadcast" -> when(cmd.command){
|
||||
"getMessageConfig" ->{
|
||||
commandLED?.Blink()
|
||||
val data = mapOf(
|
||||
"M1" to cfg.M1,
|
||||
"M2" to cfg.M2,
|
||||
@@ -291,6 +329,7 @@ fun main() {
|
||||
WsReply(cmd.command, objectMapper.writeValueAsString(data))
|
||||
}
|
||||
"getPlaybackStatus" ->{
|
||||
commandLED?.Blink()
|
||||
if (afp!=null && true==afp?.isPlaying){
|
||||
WsReply(cmd.command, "Playing: ${afp?.filename}, Duration: ${afp?.duration?.toInt()}, Elapsed: ${afp?.elapsed?.toInt()} seconds")
|
||||
} else {
|
||||
@@ -298,7 +337,7 @@ fun main() {
|
||||
}
|
||||
}
|
||||
"playMessage" ->{
|
||||
|
||||
commandLED?.Blink()
|
||||
afp?.Stop()
|
||||
afp = null
|
||||
// stop Opus Receiver if it is running
|
||||
@@ -317,14 +356,23 @@ fun main() {
|
||||
null
|
||||
}
|
||||
}
|
||||
relay1?.setOFF()
|
||||
relay2?.setOFF()
|
||||
if (filename!=null){
|
||||
try{
|
||||
afp= AudioFilePlayer(audioID, filename)
|
||||
afp?.Play { _ -> afp = null}
|
||||
afp?.Play { _ ->
|
||||
afp = null
|
||||
relay1?.setOFF()
|
||||
relay2?.setOFF()
|
||||
}
|
||||
relay1?.setON()
|
||||
relay2?.setON()
|
||||
WsReply(cmd.command,"success")
|
||||
|
||||
} catch (e: Exception){
|
||||
afp?.Stop()
|
||||
|
||||
afp = null
|
||||
WsReply(cmd.command, "failed: ${e.message}")
|
||||
}
|
||||
@@ -336,14 +384,18 @@ fun main() {
|
||||
|
||||
}
|
||||
"stopMessage" ->{
|
||||
commandLED?.Blink()
|
||||
afp?.Stop()
|
||||
afp = null
|
||||
relay1?.setOFF()
|
||||
relay2?.setOFF()
|
||||
WsReply(cmd.command,"success")
|
||||
}
|
||||
else -> WsReply(cmd.command,"Invalid command: ${cmd.command}")
|
||||
}
|
||||
"pocreceiver" -> when(cmd.command){
|
||||
"getZelloStatus" -> {
|
||||
commandLED?.Blink()
|
||||
var status = "Disconnected"
|
||||
if (z.currentChannel?.isNotBlank() == true){
|
||||
status = "Channel: ${z.currentChannel}, Online: ${z.isOnline}, Username: ${z.username}"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package audio
|
||||
|
||||
import audio.AudioUtility.Companion.bass
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -15,7 +16,7 @@ import kotlin.io.path.exists
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class AudioFilePlayer(deviceID: Int, val filename: String, device_samplingrate: Int = 48000) {
|
||||
private val bass: Bass = Bass.Instance
|
||||
|
||||
private var filehandle = 0
|
||||
var isPlaying = false
|
||||
val fileSize: Long
|
||||
@@ -25,6 +26,7 @@ class AudioFilePlayer(deviceID: Int, val filename: String, device_samplingrate:
|
||||
val fullpath = Codes.audioFilePath.resolve(filename)
|
||||
if (fullpath.exists()){
|
||||
if (AudioUtility.InitDevice(deviceID, device_samplingrate)) {
|
||||
AudioUtility.setVolumeOutput(deviceID,1.0f)
|
||||
filehandle = bass.BASS_StreamCreateFile(false, fullpath.absolutePathString(), 0, 0, 0)
|
||||
if (filehandle!=0){
|
||||
fileSize = bass.BASS_ChannelGetLength(filehandle, Bass.BASS_POS_BYTE)
|
||||
@@ -47,6 +49,7 @@ class AudioFilePlayer(deviceID: Int, val filename: String, device_samplingrate:
|
||||
|
||||
fun Play(finished: Consumer<Any> ) : Boolean{
|
||||
if (bass.BASS_ChannelPlay(filehandle, false)){
|
||||
bass.BASS_ChannelSetAttribute(filehandle, Bass.BASS_ATTRIB_VOL, 1.0f)
|
||||
elapsed = 0.0
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
isPlaying = true
|
||||
|
||||
@@ -1,15 +1,63 @@
|
||||
package audio
|
||||
import com.sun.jna.Platform
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
|
||||
@Suppress("unused")
|
||||
class AudioUtility {
|
||||
|
||||
private val logger : Logger = LoggerFactory.getLogger(AudioUtility::class.java)
|
||||
|
||||
|
||||
|
||||
companion object{
|
||||
private val bass = Bass.Instance
|
||||
val bass : Bass = Bass.Instance
|
||||
private val logger : Logger = LoggerFactory.getLogger(AudioUtility::class.java)
|
||||
|
||||
fun ExtractLibraries(parent: String, filename: String, targetPath: Path) : Path?{
|
||||
if (targetPath.resolve(filename).isRegularFile()) return targetPath.resolve(filename) // already exists
|
||||
val out = targetPath.resolve(filename)
|
||||
|
||||
try{
|
||||
AudioUtility::class.java.getResourceAsStream("$parent/$filename").use { ins ->
|
||||
requireNotNull(ins) { "Resource $parent/$filename not found" }
|
||||
Files.copy(ins, out, REPLACE_EXISTING)
|
||||
}
|
||||
out.toFile().setReadable(true, false)
|
||||
out.toFile().setExecutable(true, false)
|
||||
return out
|
||||
} catch (_: Exception){
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun LoadLibraries(){
|
||||
val runfolder = Path.of(System.getProperty("user.dir"))
|
||||
//logger.info("Checking for BASS libraries in $runfolder")
|
||||
val resourceprefix = Platform.RESOURCE_PREFIX
|
||||
logger.info("Resource prefix : $resourceprefix")
|
||||
val bass = ExtractLibraries(resourceprefix, System.mapLibraryName("bass"), runfolder)
|
||||
val bassopus = ExtractLibraries(resourceprefix, System.mapLibraryName("bassopus"), runfolder)
|
||||
if (bass!=null){
|
||||
System.load(bass.toString())
|
||||
}
|
||||
if (bassopus!=null){
|
||||
System.load(bassopus.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun PrintVersion(){
|
||||
logger.info("BASS Version: ${bass.BASS_GetVersion().toHexString()}")
|
||||
}
|
||||
|
||||
fun LoadPlugin(plugin: String){
|
||||
if (bass.BASS_PluginLoad(plugin, 0)>0){
|
||||
logger.info("Plugin $plugin loaded successfully")
|
||||
} else {
|
||||
logger.error("Failed to load plugin $plugin: ${bass.BASS_ErrorGetCode()}")
|
||||
}
|
||||
}
|
||||
|
||||
fun DetectPlaybackDevices() : List<Pair<Int, String>> {
|
||||
val result = ArrayList<Pair<Int, String>>()
|
||||
@@ -24,6 +72,14 @@ class AudioUtility {
|
||||
return result
|
||||
}
|
||||
|
||||
fun setVolumeOutput(deviceID: Int, value: Float){
|
||||
var vol = value
|
||||
if (vol < 0) vol = 0f
|
||||
if (vol > 1) vol = 1f
|
||||
bass.BASS_SetDevice(deviceID)
|
||||
bass.BASS_SetVolume(vol)
|
||||
}
|
||||
|
||||
fun InitDevice(deviceID: Int, device_samplingrate: Int = 48000) : Boolean {
|
||||
val dev = Bass.BASS_DEVICEINFO()
|
||||
if (bass.BASS_GetDeviceInfo(deviceID, dev)) {
|
||||
@@ -54,9 +110,7 @@ class AudioUtility {
|
||||
}
|
||||
}
|
||||
|
||||
init{
|
||||
logger.info("Bass Version = ${bass.BASS_GetVersion().toHexString()}")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.sun.jna.*;
|
||||
public interface Bass extends Library {
|
||||
|
||||
Bass Instance = Native.load("bass", Bass.class);
|
||||
|
||||
int BASSVERSION = 0x204; // API version
|
||||
String BASSVERSIONTEXT = "2.4";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package audio
|
||||
|
||||
import audio.AudioUtility.Companion.bass
|
||||
import com.sun.jna.Memory
|
||||
import com.sun.jna.Pointer
|
||||
import org.slf4j.Logger
|
||||
@@ -13,11 +14,11 @@ import org.slf4j.LoggerFactory
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class OpusStreamReceiver(val deviceID: Int, val samplingrate: Int = 16000) {
|
||||
private val bass = Bass.Instance
|
||||
private val bassopus = BASSOPUS.Instance
|
||||
private var filehandle = 0
|
||||
private val logger : Logger = LoggerFactory.getLogger(OpusStreamReceiver::class.java)
|
||||
var isPlaying = false
|
||||
val bassopus : BASSOPUS = BASSOPUS.Instance
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -34,9 +35,12 @@ class OpusStreamReceiver(val deviceID: Int, val samplingrate: Int = 16000) {
|
||||
opushead.channels = 1
|
||||
opushead.inputrate = samplingrate
|
||||
val procpush = Pointer(-1)
|
||||
AudioUtility.setVolumeOutput(deviceID, 1.0f)
|
||||
|
||||
filehandle = bassopus.BASS_OPUS_StreamCreate(opushead,0, procpush, null)
|
||||
if (filehandle != 0){
|
||||
if (bass.BASS_ChannelPlay(filehandle,false)){
|
||||
bass.BASS_ChannelSetAttribute(filehandle, Bass.BASS_ATTRIB_VOL, 1.0f)
|
||||
isPlaying = true
|
||||
return true
|
||||
} else logger.error("BASS_ChannelPlay failed for filehandle $filehandle, code ${bass.BASS_ErrorGetCode()}")
|
||||
|
||||
157
src/sbc/DigitalOutput.kt
Normal file
157
src/sbc/DigitalOutput.kt
Normal file
@@ -0,0 +1,157 @@
|
||||
package sbc
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.LoggerFactory
|
||||
import somecodes.Codes.Companion.gpioExportPath
|
||||
import somecodes.Codes.Companion.gpioPath
|
||||
import somecodes.Codes.Companion.gpioUnexportPath
|
||||
import somecodes.Codes.Companion.haveGpioSupport
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isDirectory
|
||||
|
||||
/**
|
||||
* Create a digital output for a GPIO pin.
|
||||
* @param linuxGpio The GPIO pin number in Linux.
|
||||
* @param name The name of the digital output.
|
||||
* @param activeHigh If true, the output is active high (default is true).
|
||||
* @property inited Indicates whether the digital output has been initialized.
|
||||
*
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class DigitalOutput(val linuxGpio: Int, val name: String, val activeHigh: Boolean = true) {
|
||||
var inited = false; private set
|
||||
private var valuepath: Path? = null
|
||||
private val logger = LoggerFactory.getLogger("DigitalOutput $name ($linuxGpio)")
|
||||
init{
|
||||
if (haveGpioSupport()){
|
||||
if (checkExists()){
|
||||
// already exists
|
||||
try{
|
||||
// try to set direction to output
|
||||
val dir = gpioPath.resolve("gpio$linuxGpio").resolve("direction")
|
||||
Files.writeString(dir, "out")
|
||||
// successfully set direction to output
|
||||
inited = true
|
||||
valuepath = gpioPath.resolve("gpio$linuxGpio").resolve("value")
|
||||
logger.info("GPIO $name GPIO $linuxGpio already exists as output")
|
||||
} catch (e : Exception){
|
||||
// failed to set direction to output, log error
|
||||
logger.error("Failed to set existing GPIO $name GPIO $linuxGpio as output: ${e.message}")
|
||||
}
|
||||
|
||||
} else {
|
||||
// not yet exists, export it
|
||||
try{
|
||||
// export the GPIO pin
|
||||
Files.writeString(gpioExportPath, linuxGpio.toString())
|
||||
if (checkExists()){
|
||||
// successfully exported, now set direction to output
|
||||
val dir = gpioPath.resolve("gpio$linuxGpio").resolve("direction")
|
||||
Files.writeString(dir, "out")
|
||||
inited = true
|
||||
valuepath = gpioPath.resolve("gpio$linuxGpio").resolve("value")
|
||||
logger.info("Initialized GPIO $name GPIO $linuxGpio as output")
|
||||
} else {
|
||||
// failed to export, log error
|
||||
logger.error("GPIO $name GPIO $linuxGpio does not exist after export")
|
||||
}
|
||||
|
||||
|
||||
} catch (e: Exception){
|
||||
// failed to export GPIO pin, log error
|
||||
logger.error("Failed to export GPIO $name GPIO $linuxGpio: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else logger.error("DigitalOutput $name GPIO $linuxGpio not initialized: GPIO support not available")
|
||||
}
|
||||
|
||||
fun checkExists(): Boolean{
|
||||
return try {
|
||||
val gpioPath = gpioPath.resolve("gpio$linuxGpio")
|
||||
if (gpioPath.isDirectory() && gpioPath.toFile().exists()){
|
||||
val dir = gpioPath.resolve("direction").toFile()
|
||||
val value = gpioPath.resolve("value").toFile()
|
||||
if (dir.exists() && value.exists()) {
|
||||
return dir.canWrite() && value.canWrite()
|
||||
}
|
||||
}
|
||||
false
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error checking if GPIO $linuxGpio exists: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the GPIO pin by unexporting it.
|
||||
*/
|
||||
fun release(){
|
||||
if (inited){
|
||||
try{
|
||||
Files.writeString(gpioUnexportPath, linuxGpio.toString())
|
||||
inited = false
|
||||
valuepath = null
|
||||
} catch (e : Exception){
|
||||
logger.error("Failed to unexport GPIO $name GPIO ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Digital Output to ON
|
||||
*/
|
||||
fun setON() : Boolean{
|
||||
if (inited){
|
||||
if (valuepath!=null){
|
||||
try{
|
||||
Files.writeString(valuepath!!, if (activeHigh) "1" else "0")
|
||||
return true
|
||||
} catch (e: Exception){
|
||||
logger.error("Failed to set GPIO $name GPIO $linuxGpio ON: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Digital Output to OFF
|
||||
*/
|
||||
fun setOFF() : Boolean{
|
||||
if (inited){
|
||||
if (valuepath!=null){
|
||||
try{
|
||||
Files.writeString(valuepath!!, if (activeHigh) "0" else "1")
|
||||
return true
|
||||
} catch (e : Exception){
|
||||
logger.error("Failed to set GPIO $name GPIO $linuxGpio OFF: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Blink the Digital Output for a specified duration.
|
||||
* @param ms The duration in milliseconds to keep the output ON before turning it OFF (default is 50ms).
|
||||
*/
|
||||
fun Blink(ms: Long = 50){
|
||||
if (inited){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
setON()
|
||||
delay(ms)
|
||||
setOFF()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
21
src/sbc/NanopiDuo2.kt
Normal file
21
src/sbc/NanopiDuo2.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package sbc
|
||||
|
||||
/**
|
||||
* Nanopi Duo2 pin and GPIO mapping.
|
||||
* Source : https://wiki.friendlyelec.com/wiki/index.php/NanoPi_Duo2
|
||||
*/
|
||||
@Suppress("unused")
|
||||
enum class NanopiDuo2(val pin: Int, val linuxGpio: Int) {
|
||||
IRRX(9,363),
|
||||
PG11(11,203),
|
||||
RXD(2,5),
|
||||
TXD(4,4),
|
||||
SCL(8,11),
|
||||
SDA(10,12),
|
||||
CS(12,13),
|
||||
CLK(14,14),
|
||||
MISO(16,16),
|
||||
MOSI(18,15),
|
||||
RX1(20,199),
|
||||
TX1(22,198)
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package somecodes
|
||||
|
||||
import com.sun.jna.Platform
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
|
||||
@Suppress("unused")
|
||||
@@ -8,10 +11,25 @@ class Codes {
|
||||
|
||||
companion object{
|
||||
val audioFilePath = Path(System.getProperty("user.dir"), "audiofile")
|
||||
val gpioPath : Path = Path("/sys/class/gpio")
|
||||
val gpioExportPath : Path = gpioPath.resolve("export")
|
||||
val gpioUnexportPath : Path = gpioPath.resolve("unexport")
|
||||
private val logger = LoggerFactory.getLogger("Codes")
|
||||
private val validAudioExtensions = setOf("wav", "mp3")
|
||||
private val KB_size = 1024 // 1 KB = 1024 bytes
|
||||
private val MB_size = 1024 * KB_size // 1 MB = 1024 KB
|
||||
private val GB_size = 1024 * MB_size // 1 GB = 1024 MB
|
||||
private const val KB_size = 1024 // 1 KB = 1024 bytes
|
||||
private const val MB_size = 1024 * KB_size // 1 MB = 1024 KB
|
||||
private const val GB_size = 1024 * MB_size // 1 GB = 1024 MB
|
||||
|
||||
fun haveGpioSupport() : Boolean {
|
||||
if (Platform.isLinux()){
|
||||
if (gpioExportPath.toFile().exists() && gpioUnexportPath.toFile().exists()) {
|
||||
if (gpioExportPath.toFile().canWrite() && gpioUnexportPath.toFile().canWrite()) {
|
||||
return true
|
||||
} else logger.error("$gpioExportPath or $gpioUnexportPath is not writable")
|
||||
} else logger.error("$gpioExportPath or $gpioUnexportPath does not exist")
|
||||
} else logger.error("Platform is not Linux, GPIO support is not available")
|
||||
return false
|
||||
}
|
||||
|
||||
fun SizeToString(size: Long) : String {
|
||||
return when {
|
||||
|
||||
@@ -35,7 +35,7 @@ public class webApp {
|
||||
}
|
||||
app = Javalin.create(config -> {
|
||||
config.useVirtualThreads = true; // Enable virtual threads for better performance
|
||||
config.staticFiles.add("/");
|
||||
config.staticFiles.add("/webpage");
|
||||
config.router.apiBuilder(()->{
|
||||
path("/", () -> get(ctx -> {
|
||||
if (ctx.sessionAttribute("user") == null) {
|
||||
|
||||
@@ -128,7 +128,7 @@ class ZelloClient(val address : URI, val username: String, val password: String,
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onMessage(message: String?) {
|
||||
logger.info("Message received: $message")
|
||||
//logger.info("Message received: $message")
|
||||
val jsnode = mapper.readTree(message)
|
||||
if (jsnode["seq"] != null) {
|
||||
when(val seq = jsnode.get("seq").asInt()){
|
||||
@@ -229,19 +229,6 @@ class ZelloClient(val address : URI, val username: String, val password: String,
|
||||
isOnline = false
|
||||
currentChannel = null
|
||||
|
||||
|
||||
//Revisi 06/08/2025 : Change to Coroutines
|
||||
// val thread = Thread {
|
||||
// try {
|
||||
// Thread.sleep(10000)
|
||||
// connect()
|
||||
// } catch (e: InterruptedException) {
|
||||
// logger.error("Reconnection interrupted: ${e.message}")
|
||||
// }
|
||||
// }
|
||||
// thread.name= "ZelloClient-ReconnectThread"
|
||||
// thread.isDaemon = true
|
||||
// thread.start()
|
||||
}
|
||||
|
||||
override fun onError(ex: Exception?) {
|
||||
|
||||
Reference in New Issue
Block a user