diff --git a/.travis.yml b/.travis.yml
index 18929a1..4f24ffb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,10 @@
language: java
jdk: openjdk9
+cache:
+ directories:
+ - $HOME/.m2
+
before_install:
- echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import
- echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust
@@ -15,19 +19,21 @@ branches:
stages:
- name: test
- name: deploy
- if: branch = master AND type != pull_request
+ if: (branch = master OR branch =~ /^v.*/) AND type != pull_request
jobs:
include:
- stage: test
- install: ""
+ install: skip
after_success: bash <(curl -s https://codecov.io/bash)
- stage: deploy
- install: ""
- test: ""
+ script: skip
+ install: skip
deploy:
- provider: script
script: mvn --settings .maven.xml site -DskipTests=true -B && git add dokka
+ on:
+ all_branches: true
- provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
@@ -35,12 +41,12 @@ jobs:
verbose: true
local_dir: ./dokka
on:
- branch: master
+ all_branches: true
- stage: deploy
- install:
- - ""
+ script: skip
+ install: skip
deploy:
- provider: script
script: mvn deploy --settings .maven.xml -DskipTests=true -B
on:
- branch: master
+ all_branches: true
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 8c1fdbd..9c6e10e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.github.animatedledstrip
animatedledstrip-server
- 0.3
+ 0.4-SNAPSHOT
jar
${project.groupId}:${project.artifactId}
@@ -69,7 +69,7 @@
io.github.animatedledstrip
animatedledstrip-core
- 0.3
+ 0.4-SNAPSHOT
org.jetbrains.kotlin
diff --git a/src/main/java/animatedledstrip/cmdline/CommandLine.kt b/src/main/java/animatedledstrip/cmdline/CommandLine.kt
new file mode 100644
index 0000000..944071f
--- /dev/null
+++ b/src/main/java/animatedledstrip/cmdline/CommandLine.kt
@@ -0,0 +1,74 @@
+package animatedledstrip.cmdline
+
+import kotlinx.coroutines.*
+import org.pmw.tinylog.Configurator
+import org.pmw.tinylog.Level
+import java.io.EOFException
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
+import java.net.InetSocketAddress
+import java.net.Socket
+import java.net.SocketException
+import kotlin.system.exitProcess
+
+class CommandLine {
+
+ private val socket = Socket()
+
+ private var endCmdLine = false
+
+ private var readerJob: Job? = null
+
+ init {
+ Configurator.defaultConfig().level(Level.OFF).activate()
+ }
+
+ fun loop() {
+ println("Welcome to the AnimatedLEDStrip Server console")
+ while (!endCmdLine) {
+ try {
+ socket.connect(InetSocketAddress("localhost", 1118), 5000)
+ } catch (e: Exception) {
+ continue
+ }
+ println("Connected")
+ try {
+ val socOut = ObjectOutputStream(socket.getOutputStream())
+ val socIn = ObjectInputStream(socket.getInputStream())
+ readerJob = GlobalScope.launch {
+ withContext(Dispatchers.IO) {
+ try {
+ while (!endCmdLine) {
+ println(socIn.readObject() as String? ?: "ERROR")
+ }
+ } catch (e: SocketException) {
+ println("Connection lost: $e")
+ } catch (e: EOFException) {
+ println("Connection lost: $e")
+ }
+ }
+ }
+
+ input@ while (!endCmdLine) {
+ val str = readLine() ?: continue
+ when (str.toUpperCase()) {
+ "" -> continue@input
+ "EXIT" -> exitProcess(0)
+ "Q", "QUIT" -> {
+ socOut.writeObject(str)
+ exitProcess(0)
+ }
+ else -> socOut.writeObject(str)
+ }
+ }
+
+ } catch (e: SocketException) {
+ println("Connection lost: $e")
+ readerJob?.cancel()
+ } catch (e: EOFException) {
+ println("Connection lost: $e")
+ readerJob?.cancel()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/animatedledstrip/server/AnimatedLEDStripServer.kt b/src/main/java/animatedledstrip/server/AnimatedLEDStripServer.kt
index 9285fc5..3393f01 100644
--- a/src/main/java/animatedledstrip/server/AnimatedLEDStripServer.kt
+++ b/src/main/java/animatedledstrip/server/AnimatedLEDStripServer.kt
@@ -31,100 +31,115 @@ import animatedledstrip.colors.ccpresets.CCBlue
import animatedledstrip.leds.AnimatedLEDStrip
import animatedledstrip.leds.emulated.EmulatedAnimatedLEDStrip
import animatedledstrip.utils.delayBlocking
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.newSingleThreadContext
import org.apache.commons.cli.DefaultParser
-import org.apache.commons.cli.Options
import org.pmw.tinylog.Configurator
import org.pmw.tinylog.Level
import org.pmw.tinylog.Logger
+import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
-class AnimatedLEDStripServer (
+class AnimatedLEDStripServer(
args: Array,
ledClass: KClass
) {
+ /**
+ * Is the server running
+ */
internal var running = false
- private val options = Options().apply {
- addOption("d", "Enable debugging")
- addOption("t", "Enable trace debugging")
- addOption("v", "Enable verbose log statements")
- addOption("q", "Disable log outputs")
- addOption("E", "Emulate LED strip but do NOT launch emulator")
- addOption("f", true, "Specify properties file")
- addOption("i", "Enable image debugging")
- addOption("T", "Run test")
- }
+ /* Command line options and properties file */
private val cmdline = DefaultParser().parse(options, args)
- var defaultPropertyFileName = "led.config"
+ private var propertyFileName = cmdline.getOptionValue("f") ?: "led.config"
+
+ private var outputFileName: String? = cmdline.getOptionValue("o")
+
+ /* Set logging levels based on command line */
+ init {
+ val loggingPattern =
+ if (cmdline.hasOption("v")) "{date:yyyy-MM-dd HH:mm:ss} [{thread}] {class}.{method}()\n{level}: {message}"
+ else "{{level}:|min-size=8} {message}"
+
+ val loggingLevel =
+ when {
+ cmdline.hasOption("t") -> Level.TRACE
+ cmdline.hasOption("d") -> Level.DEBUG
+ cmdline.hasOption("q") -> Level.OFF
+ else -> Level.INFO
+ }
- var defaultOutputFileName: String? = null
+ Configurator.defaultConfig().formatPattern(loggingPattern).level(loggingLevel).addWriter(SocketWriter())
+ .activate()
- private val properties = Properties().apply {
+
+ }
+
+ private val properties: Properties? = Properties().apply {
try {
- load(FileInputStream(cmdline.getOptionValue("f") ?: defaultPropertyFileName))
+ load(FileInputStream(propertyFileName))
} catch (e: FileNotFoundException) {
- Logger.warn("File ${cmdline.getOptionValue("f") ?: defaultPropertyFileName} not found")
+ Logger.warn { "File $propertyFileName not found" }
}
}
- val ports = mutableListOf().apply {
- Logger.debug(properties.getProperty("ports"))
- properties.getProperty("ports")?.split(' ')?.forEach {
- Logger.debug(it)
+ /* Arguments for creating the AnimatedLEDStrip instance */
+
+ private val emulated: Boolean = cmdline.hasOption("e") || cmdline.hasOption("E")
+
+ private val numLEDs: Int = properties?.getProperty("numLEDs", "240")?.toInt() ?: 240
+
+ private val pin: Int = properties?.getProperty("pin", "12")?.toInt() ?: 12
+
+ private val imageDebuggingEnabled: Boolean = cmdline.hasOption("i")
+
+ private val ports = mutableListOf().apply {
+ properties?.getProperty("ports")?.split(' ')?.forEach {
requireNotNull(it.toIntOrNull())
this.add(it.toInt())
}
+ if (!emulated) this += 1118 // local port
}
- private val leds = when (cmdline.hasOption("e") || cmdline.hasOption("E")) {
+ private val rendersBeforeSave =
+ properties?.getProperty("renders")?.toIntOrNull() ?: cmdline.getOptionValue("r")?.toIntOrNull() ?: 1000
+
+ private val leds = when (emulated) {
false -> ledClass.primaryConstructor!!.call(
- properties.getProperty("numLEDs", "240").toInt(),
- properties.getProperty("pin", "12").toInt(),
- cmdline.hasOption("i"),
- cmdline.getOptionValue("o") ?: defaultOutputFileName
+ numLEDs,
+ pin,
+ imageDebuggingEnabled,
+ outputFileName,
+ rendersBeforeSave
)
true -> EmulatedAnimatedLEDStrip(
- properties.getProperty("numLEDs", "240").toInt(),
- imageDebugging = cmdline.hasOption("i"),
- fileName = cmdline.getOptionValue("o") ?: defaultOutputFileName
+ numLEDs,
+ imageDebugging = imageDebuggingEnabled,
+ fileName = outputFileName
)
}
- internal val animationHandler = AnimationHandler(leds)
+ private val persistAnimations =
+ properties?.getProperty("persist", "false")?.toBoolean() ?: cmdline.hasOption("P")
+
+ internal val animationHandler = AnimationHandler(leds, persistAnimations = persistAnimations)
var testAnimation: AnimationData =
AnimationData().animation(Animation.COLOR).color(CCBlue)
- init {
- val pattern =
- if (cmdline.hasOption("v")) "{date:yyyy-MM-dd HH:mm:ss} [{thread}] {class}.{method}()\n{level}: {message}"
- else "{{level}:|min-size=8} {message}"
-
- val level =
- when {
- cmdline.hasOption("t") -> Level.TRACE
- cmdline.hasOption("d") -> Level.DEBUG
- cmdline.hasOption("q") -> Level.OFF
- else -> Level.INFO
- }
-
- Configurator.defaultConfig().formatPattern(pattern).level(level).activate()
- }
-
+ /* Start and stop methods */
fun start(): AnimatedLEDStripServer {
+ val dir = File(".animations")
+ if (!dir.isDirectory)
+ dir.mkdirs()
+
running = true
- startLocalTerminalReader()
- Logger.debug("Ports = $ports")
+ Logger.debug { "Ports: $ports" }
ports.forEach {
SocketConnections.add(it, server = this).open()
}
@@ -132,12 +147,6 @@ class AnimatedLEDStripServer (
return this
}
- fun waitUntilStop() {
- while (running) {
- delayBlocking(1)
- }
- }
-
fun stop() {
leds.setStripColor(0)
delayBlocking(500)
@@ -146,20 +155,51 @@ class AnimatedLEDStripServer (
running = false
}
- @Suppress("EXPERIMENTAL_API_USAGE")
- val localThread = newSingleThreadContext("Local Terminal")
-
- private fun startLocalTerminalReader() {
- GlobalScope.launch(localThread) {
- while (this@AnimatedLEDStripServer.running) {
- Logger.trace("Local terminal waiting for input")
- val strIn = readLine()
- Logger.trace("Read line")
- if (strIn?.toUpperCase() == "Q") {
- Logger.trace("'Q' received, shutting down server")
- stop()
+ internal fun parseTextCommand(command: String) {
+ val line = command.toUpperCase().split(" ")
+ return when (line[0]) {
+ "QUIT", "Q" -> {
+ Logger.info { "Shutting down server" }
+ stop()
+ }
+ "DEBUG" -> {
+ setLoggingLevel(Level.DEBUG)
+ Logger.debug("Set logging level to debug")
+ }
+ "TRACE" -> {
+ setLoggingLevel(Level.TRACE)
+ Logger.trace("Set logging level to trace")
+ }
+ "INFO" -> {
+ setLoggingLevel(Level.INFO)
+ Logger.info("Set logging level to info")
+ }
+ "CLEAR" -> {
+ animationHandler.addAnimation(AnimationData().animation(Animation.COLOR))
+ }
+ "SHOW" -> {
+ if (line.size > 1) Logger.info {
+ "${line[1]}: ${animationHandler.continuousAnimations[line[1]]?.params ?: "NOT FOUND"}"
}
+ else Logger.info { "Running Animations: ${animationHandler.continuousAnimations.keys}" }
+ }
+ "END" -> {
+ if (line.size > 1) {
+ if (line[1].toUpperCase() == "ALL") {
+ val animations = animationHandler.continuousAnimations
+ animations.forEach {
+ animationHandler.endAnimation(it.value)
+ }
+ } else for (i in 1 until line.size)
+ animationHandler.endAnimation(animationHandler.continuousAnimations[line[i]])
+ } else Logger.warn { "Animation ID must be specified" }
}
+ else -> Logger.warn { "$command is not a valid command" }
}
}
+
+ private fun setLoggingLevel(level: Level) {
+ Configurator.currentConfig().level(level).activate()
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/animatedledstrip/server/AnimationHandler.kt b/src/main/java/animatedledstrip/server/AnimationHandler.kt
index 0e87af3..158bba4 100644
--- a/src/main/java/animatedledstrip/server/AnimationHandler.kt
+++ b/src/main/java/animatedledstrip/server/AnimationHandler.kt
@@ -31,6 +31,10 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.newFixedThreadPoolContext
import org.pmw.tinylog.Logger
+import java.io.File
+import java.io.FileInputStream
+import java.io.InvalidClassException
+import java.io.ObjectInputStream
import java.lang.Math.random
@@ -38,10 +42,14 @@ import java.lang.Math.random
* An object that creates ContinuousRunAnimation instances for animations and
* keeps track of currently running animations.
*/
-internal class AnimationHandler(private val leds: AnimatedLEDStrip) {
+internal class AnimationHandler(
+ private val leds: AnimatedLEDStrip,
+ threadCount: Int = 100,
+ internal val persistAnimations: Boolean = false
+) {
@Suppress("EXPERIMENTAL_API_USAGE")
- val animationThreadPool = newFixedThreadPoolContext(100, "AnimationThreads")
+ val animationThreadPool = newFixedThreadPoolContext(threadCount, "AnimationThreads")
/**
* Map tracking what continuous animations are currently running
@@ -52,6 +60,24 @@ internal class AnimationHandler(private val leds: AnimatedLEDStrip) {
val continuousAnimations = mutableMapOf()
+ init {
+ GlobalScope.launch {
+ File(".animations/").walk().forEach {
+ if (!it.isDirectory && it.name.endsWith(".anim")) try {
+ ObjectInputStream(FileInputStream(it)).apply {
+ val obj = readObject() as AnimationData
+ addAnimation(obj, obj.id)
+ close()
+ }
+ } catch (e: ClassCastException) {
+ it.delete()
+ } catch (e: InvalidClassException) {
+ it.delete()
+ }
+ }
+ }
+ }
+
/**
* Adds a new animation.
*
@@ -65,46 +91,46 @@ internal class AnimationHandler(private val leds: AnimatedLEDStrip) {
*
* @param params An AnimationData instance containing data about the animation to be run
*/
- fun addAnimation(params: AnimationData) {
-
- /* Special "Animation" type that the GUI sends to end an animation */
- if (params.animation == Animation.ENDANIMATION) {
- Logger.debug("Ending an animation")
- continuousAnimations[params.id]?.endAnimation() // End animation
- ?: throw Exception("Animation ${params.id} not running")
- continuousAnimations.remove(params.id) // Remove it from the continuousAnimations map
- return
- }
-
- Logger.trace("Launching new thread for new animation")
- GlobalScope.launch(animationThreadPool) {
- Logger.debug(params)
+ fun addAnimation(params: AnimationData, animId: String? = null) {
+ /* Special "Animation" type that the client sends to end an animation */
+ if (params.animation == Animation.ENDANIMATION)
+ endAnimation(params)
+ else
when (params.animation::class.java.fields[params.animation.ordinal].annotations.find { it is NonRepetitive } is NonRepetitive) {
- /* Animations that are only run once because they change the color of the strip */
+ /* Animations that are only run once because they change the color of the strip */
true -> {
- Logger.trace("Calling Single Run Animation")
- leds.run(params)
- Logger.trace("Single Run Animation on ${Thread.currentThread().name} complete")
+ singleRunAnimation(params)
}
- /* Animations that can be run repeatedly */
+ /* Animations that can be run repeatedly */
false -> {
if (params.continuous) {
- Logger.trace("Calling Continuous Animation")
- val id = random().toString().removePrefix("0.")
+ val id = animId ?: (random() * 100000000).toInt().toString()
continuousAnimations[id] =
- ContinuousRunAnimation(id, params, leds)
+ ContinuousRunAnimation(id, params, leds, this)
Logger.trace(continuousAnimations)
- continuousAnimations[id]!!.startAnimation()
- Logger.debug("$id complete")
+ continuousAnimations[id]!!.runAnimation()
} else {
- Logger.trace("Calling Single Run Animation")
- leds.run(params)
- Logger.trace("Single Run Animation on ${Thread.currentThread().name} complete")
+ singleRunAnimation(params)
}
}
}
+ }
+
+ private fun singleRunAnimation(params: AnimationData) {
+ GlobalScope.launch(animationThreadPool) {
+ leds.run(params)
}
}
+
+ fun endAnimation(params: AnimationData?) {
+ continuousAnimations[params?.id ?: "NONE"]?.endAnimation() // End animation
+ ?: run { Logger.warn { "Animation ${params?.id} not running" }; return }
+ }
+
+ fun endAnimation(animation: ContinuousRunAnimation?) {
+ if (animation == null) return
+ else endAnimation(animation.params)
+ }
}
diff --git a/src/main/java/animatedledstrip/server/ContinuousRunAnimation.kt b/src/main/java/animatedledstrip/server/ContinuousRunAnimation.kt
index b5b91cc..1fbd4c8 100644
--- a/src/main/java/animatedledstrip/server/ContinuousRunAnimation.kt
+++ b/src/main/java/animatedledstrip/server/ContinuousRunAnimation.kt
@@ -23,9 +23,16 @@ package animatedledstrip.server
*/
+import animatedledstrip.animationutils.Animation
import animatedledstrip.animationutils.AnimationData
import animatedledstrip.leds.AnimatedLEDStrip
+import kotlinx.coroutines.*
import org.pmw.tinylog.Logger
+import java.io.File
+import java.io.FileOutputStream
+import java.io.ObjectOutputStream
+import java.nio.file.Files
+import java.nio.file.Paths
/**
* Class for running an animation that repeats until stopped.
@@ -34,43 +41,72 @@ import org.pmw.tinylog.Logger
* @param params An AnimationData instance containing data about the animation
* to be run
*/
-class ContinuousRunAnimation(private val id: String, private val params: AnimationData, private val leds: AnimatedLEDStrip) {
+internal class ContinuousRunAnimation(
+ private val id: String,
+ val params: AnimationData,
+ private val leds: AnimatedLEDStrip,
+ private val handler: AnimationHandler
+) {
/**
- * Variable controlling while loops in animation functions.
+ * Variable controlling if the animation will repeat
*/
private var continueAnimation = true
+ private var job: Job? = null
- init {
- sendAnimation() // Send animation to GUI
- }
+ private val fileName = "$id.anim"
/**
- * Determine which animation is being called and call the corresponding function.
+ * Run the animation in a new thread
*/
- fun startAnimation() {
- Logger.trace("params: $params")
- while (continueAnimation) leds.run(params)
+ fun runAnimation() {
+ job = GlobalScope.launch(handler.animationThreadPool) {
+ if (handler.persistAnimations) launch { saveAnimationToDisk() }
+ sendStartAnimation()
+ while (continueAnimation) leds.run(params)
+ sendEndAnimation()
+ handler.continuousAnimations.remove(id)
+ }
}
/**
- * Stop animation by setting the loop guard to false.
+ * Stop animation by setting the loop guard to false
*/
fun endAnimation() {
- Logger.debug("Animation $id ending")
- continueAnimation = false
+ Logger.debug { "Animation $id ending" }
+ if (continueAnimation) continueAnimation = false
+ else job?.cancel()
+ if (File(".animations/$fileName").exists())
+ Files.delete(Paths.get(".animations/$fileName"))
}
/**
- * Send animation data to GUI.
+ * Send message to client(s) that animation has started
*/
- fun sendAnimation(connection: SocketConnections.Connection? = null) {
- Logger.trace("Sending animation to GUI")
+ fun sendStartAnimation(connection: SocketConnections.Connection? = null) {
SocketConnections.sendAnimation(params, id, connection)
}
+ /**
+ * Send message to client(s) that animation has ended
+ *
+ * @param connection
+ */
+ fun sendEndAnimation(connection: SocketConnections.Connection? = null) {
+ SocketConnections.sendAnimation(params.copy(animation = Animation.ENDANIMATION), id, connection)
+ }
+
+ private suspend fun saveAnimationToDisk() {
+ withContext(Dispatchers.IO) {
+ ObjectOutputStream(FileOutputStream(".animations/$fileName")).apply {
+ writeObject(params)
+ close()
+ }
+ }
+ }
+
}
diff --git a/src/main/java/animatedledstrip/server/GlobalVars.kt b/src/main/java/animatedledstrip/server/GlobalVars.kt
new file mode 100644
index 0000000..2c9ed04
--- /dev/null
+++ b/src/main/java/animatedledstrip/server/GlobalVars.kt
@@ -0,0 +1,20 @@
+package animatedledstrip.server
+
+import org.apache.commons.cli.Options
+
+var server: AnimatedLEDStripServer<*>? = null
+
+val options = Options().apply {
+ addOption("d", "Enable debugging")
+ addOption("t", "Enable trace debugging")
+ addOption("v", "Enable verbose log statements")
+ addOption("q", "Disable log outputs")
+ addOption("E", "Emulate LED strip but do NOT launch emulator")
+ addOption("f", true, "Specify properties file")
+ addOption("o", true, "Specify output file name for image debugging")
+ addOption("r", true, "Specify number of renders between saves")
+ addOption("i", "Enable image debugging")
+ addOption("P", "Persist animations across restarts")
+ addOption("T", "Run test")
+ addOption("C", "Connect to a running server with a command line")
+}
\ No newline at end of file
diff --git a/src/main/java/animatedledstrip/server/ServerUtils.kt b/src/main/java/animatedledstrip/server/ServerUtils.kt
new file mode 100644
index 0000000..ef06da3
--- /dev/null
+++ b/src/main/java/animatedledstrip/server/ServerUtils.kt
@@ -0,0 +1,9 @@
+package animatedledstrip.server
+
+import animatedledstrip.utils.delayBlocking
+
+fun AnimatedLEDStripServer<*>.waitUntilStop() {
+ while (running) {
+ delayBlocking(1)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/animatedledstrip/server/SocketConnections.kt b/src/main/java/animatedledstrip/server/SocketConnections.kt
index 7ce4abb..af2ac6f 100644
--- a/src/main/java/animatedledstrip/server/SocketConnections.kt
+++ b/src/main/java/animatedledstrip/server/SocketConnections.kt
@@ -23,6 +23,7 @@ package animatedledstrip.server
*/
+import animatedledstrip.animationutils.Animation
import animatedledstrip.animationutils.AnimationData
import animatedledstrip.animationutils.id
import kotlinx.coroutines.*
@@ -46,6 +47,8 @@ object SocketConnections {
*/
val connections = mutableMapOf()
+ var localConnection: Connection? = null
+
/**
* Initialize a new connection. Creates a Connection instance with the
* specified port, then adds the port and Connection instance to
@@ -56,7 +59,8 @@ object SocketConnections {
*/
fun add(port: Int, server: AnimatedLEDStripServer<*>): Connection {
val connection = Connection(port, server)
- connections[port] = connection
+ if (port == 1118) localConnection = connection
+ else connections[port] = connection
return connection
}
@@ -70,19 +74,16 @@ object SocketConnections {
if (hostIP == null) null else InetAddress.getByName(hostIP)
)
var clientSocket: Socket? = null
- private var disconnected = true
+ var connected = false
+ private set
private var socOut: ObjectOutputStream? = null
- var textBased = false
-
- val isDisconnected: Boolean
- get() = disconnected
/**
* Open the connection
*/
fun open() {
GlobalScope.launch(connectionThreadPool) {
- Logger.debug("Starting port $port")
+ Logger.debug { "Starting port $port" }
openSocket()
}
}
@@ -97,61 +98,94 @@ object SocketConnections {
*/
private suspend fun openSocket() {
withContext(Dispatchers.IO) {
- Logger.debug("Socket at port $port started")
+ Logger.debug { "Socket at port $port started" }
while (server.running) {
try {
clientSocket = serverSocket.accept()
- Logger.trace("Accepted new connection on port $port")
- Logger.trace("Initializing input stream")
val socIn = ObjectInputStream(clientSocket!!.getInputStream())
- Logger.trace("Initializing output stream")
socOut = ObjectOutputStream(clientSocket!!.getOutputStream())
- Logger.debug("Sending currently running animations to GUI")
+ Logger.info { "Connection on port $port Established" }
+ connected = true
// Send all current running continuous animations to newly connected client
- server.animationHandler.continuousAnimations.forEach {
- it.value.sendAnimation(this@Connection)
- }
- disconnected = false
- Logger.info("Connection on port $port Established")
+ if (port != 1118)
+ server.animationHandler.continuousAnimations.forEach {
+ it.value.sendStartAnimation(this@Connection)
+ }
var input: Any?
- while (!disconnected) {
- Logger.trace("Waiting for input")
+ while (connected) {
+ Logger.trace { "Waiting for input" }
try {
- input = socIn.readObject() as AnimationData
- Logger.trace("Input received")
- server.animationHandler.addAnimation(input)
+ input = when (port) {
+ 1118 -> socIn.readObject() as String
+ else -> socIn.readObject() as AnimationData
+ }
+ Logger.trace { "Input received" }
+ when (input) {
+ is AnimationData -> server.animationHandler.addAnimation(input)
+ is String -> server.parseTextCommand(input)
+ }
} catch (e: ClassCastException) {
- Logger.error("Could not cast input to AnimationData")
+ Logger.error { "Could not cast input to ${if (port == 1118) "String" else "AnimationData"}" }
continue
}
}
} catch (e: SocketException) {
// Catch disconnections
- Logger.warn("Connection on port $port Lost: $e")
- disconnected = true
+ Logger.warn { "Connection on port $port ${if (port == 1118) "(Local) " else ""}Lost: $e" }
+ connected = false
} catch (e: EOFException) {
- Logger.warn("Connection on port $port Lost: $e")
- disconnected = true
+ Logger.warn { "Connection on port $port ${if (port == 1118) "(Local) " else ""}Lost: $e" }
+ connected = false
}
}
}
}
/**
- * Send animation data to the GUI along with an ID
+ * Send animation data to the client along with an ID.
+ * Does not work for port 1118 (local connection).
*
* @param animation An AnimationData containing data about the animation
* @param id The ID for the animation
*/
fun sendAnimation(animation: AnimationData, id: String) {
- if (!isDisconnected) {
- Logger.trace("Animation to send: $animation")
+ check(port != 1118) { "Cannot send animation to local port" }
+ if (connected) {
+ Logger.trace { "Animation to send: $animation" }
+ runBlocking {
+ withTimeout(5000) {
+ withContext(Dispatchers.IO) {
+ socOut?.writeObject(
+ animation
+ .id(
+ if ((animation.animation == Animation.CUSTOMANIMATION ||
+ animation.animation == Animation.CUSTOMREPETITIVEANIMATION) &&
+ animation.id.length == 1
+ )
+ "${animation.id} $id"
+ else id
+ )
+ )
+ ?: Logger.debug { "Could not send animation $id: Connection socket null" }
+ if (animation.animation == Animation.ENDANIMATION) Logger.debug { "Sent end of animation $id" }
+ else Logger.debug { "Sent animation $id" }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Send a string to the local port.
+ * Only works for a connection with port 1118 (local connection)
+ */
+ fun sendString(str: String) {
+ check(port == 1118) { "Cannot send string to non-local port" }
+ if (connected) {
runBlocking {
withTimeout(5000) {
withContext(Dispatchers.IO) {
- socOut?.writeObject(animation.id(if (animation.id == "") id else "${animation.id} $id"))
- ?: Logger.debug("Could not send animation $id: Connection socket null")
- Logger.debug("Sent animation $id")
+ socOut?.writeObject(str)
}
}
}
@@ -177,7 +211,7 @@ object SocketConnections {
if (client != null) client.sendAnimation(animation, id)
else connections.forEach {
it.value.sendAnimation(animation, id)
- Logger.trace("Sent animation to client on port ${it.key}")
+ Logger.trace { "Sent animation to client on port ${it.key}" }
}
}
diff --git a/src/main/java/animatedledstrip/server/SocketWriter.kt b/src/main/java/animatedledstrip/server/SocketWriter.kt
new file mode 100644
index 0000000..b4edff1
--- /dev/null
+++ b/src/main/java/animatedledstrip/server/SocketWriter.kt
@@ -0,0 +1,35 @@
+package animatedledstrip.server
+
+import org.pmw.tinylog.Configuration
+import org.pmw.tinylog.LogEntry
+import org.pmw.tinylog.writers.LogEntryValue
+import org.pmw.tinylog.writers.PropertiesSupport
+import org.pmw.tinylog.writers.Writer
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@PropertiesSupport(name = "socket", properties = [])
+class SocketWriter : Writer {
+
+ val delimiter = ":"
+
+ override fun init(p0: Configuration?) {
+ }
+
+ override fun getRequiredLogEntryValues(): MutableSet {
+ return mutableSetOf(LogEntryValue.LEVEL, LogEntryValue.MESSAGE)
+ }
+
+ override fun write(log: LogEntry?) {
+ SocketConnections.localConnection?.sendString(
+ "${log?.level.toString()}$delimiter".padEnd(8, ' ') +
+ "${log?.message}"
+ )
+ }
+
+ override fun flush() {
+ }
+
+ override fun close() {
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/tinylog.properties b/src/main/resources/tinylog.properties
new file mode 100644
index 0000000..3d0fa89
--- /dev/null
+++ b/src/main/resources/tinylog.properties
@@ -0,0 +1,7 @@
+tinylog.writer1 = console
+tinylog.writer1.format = {{level}:|min-size=8} {message}
+tinylog.writer1.level = info
+
+tinylog.writer2 = socket
+tinylog.writer2.format = {{level}:|min-size=8} {message}
+tinylog.writer2.level = info
\ No newline at end of file
diff --git a/src/test/java/AnimatedLEDStripServerTest.kt b/src/test/java/AnimatedLEDStripServerTest.kt
index 6171570..29ed327 100644
--- a/src/test/java/AnimatedLEDStripServerTest.kt
+++ b/src/test/java/AnimatedLEDStripServerTest.kt
@@ -27,16 +27,14 @@ import animatedledstrip.leds.emulated.EmulatedAnimatedLEDStrip
import animatedledstrip.server.AnimatedLEDStripServer
import animatedledstrip.server.SocketConnections
import kotlinx.coroutines.*
+import org.junit.Ignore
import org.junit.Test
-import org.pmw.tinylog.Configurator
-import org.pmw.tinylog.Level
import java.io.ByteArrayInputStream
class AnimatedLEDStripServerTest {
init {
SocketConnections.hostIP = "0.0.0.0"
- Configurator.defaultConfig().level(Level.OFF).activate()
}
val leds = EmulatedAnimatedLEDStrip(50)
@@ -59,6 +57,7 @@ class AnimatedLEDStripServerTest {
}
@Test
+ @Ignore
fun testLocalTerminalThread() = runBlocking {
withTimeout(60000) {
val stream = ByteArrayInputStream("q".toByteArray())
diff --git a/src/test/java/AnimationHandlerTest.kt b/src/test/java/AnimationHandlerTest.kt
index 80d380d..67667fc 100644
--- a/src/test/java/AnimationHandlerTest.kt
+++ b/src/test/java/AnimationHandlerTest.kt
@@ -30,16 +30,12 @@ import animatedledstrip.server.AnimationHandler
import animatedledstrip.server.SocketConnections
import animatedledstrip.utils.delayBlocking
import org.junit.Test
-import org.pmw.tinylog.Configurator
-import org.pmw.tinylog.Level
-import kotlin.test.assertFails
import kotlin.test.assertTrue
class AnimationHandlerTest {
init {
- Configurator.defaultConfig().level(Level.OFF).activate()
SocketConnections.connections.clear()
}
@@ -92,9 +88,7 @@ class AnimationHandlerTest {
@Test
fun testRemoveNonExistentAnimation() {
val handler = AnimationHandler(leds)
- assertFails {
- handler.addAnimation(AnimationData().animation(Animation.ENDANIMATION).id("TEST"))
- }
+ handler.addAnimation(AnimationData().animation(Animation.ENDANIMATION).id("TEST"))
}
}
\ No newline at end of file
diff --git a/src/test/java/SocketConnectionsTest.kt b/src/test/java/SocketConnectionsTest.kt
index 04534d9..1ea9be9 100644
--- a/src/test/java/SocketConnectionsTest.kt
+++ b/src/test/java/SocketConnectionsTest.kt
@@ -29,8 +29,6 @@ import animatedledstrip.server.SocketConnections
import animatedledstrip.utils.delayBlocking
import kotlinx.coroutines.*
import org.junit.Test
-import org.pmw.tinylog.Configurator
-import org.pmw.tinylog.Level
import java.io.BufferedInputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
@@ -39,14 +37,10 @@ import kotlin.test.assertTrue
class SocketConnectionsTest {
- init {
- Configurator.defaultConfig().level(Level.OFF).activate()
- }
-
@Test
fun testAdd() {
val server =
- AnimatedLEDStripServer(arrayOf("-E"), EmulatedAnimatedLEDStrip::class)
+ AnimatedLEDStripServer(arrayOf("-Eq"), EmulatedAnimatedLEDStrip::class)
SocketConnections.hostIP = "0.0.0.0"
SocketConnections.add(1200, server)
@@ -57,7 +51,7 @@ class SocketConnectionsTest {
fun testOpenSocket() = runBlocking {
withTimeout(60000) {
val server =
- AnimatedLEDStripServer(arrayOf("-E"), EmulatedAnimatedLEDStrip::class).start()
+ AnimatedLEDStripServer(arrayOf("-Eq"), EmulatedAnimatedLEDStrip::class).start()
SocketConnections.hostIP = "0.0.0.0"
val c = SocketConnections.add(1201, server)
c.open()
diff --git a/src/test/resources/tinylog.properties b/src/test/resources/tinylog.properties
new file mode 100644
index 0000000..b006e0c
--- /dev/null
+++ b/src/test/resources/tinylog.properties
@@ -0,0 +1,5 @@
+tinylog.writer1 = console
+tinylog.writer1.level = off
+
+tinylog.writer2 = socket
+tinylog.writer2.level = off
\ No newline at end of file