Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,32 +19,34 @@ 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
keep-history: true
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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.animatedledstrip</groupId>
<artifactId>animatedledstrip-server</artifactId>
<version>0.3</version>
<version>0.4-SNAPSHOT</version>
<packaging>jar</packaging>

<name>${project.groupId}:${project.artifactId}</name>
Expand Down Expand Up @@ -69,7 +69,7 @@
<dependency>
<groupId>io.github.animatedledstrip</groupId>
<artifactId>animatedledstrip-core</artifactId>
<version>0.3</version>
<version>0.4-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/animatedledstrip/cmdline/CommandLine.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}
178 changes: 109 additions & 69 deletions src/main/java/animatedledstrip/server/AnimatedLEDStripServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,113 +31,122 @@ 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 <T: AnimatedLEDStrip> (
class AnimatedLEDStripServer<T : AnimatedLEDStrip>(
args: Array<String>,
ledClass: KClass<T>
) {
/**
* 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<Int>().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<Int>().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<T> {
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()
}
if (cmdline.hasOption("T")) animationHandler.addAnimation(testAnimation)
return this
}

fun waitUntilStop() {
while (running) {
delayBlocking(1)
}
}

fun stop() {
leds.setStripColor(0)
delayBlocking(500)
Expand All @@ -146,20 +155,51 @@ class AnimatedLEDStripServer <T: AnimatedLEDStrip> (
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()
}

}
Loading