diff --git a/klanglicht-core/src/main/kotlin/de/visualdigits/kotlin/klanglicht/model/parameter/Scene.kt b/klanglicht-core/src/main/kotlin/de/visualdigits/kotlin/klanglicht/model/parameter/Scene.kt index 7f4747e..f305173 100644 --- a/klanglicht-core/src/main/kotlin/de/visualdigits/kotlin/klanglicht/model/parameter/Scene.kt +++ b/klanglicht-core/src/main/kotlin/de/visualdigits/kotlin/klanglicht/model/parameter/Scene.kt @@ -34,6 +34,23 @@ class Scene( } } + fun fade( + other: Scene, + fadeDuration: Long, + preferences: Preferences + ) { + val dmxFrameTime = preferences.dmx.frameTime // millis + val step = 1.0 / fadeDuration.toDouble() * dmxFrameTime.toDouble() + var factor = 0.0 + + while (factor < 1.0) { + fade(other, factor).write(preferences) + factor += step + Thread.sleep(dmxFrameTime) + } + other.write(preferences) + } + override fun fade( other: Any, factor: Double @@ -51,21 +68,4 @@ class Scene( } else throw IllegalArgumentException("Cannot not fade another type") } - - fun fade( - other: Scene, - fadeDuration: Long, - preferences: Preferences - ) { - val dmxFrameTime = preferences.dmx.frameTime // millis - val step = 1.0 / fadeDuration.toDouble() * dmxFrameTime.toDouble() - var factor = 0.0 - - while (factor < 1.0) { - fade(other, factor).write(preferences) - factor += step - Thread.sleep(dmxFrameTime) - } - other.write(preferences) - } } diff --git a/klanglicht-core/src/main/kotlin/de/visualdigits/kotlin/klanglicht/model/preferences/Preferences.kt b/klanglicht-core/src/main/kotlin/de/visualdigits/kotlin/klanglicht/model/preferences/Preferences.kt index 757c8fd..60ae7cd 100644 --- a/klanglicht-core/src/main/kotlin/de/visualdigits/kotlin/klanglicht/model/preferences/Preferences.kt +++ b/klanglicht-core/src/main/kotlin/de/visualdigits/kotlin/klanglicht/model/preferences/Preferences.kt @@ -11,7 +11,7 @@ import java.io.File import java.nio.file.Paths -@JsonIgnoreProperties("klanglichtDir", "dmxInterface", "fixtures", "serviceMap", "stageMap") +@JsonIgnoreProperties("klanglichtDir", "dmxInterface", "fixtures", "serviceMap", "shellyMap", "stageMap") data class Preferences( val name: String = "", val services: List = listOf(), @@ -29,6 +29,8 @@ data class Preferences( var serviceMap: Map = mapOf() + var shellyMap: Map = mapOf() + var stageMap: Map = mapOf() fun init(klanglichtDir: File) { @@ -47,6 +49,8 @@ data class Preferences( stageMap = stage.associateBy { it.id } + shellyMap = shelly.associateBy { it.name } + val dmxInterface = DmxInterface.load(dmx.interfaceType) dmxInterface.open(dmx.port) this.dmxInterface = dmxInterface diff --git a/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/handler/HybridStageHandler.kt b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/handler/HybridStageHandler.kt index ee6463b..45f5c51 100644 --- a/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/handler/HybridStageHandler.kt +++ b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/handler/HybridStageHandler.kt @@ -5,7 +5,6 @@ import de.visualdigits.kotlin.klanglicht.model.hybrid.HybridDeviceType import de.visualdigits.kotlin.klanglicht.rest.dmx.handler.DmxHandler import de.visualdigits.kotlin.klanglicht.rest.common.configuration.ConfigHolder import de.visualdigits.kotlin.klanglicht.rest.shelly.handler.ShellyHandler -import org.apache.commons.lang3.StringUtils import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -43,7 +42,6 @@ class HybridStageHandler { .map { it.trim() } val nd = lIds.size - 1 var d = 0 - val hasIds = StringUtils.isNotEmpty(ids) val lHexColors = hexColors .split(",") @@ -57,29 +55,28 @@ class HybridStageHandler { .map { it.toFloat() } val ng = lGains.size - 1 var g = 0 - val overrideGains = StringUtils.isNotEmpty(gains) + val dmxIds: MutableList = ArrayList() val dmxHexColors: MutableList = ArrayList() val dmxGains: MutableList = ArrayList() val shellyIds: MutableList = ArrayList() val shellyHexColors: MutableList = ArrayList() val shellyGains: MutableList = ArrayList() - if (hasIds) { + if (lIds.isNotEmpty()) { for (id in lIds) { - val sid = id val hexColor = lHexColors[h] val gain = lGains[g] - val device = configHolder!!.preferences?.stageMap?.get(sid) + val device = configHolder!!.preferences?.stageMap?.get(id) if (device != null) { processDevice( - overrideGains, + lGains.isNotEmpty(), dmxIds, dmxHexColors, dmxGains, shellyIds, shellyHexColors, shellyGains, - sid, + id, hexColor, gain, device @@ -97,11 +94,11 @@ class HybridStageHandler { } } else { configHolder!!.preferences?.stage?.forEach { device -> - val sid = device.id.trim()?:"" + val sid = device.id.trim() ?: "" val hexColor = lHexColors[h] val gain = lGains[g] processDevice( - overrideGains, + lGains.isNotEmpty(), dmxIds, dmxHexColors, dmxGains, diff --git a/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/HybridScene.kt b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/HybridScene.kt new file mode 100644 index 0000000..9377708 --- /dev/null +++ b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/HybridScene.kt @@ -0,0 +1,147 @@ +package de.visualdigits.kotlin.klanglicht.rest.hybrid.model + +import de.visualdigits.kotlin.klanglicht.model.color.RGBColor +import de.visualdigits.kotlin.klanglicht.model.hybrid.HybridDevice +import de.visualdigits.kotlin.klanglicht.model.hybrid.HybridDeviceType +import de.visualdigits.kotlin.klanglicht.model.parameter.Fadeable +import de.visualdigits.kotlin.klanglicht.model.parameter.IntParameter +import de.visualdigits.kotlin.klanglicht.model.parameter.ParameterSet +import de.visualdigits.kotlin.klanglicht.model.parameter.Scene +import de.visualdigits.kotlin.klanglicht.model.preferences.Preferences +import de.visualdigits.kotlin.klanglicht.rest.shelly.client.ShellyClient +import kotlin.math.min +import kotlin.math.roundToInt + +class HybridScene( + val ids: String, + val hexColors: String, + val gains: String, + val preferences: Preferences +) : Fadeable { + + private val fadeables: List> + + init { + val lIds = ids + .split(",") + .filter { it.isNotEmpty() } + .map { it.trim() } + val nd = lIds.size - 1 + var d = 0 + + val lHexColors = hexColors + .split(",") + .filter { it.isNotEmpty() } + val nh = lHexColors.size - 1 + var h = 0 + + val lGains = gains + .split(",") + .filter { it.isNotEmpty() } + .map { it.toFloat() } + val ng = lGains.size - 1 + var g = 0 + + fadeables = if (lIds.isNotEmpty()) { + lIds.mapNotNull { id -> + val device = preferences.stageMap[id] + val hexColor = lHexColors[min(nh, h++)] + val gain = lGains.getOrNull(min(ng, g++)) + device?.let { d -> processDevice(d, preferences, id, gain, hexColor) } + } + } else { + preferences.stage.mapNotNull { device -> + val hexColor = lHexColors[min(nh, h++)] + val gain = lGains.getOrNull(min(ng, g++)) + processDevice(device, preferences, device.id, gain, hexColor) } + } + } + + override fun toString(): String { + return hexColors + .split(",") + .filter { it.isNotEmpty() } + .map { RGBColor(it).ansiColor() } + .joinToString("") + } + + private fun processDevice( + device: HybridDevice, + preferences: Preferences, + id: String, + gain: Float?, + hexColor: String + ): Fadeable<*>? { + return when (device.type) { + HybridDeviceType.dmx -> { + val dmxDevice = preferences.dmx.dmxDevices[id] + if (dmxDevice != null) { + ParameterSet( + baseChannel = dmxDevice.baseChannel, + parameters = mutableListOf( + IntParameter("MasterDimmer", (255 * (gain ?: dmxDevice.gain)).roundToInt()), + RGBColor(hexColor) + ) + ) + } else null + } + + HybridDeviceType.shelly -> { + val shellyDevice = preferences.shellyMap[id] + if (shellyDevice != null) { + ShellyColor(shellyDevice.ipAddress, RGBColor(hexColor), gain ?: shellyDevice.gain) + } else null + } + + else -> null + } + } + + fun write(preferences: Preferences, write: Boolean = true) { + Scene( + name = "HybridScene", + parameterSet = fadeables.filterIsInstance() + ).write(preferences, write) + fadeables + .filterIsInstance() + .forEach { shellyColor -> ShellyClient.setColor(shellyColor.ipAddress, shellyColor.color, shellyColor.gain) } + } + + fun fade( + other: HybridScene, + fadeDuration: Long, + preferences: Preferences + ) { + val dmxFrameTime = preferences.dmx.frameTime // millis + val step = 1.0 / fadeDuration.toDouble() * dmxFrameTime.toDouble() + var factor = 0.0 + + while (factor < 1.0) { + fade(other, factor).write(preferences) + factor += step + Thread.sleep(dmxFrameTime) + } + other.write(preferences) + } + + override fun fade(other: Any, factor: Double): HybridScene { + return if (other is HybridScene) { + val fadedHexColors = fadeables + .zip(other.fadeables) + .mapNotNull { + val faded = it.first.fade(it.second, factor) + when (faded) { + is ShellyColor -> faded.color.hex() + is ParameterSet -> faded.parameters + .filterIsInstance() + .firstOrNull() + ?.let { c -> c.hex() } + else -> null + } + }.joinToString(",") + HybridScene(ids, fadedHexColors, gains, preferences) + } else { + throw IllegalArgumentException("Cannot not fade another type") + } + } +} diff --git a/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/ShellyColor.kt b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/ShellyColor.kt new file mode 100644 index 0000000..49db5d2 --- /dev/null +++ b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/ShellyColor.kt @@ -0,0 +1,19 @@ +package de.visualdigits.kotlin.klanglicht.rest.hybrid.model + +import de.visualdigits.kotlin.klanglicht.model.color.RGBColor +import de.visualdigits.kotlin.klanglicht.model.parameter.Fadeable + +class ShellyColor( + val ipAddress: String, + val color: RGBColor, + val gain: Float +): Fadeable { + + override fun fade(other: Any, factor: Double): ShellyColor { + return if (other is ShellyColor) { + ShellyColor(ipAddress, color.fade(other.color, factor), gain) + } else { + throw IllegalArgumentException("Cannot not fade another type") + } + } +} diff --git a/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/shelly/client/ShellyClient.kt b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/shelly/client/ShellyClient.kt index d3d71c3..59c2006 100644 --- a/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/shelly/client/ShellyClient.kt +++ b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/shelly/client/ShellyClient.kt @@ -7,7 +7,7 @@ import java.net.URL object ShellyClient { - fun power( + fun setPower( ipAddress: String, command: String, turnOn: Boolean, @@ -16,7 +16,7 @@ object ShellyClient { return URL("http://" + ipAddress + "/" + command + "?turn=" + (if (turnOn) "on" else "off") + "&transition=" + transitionDuration + "&").readText() } - fun gain( + fun setGain( ipAddress: String, gain: Int, transitionDuration: Long @@ -24,19 +24,19 @@ object ShellyClient { return URL("http://$ipAddress/color/0?gain=$gain&transition=$transitionDuration&").readText() } - fun status( + fun getStatus( ipAddress: String ): Status { val json = URL("http://$ipAddress/status").readText() return Status.load(json) } - fun color( + fun setColor( ipAddress: String, rgbColor: RGBColor, gain: Float, - transitionDuration: Long, - turnOn: Boolean, + transitionDuration: Long = 0, + turnOn: Boolean = true, ): Light { val json = URL( "http://$ipAddress/color/0?" + diff --git a/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/shelly/handler/ShellyHandler.kt b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/shelly/handler/ShellyHandler.kt index 0fa6023..817d9ee 100644 --- a/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/shelly/handler/ShellyHandler.kt +++ b/klanglicht-rest/src/main/kotlin/de/visualdigits/kotlin/klanglicht/rest/shelly/handler/ShellyHandler.kt @@ -13,7 +13,6 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component -import java.util.Arrays @Component class ShellyHandler { @@ -214,7 +213,7 @@ class ShellyHandler { configHolder.putColor(sid, lastColor) // update state log.info("power: $ipAddress") try { - ShellyClient.power( + ShellyClient.setPower( ipAddress = ipAddress, command = command, turnOn = turnOn, @@ -245,7 +244,7 @@ class ShellyHandler { lastColor.gain = gain.toFloat() configHolder.putColor(sid, lastColor) // update state try { - ShellyClient.gain(ipAddress = ipAddress, gain = gain, transitionDuration = transitionDuration) + ShellyClient.setGain(ipAddress = ipAddress, gain = gain, transitionDuration = transitionDuration) } catch (e: Exception) { log.warn("Could not get gain for shelly at '$ipAddress'") } @@ -269,7 +268,7 @@ class ShellyHandler { log.info("get status: $url") var status: Status try { - status = ShellyClient.status(ipAddress) + status = ShellyClient.getStatus(ipAddress) } catch (e: Exception) { log.warn("Could not get ststus for url '$url'") status = Status() @@ -304,7 +303,7 @@ class ShellyHandler { val ipAddress: String = shellyDevice.ipAddress log.info("setColor: $ipAddress") try { - ShellyClient.color( + ShellyClient.setColor( ipAddress = ipAddress, rgbColor = rgbColor, gain = gain, diff --git a/klanglicht-rest/src/test/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/HybridSceneTest.kt b/klanglicht-rest/src/test/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/HybridSceneTest.kt new file mode 100644 index 0000000..8de7690 --- /dev/null +++ b/klanglicht-rest/src/test/kotlin/de/visualdigits/kotlin/klanglicht/rest/hybrid/model/HybridSceneTest.kt @@ -0,0 +1,35 @@ +package de.visualdigits.kotlin.klanglicht.rest.hybrid.model + +import de.visualdigits.kotlin.klanglicht.model.preferences.Preferences +import org.apache.commons.lang3.SystemUtils +import org.junit.jupiter.api.Test +import java.io.File + +class HybridSceneTest { + + val preferences = Preferences.load(File(SystemUtils.getUserHome(), ".klanglicht")) + + @Test + fun testFade() { + val scene1 = HybridScene( + ids = "Starwars,Rgbw,15,29,Bar", + hexColors = "#ff0000,#00ff00,#0000ff,#ffff00,#00ffff", + gains = "", + preferences = preferences + ) + println(scene1) + val scene2 = HybridScene( + ids = "Starwars,Rgbw,15,29,Bar", + hexColors = "#00ffff,#ff00ff,#ffff00,#0000ff,#ff0000", + gains = "", + preferences = preferences + ) + println(scene2) + println() + val n = 10 + for (f in 0 until n) { + val faded = scene1.fade(scene2, f.toDouble() / n) + println(faded) + } + } +}