@@ -31,113 +31,122 @@ import animatedledstrip.colors.ccpresets.CCBlue
3131import animatedledstrip.leds.AnimatedLEDStrip
3232import animatedledstrip.leds.emulated.EmulatedAnimatedLEDStrip
3333import animatedledstrip.utils.delayBlocking
34- import kotlinx.coroutines.GlobalScope
35- import kotlinx.coroutines.launch
36- import kotlinx.coroutines.newSingleThreadContext
3734import org.apache.commons.cli.DefaultParser
38- import org.apache.commons.cli.Options
3935import org.pmw.tinylog.Configurator
4036import org.pmw.tinylog.Level
4137import org.pmw.tinylog.Logger
38+ import java.io.File
4239import java.io.FileInputStream
4340import java.io.FileNotFoundException
4441import java.util.*
4542import kotlin.reflect.KClass
4643import 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