Skip to content

Commit cdb05d3

Browse files
authored
Merge pull request #5 from AnimatedLEDStrip/v0.4
v0.4
2 parents 24e52d5 + 803e407 commit cdb05d3

15 files changed

Lines changed: 454 additions & 175 deletions

.travis.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
language: java
33
jdk: openjdk9
44

5+
cache:
6+
directories:
7+
- $HOME/.m2
8+
59
before_install:
610
- echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import
711
- echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust
@@ -15,32 +19,34 @@ branches:
1519
stages:
1620
- name: test
1721
- name: deploy
18-
if: branch = master AND type != pull_request
22+
if: (branch = master OR branch =~ /^v.*/) AND type != pull_request
1923

2024
jobs:
2125
include:
2226
- stage: test
23-
install: ""
27+
install: skip
2428
after_success: bash <(curl -s https://codecov.io/bash)
2529
- stage: deploy
26-
install: ""
27-
test: ""
30+
script: skip
31+
install: skip
2832
deploy:
2933
- provider: script
3034
script: mvn --settings .maven.xml site -DskipTests=true -B && git add dokka
35+
on:
36+
all_branches: true
3137
- provider: pages
3238
skip-cleanup: true
3339
github-token: $GITHUB_TOKEN
3440
keep-history: true
3541
verbose: true
3642
local_dir: ./dokka
3743
on:
38-
branch: master
44+
all_branches: true
3945
- stage: deploy
40-
install:
41-
- ""
46+
script: skip
47+
install: skip
4248
deploy:
4349
- provider: script
4450
script: mvn deploy --settings .maven.xml -DskipTests=true -B
4551
on:
46-
branch: master
52+
all_branches: true

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.github.animatedledstrip</groupId>
88
<artifactId>animatedledstrip-server</artifactId>
9-
<version>0.3</version>
9+
<version>0.4-SNAPSHOT</version>
1010
<packaging>jar</packaging>
1111

1212
<name>${project.groupId}:${project.artifactId}</name>
@@ -69,7 +69,7 @@
6969
<dependency>
7070
<groupId>io.github.animatedledstrip</groupId>
7171
<artifactId>animatedledstrip-core</artifactId>
72-
<version>0.3</version>
72+
<version>0.4-SNAPSHOT</version>
7373
</dependency>
7474
<dependency>
7575
<groupId>org.jetbrains.kotlin</groupId>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package animatedledstrip.cmdline
2+
3+
import kotlinx.coroutines.*
4+
import org.pmw.tinylog.Configurator
5+
import org.pmw.tinylog.Level
6+
import java.io.EOFException
7+
import java.io.ObjectInputStream
8+
import java.io.ObjectOutputStream
9+
import java.net.InetSocketAddress
10+
import java.net.Socket
11+
import java.net.SocketException
12+
import kotlin.system.exitProcess
13+
14+
class CommandLine {
15+
16+
private val socket = Socket()
17+
18+
private var endCmdLine = false
19+
20+
private var readerJob: Job? = null
21+
22+
init {
23+
Configurator.defaultConfig().level(Level.OFF).activate()
24+
}
25+
26+
fun loop() {
27+
println("Welcome to the AnimatedLEDStrip Server console")
28+
while (!endCmdLine) {
29+
try {
30+
socket.connect(InetSocketAddress("localhost", 1118), 5000)
31+
} catch (e: Exception) {
32+
continue
33+
}
34+
println("Connected")
35+
try {
36+
val socOut = ObjectOutputStream(socket.getOutputStream())
37+
val socIn = ObjectInputStream(socket.getInputStream())
38+
readerJob = GlobalScope.launch {
39+
withContext(Dispatchers.IO) {
40+
try {
41+
while (!endCmdLine) {
42+
println(socIn.readObject() as String? ?: "ERROR")
43+
}
44+
} catch (e: SocketException) {
45+
println("Connection lost: $e")
46+
} catch (e: EOFException) {
47+
println("Connection lost: $e")
48+
}
49+
}
50+
}
51+
52+
input@ while (!endCmdLine) {
53+
val str = readLine() ?: continue
54+
when (str.toUpperCase()) {
55+
"" -> continue@input
56+
"EXIT" -> exitProcess(0)
57+
"Q", "QUIT" -> {
58+
socOut.writeObject(str)
59+
exitProcess(0)
60+
}
61+
else -> socOut.writeObject(str)
62+
}
63+
}
64+
65+
} catch (e: SocketException) {
66+
println("Connection lost: $e")
67+
readerJob?.cancel()
68+
} catch (e: EOFException) {
69+
println("Connection lost: $e")
70+
readerJob?.cancel()
71+
}
72+
}
73+
}
74+
}

src/main/java/animatedledstrip/server/AnimatedLEDStripServer.kt

Lines changed: 109 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -31,113 +31,122 @@ import animatedledstrip.colors.ccpresets.CCBlue
3131
import animatedledstrip.leds.AnimatedLEDStrip
3232
import animatedledstrip.leds.emulated.EmulatedAnimatedLEDStrip
3333
import animatedledstrip.utils.delayBlocking
34-
import kotlinx.coroutines.GlobalScope
35-
import kotlinx.coroutines.launch
36-
import kotlinx.coroutines.newSingleThreadContext
3734
import org.apache.commons.cli.DefaultParser
38-
import org.apache.commons.cli.Options
3935
import org.pmw.tinylog.Configurator
4036
import org.pmw.tinylog.Level
4137
import org.pmw.tinylog.Logger
38+
import java.io.File
4239
import java.io.FileInputStream
4340
import java.io.FileNotFoundException
4441
import java.util.*
4542
import kotlin.reflect.KClass
4643
import kotlin.reflect.full.primaryConstructor
4744

48-
class AnimatedLEDStripServer <T: AnimatedLEDStrip> (
45+
class AnimatedLEDStripServer<T : AnimatedLEDStrip>(
4946
args: Array<String>,
5047
ledClass: KClass<T>
5148
) {
49+
/**
50+
* Is the server running
51+
*/
5252
internal var running = false
5353

54-
private val options = Options().apply {
55-
addOption("d", "Enable debugging")
56-
addOption("t", "Enable trace debugging")
57-
addOption("v", "Enable verbose log statements")
58-
addOption("q", "Disable log outputs")
59-
addOption("E", "Emulate LED strip but do NOT launch emulator")
60-
addOption("f", true, "Specify properties file")
61-
addOption("i", "Enable image debugging")
62-
addOption("T", "Run test")
63-
}
54+
/* Command line options and properties file */
6455

6556
private val cmdline = DefaultParser().parse(options, args)
6657

67-
var defaultPropertyFileName = "led.config"
58+
private var propertyFileName = cmdline.getOptionValue("f") ?: "led.config"
59+
60+
private var outputFileName: String? = cmdline.getOptionValue("o")
61+
62+
/* Set logging levels based on command line */
63+
init {
64+
val loggingPattern =
65+
if (cmdline.hasOption("v")) "{date:yyyy-MM-dd HH:mm:ss} [{thread}] {class}.{method}()\n{level}: {message}"
66+
else "{{level}:|min-size=8} {message}"
67+
68+
val loggingLevel =
69+
when {
70+
cmdline.hasOption("t") -> Level.TRACE
71+
cmdline.hasOption("d") -> Level.DEBUG
72+
cmdline.hasOption("q") -> Level.OFF
73+
else -> Level.INFO
74+
}
6875

69-
var defaultOutputFileName: String? = null
76+
Configurator.defaultConfig().formatPattern(loggingPattern).level(loggingLevel).addWriter(SocketWriter())
77+
.activate()
7078

71-
private val properties = Properties().apply {
79+
80+
}
81+
82+
private val properties: Properties? = Properties().apply {
7283
try {
73-
load(FileInputStream(cmdline.getOptionValue("f") ?: defaultPropertyFileName))
84+
load(FileInputStream(propertyFileName))
7485
} catch (e: FileNotFoundException) {
75-
Logger.warn("File ${cmdline.getOptionValue("f") ?: defaultPropertyFileName} not found")
86+
Logger.warn { "File $propertyFileName not found" }
7687
}
7788
}
7889

79-
val ports = mutableListOf<Int>().apply {
80-
Logger.debug(properties.getProperty("ports"))
81-
properties.getProperty("ports")?.split(' ')?.forEach {
82-
Logger.debug(it)
90+
/* Arguments for creating the AnimatedLEDStrip instance */
91+
92+
private val emulated: Boolean = cmdline.hasOption("e") || cmdline.hasOption("E")
93+
94+
private val numLEDs: Int = properties?.getProperty("numLEDs", "240")?.toInt() ?: 240
95+
96+
private val pin: Int = properties?.getProperty("pin", "12")?.toInt() ?: 12
97+
98+
private val imageDebuggingEnabled: Boolean = cmdline.hasOption("i")
99+
100+
private val ports = mutableListOf<Int>().apply {
101+
properties?.getProperty("ports")?.split(' ')?.forEach {
83102
requireNotNull(it.toIntOrNull())
84103
this.add(it.toInt())
85104
}
105+
if (!emulated) this += 1118 // local port
86106
}
87107

88-
private val leds = when (cmdline.hasOption("e") || cmdline.hasOption("E")) {
108+
private val rendersBeforeSave =
109+
properties?.getProperty("renders")?.toIntOrNull() ?: cmdline.getOptionValue("r")?.toIntOrNull() ?: 1000
110+
111+
private val leds = when (emulated) {
89112
false -> ledClass.primaryConstructor!!.call(
90-
properties.getProperty("numLEDs", "240").toInt(),
91-
properties.getProperty("pin", "12").toInt(),
92-
cmdline.hasOption("i"),
93-
cmdline.getOptionValue("o") ?: defaultOutputFileName
113+
numLEDs,
114+
pin,
115+
imageDebuggingEnabled,
116+
outputFileName,
117+
rendersBeforeSave
94118
)
95119
true -> EmulatedAnimatedLEDStrip(
96-
properties.getProperty("numLEDs", "240").toInt(),
97-
imageDebugging = cmdline.hasOption("i"),
98-
fileName = cmdline.getOptionValue("o") ?: defaultOutputFileName
120+
numLEDs,
121+
imageDebugging = imageDebuggingEnabled,
122+
fileName = outputFileName
99123
)
100124
}
101125

102-
internal val animationHandler = AnimationHandler(leds)
126+
private val persistAnimations =
127+
properties?.getProperty("persist", "false")?.toBoolean() ?: cmdline.hasOption("P")
128+
129+
internal val animationHandler = AnimationHandler(leds, persistAnimations = persistAnimations)
103130

104131
var testAnimation: AnimationData =
105132
AnimationData().animation(Animation.COLOR).color(CCBlue)
106133

107-
init {
108-
val pattern =
109-
if (cmdline.hasOption("v")) "{date:yyyy-MM-dd HH:mm:ss} [{thread}] {class}.{method}()\n{level}: {message}"
110-
else "{{level}:|min-size=8} {message}"
111-
112-
val level =
113-
when {
114-
cmdline.hasOption("t") -> Level.TRACE
115-
cmdline.hasOption("d") -> Level.DEBUG
116-
cmdline.hasOption("q") -> Level.OFF
117-
else -> Level.INFO
118-
}
119-
120-
Configurator.defaultConfig().formatPattern(pattern).level(level).activate()
121-
}
122-
134+
/* Start and stop methods */
123135

124136
fun start(): AnimatedLEDStripServer<T> {
137+
val dir = File(".animations")
138+
if (!dir.isDirectory)
139+
dir.mkdirs()
140+
125141
running = true
126-
startLocalTerminalReader()
127-
Logger.debug("Ports = $ports")
142+
Logger.debug { "Ports: $ports" }
128143
ports.forEach {
129144
SocketConnections.add(it, server = this).open()
130145
}
131146
if (cmdline.hasOption("T")) animationHandler.addAnimation(testAnimation)
132147
return this
133148
}
134149

135-
fun waitUntilStop() {
136-
while (running) {
137-
delayBlocking(1)
138-
}
139-
}
140-
141150
fun stop() {
142151
leds.setStripColor(0)
143152
delayBlocking(500)
@@ -146,20 +155,51 @@ class AnimatedLEDStripServer <T: AnimatedLEDStrip> (
146155
running = false
147156
}
148157

149-
@Suppress("EXPERIMENTAL_API_USAGE")
150-
val localThread = newSingleThreadContext("Local Terminal")
151-
152-
private fun startLocalTerminalReader() {
153-
GlobalScope.launch(localThread) {
154-
while (this@AnimatedLEDStripServer.running) {
155-
Logger.trace("Local terminal waiting for input")
156-
val strIn = readLine()
157-
Logger.trace("Read line")
158-
if (strIn?.toUpperCase() == "Q") {
159-
Logger.trace("'Q' received, shutting down server")
160-
stop()
158+
internal fun parseTextCommand(command: String) {
159+
val line = command.toUpperCase().split(" ")
160+
return when (line[0]) {
161+
"QUIT", "Q" -> {
162+
Logger.info { "Shutting down server" }
163+
stop()
164+
}
165+
"DEBUG" -> {
166+
setLoggingLevel(Level.DEBUG)
167+
Logger.debug("Set logging level to debug")
168+
}
169+
"TRACE" -> {
170+
setLoggingLevel(Level.TRACE)
171+
Logger.trace("Set logging level to trace")
172+
}
173+
"INFO" -> {
174+
setLoggingLevel(Level.INFO)
175+
Logger.info("Set logging level to info")
176+
}
177+
"CLEAR" -> {
178+
animationHandler.addAnimation(AnimationData().animation(Animation.COLOR))
179+
}
180+
"SHOW" -> {
181+
if (line.size > 1) Logger.info {
182+
"${line[1]}: ${animationHandler.continuousAnimations[line[1]]?.params ?: "NOT FOUND"}"
161183
}
184+
else Logger.info { "Running Animations: ${animationHandler.continuousAnimations.keys}" }
185+
}
186+
"END" -> {
187+
if (line.size > 1) {
188+
if (line[1].toUpperCase() == "ALL") {
189+
val animations = animationHandler.continuousAnimations
190+
animations.forEach {
191+
animationHandler.endAnimation(it.value)
192+
}
193+
} else for (i in 1 until line.size)
194+
animationHandler.endAnimation(animationHandler.continuousAnimations[line[i]])
195+
} else Logger.warn { "Animation ID must be specified" }
162196
}
197+
else -> Logger.warn { "$command is not a valid command" }
163198
}
164199
}
200+
201+
private fun setLoggingLevel(level: Level) {
202+
Configurator.currentConfig().level(level).activate()
203+
}
204+
165205
}

0 commit comments

Comments
 (0)