Skip to content

Commit

Permalink
Implementing first version of twinkly matrix display which contains s…
Browse files Browse the repository at this point in the history
…tripes with colors from the rest of the stage - just for testing performance
  • Loading branch information
sknull committed Dec 31, 2023
1 parent 0f63e2b commit 7891164
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 105 deletions.
10 changes: 10 additions & 0 deletions klanglicht-module-dmx/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@
<artifactId>klanglicht-module-dmx</artifactId>
<version>${revision}</version>

<properties>
<version.twinkly>0.0.1-SNAPSHOT</version.twinkly>
</properties>

<dependencies>

<dependency>
<groupId>de.visualdigits</groupId>
<artifactId>twinkly-api</artifactId>
<version>${version.twinkly}</version>
</dependency>

<!-- commons -->
<dependency>
<groupId>org.apache.commons</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ package de.visualdigits.kotlin.klanglicht.model.hybrid

enum class HybridDeviceType {
dmx,
shelly
shelly,
twinkly
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import de.visualdigits.kotlin.klanglicht.model.parameter.IntParameter
import de.visualdigits.kotlin.klanglicht.model.parameter.ParameterSet
import de.visualdigits.kotlin.klanglicht.model.preferences.Preferences
import de.visualdigits.kotlin.klanglicht.model.shelly.ShellyColor
import de.visualdigits.kotlin.klanglicht.model.twinkly.XledFrameFadeable
import de.visualdigits.kotlin.twinkly.model.playable.XledFrame
import kotlin.math.min
import kotlin.math.roundToInt

import de.visualdigits.kotlin.twinkly.model.color.RGBColor as TwinklyRGBColor
class HybridScene() : Fadeable<HybridScene> {

private var ids: String = ""
Expand Down Expand Up @@ -100,14 +102,65 @@ class HybridScene() : Fadeable<HybridScene> {
val nt = lTurnOns.size - 1
var t= 0

preferences?.stage
?.filter { it.type == HybridDeviceType.twinkly }
?.mapNotNull { preferences?.twinklyMap?.get(it.id) }
?.forEach { twinklyDevice ->
val xa = twinklyDevice.xledArray
val frame = XledFrame(width = xa.width, height = xa.height)
val nc = lHexColors.size
val barWidth = xa.width / nc
for (x in 0 until nc) {
val rgbColor = RGBColor(lHexColors[x])
val bar = XledFrame(width = barWidth, height = xa.height, initialColor = TwinklyRGBColor(rgbColor.red, rgbColor.green, rgbColor.blue))
frame.replaceSubFrame(bar, x * barWidth, 0)
}
val fadeable = XledFrameFadeable(
deviceId = twinklyDevice.name,
xledFrame = frame,
deviceGain = twinklyDevice.gain
)
fadeables[twinklyDevice.name] = fadeable
}

if (lIds.isNotEmpty()) {
lIds.forEach { id ->
val device = preferences?.stageMap?.get(id)
if (device != null) {
val hexColor = lHexColors[min(nh, h++)]
val gain = lGains.getOrNull(min(ng, g++))
val turnOn = lTurnOns.getOrNull(min(nt, t++))?:false
processDevice(device, preferences, id, gain, turnOn, hexColor)
val turnOn = lTurnOns.getOrNull(min(nt, t++)) ?: false
val rgbColor = RGBColor(hexColor)
when (device.type) {
HybridDeviceType.dmx -> {
val dmxDevice = preferences?.getDmxDevice(id)
if (dmxDevice != null) {
val paramGain = if (turnOn) (255 * (gain ?: dmxDevice.gain)).roundToInt() else 0
ParameterSet(
baseChannel = dmxDevice.baseChannel,
parameters = mutableListOf(
IntParameter("MasterDimmer", paramGain),
rgbColor
)
)
} else null
}

HybridDeviceType.shelly -> {
val shellyDevice = preferences?.shellyMap?.get(id)
if (shellyDevice != null) {
ShellyColor(
deviceId = shellyDevice.name,
ipAddress = shellyDevice.ipAddress,
color = rgbColor,
deviceGain = gain ?: shellyDevice.gain,
deviceTurnOn = turnOn
)
} else null
}

else -> null
}
?.let { dd -> fadeables[id] = dd }
}
}
Expand Down Expand Up @@ -143,46 +196,6 @@ class HybridScene() : Fadeable<HybridScene> {

fun getRgbColor(id: String): RGBColor? = fadeables[id]?.getRgbColor()

private fun processDevice(
device: HybridDevice,
preferences: Preferences?,
id: String,
gain: Float?,
turnOn: Boolean,
hexColor: String
): Fadeable<*>? {
return when (device.type) {
HybridDeviceType.dmx -> {
val dmxDevice = preferences?.getDmxDevice(id)
if (dmxDevice != null) {
val paramGain = if (turnOn) (255 * (gain ?: dmxDevice.gain)).roundToInt() else 0
ParameterSet(
baseChannel = dmxDevice.baseChannel,
parameters = mutableListOf(
IntParameter("MasterDimmer", paramGain),
RGBColor(hexColor)
)
)
} else null
}

HybridDeviceType.shelly -> {
val shellyDevice = preferences?.shellyMap?.get(id)
if (shellyDevice != null) {
ShellyColor(
deviceId = shellyDevice.name,
ipAddress = shellyDevice.ipAddress,
color = RGBColor(hexColor),
deviceGain = gain ?: shellyDevice.gain,
deviceTurnOn = turnOn
)
} else null
}

else -> null
}
}

override fun write(preferences: Preferences, write: Boolean, transitionDuration: Long) {
// first collect all frame data for the dmx frame to avoid lots of costly write operations to a serial interface
fadeables().filterIsInstance<ParameterSet>().forEach { parameterSet ->
Expand All @@ -194,32 +207,22 @@ class HybridScene() : Fadeable<HybridScene> {

// call shelly interface which is pretty fast
fadeables().filterIsInstance<ShellyColor>().forEach { it.write(preferences, true) }

// call twinkly interface which is pretty fast
fadeables().filterIsInstance<XledFrameFadeable>().forEach { it.write(preferences, true) }
}

override fun fade(other: Any, factor: Double): HybridScene {
return if (other is HybridScene) {
val otherIds = other.fadeables().map { it.getId() } // ensure that we only fade elements which are in source and target scene
val fadedHexColors = fadeables()
.filter { otherIds.contains(it.getId()) }
.zip(other.fadeables())
.mapNotNull {
when (val faded = it.first.fade(it.second, factor)) {
is ShellyColor -> {
Pair(faded.getId(), faded.getRgbColor())
}
is ParameterSet -> {
val second = faded.getRgbColor()
Pair(faded.getId(), second)
}
else -> null
}
}.toMap()
val fadedScene = HybridScene(fadeables, preferences)
fadedHexColors.forEach { (id, hexColor) ->
hexColor?.let { hc ->
fadedScene.setRgbColor(id, hc)
fadeables()
.filter { otherIds.contains(it.getId()) }
.zip(other.fadeables())
.map { Pair(it.first.fade(it.second, factor).getId(), it.first.fade(it.second, factor)) }
.forEach { (id, fadeable) ->
fadedScene.putFadeable(id, fadeable)
}
}

// fixme - at this point parameterMap contains default values - why?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,20 @@ import de.visualdigits.kotlin.klanglicht.model.fixture.Fixtures
import de.visualdigits.kotlin.klanglicht.model.hybrid.HybridDevice
import de.visualdigits.kotlin.klanglicht.model.shelly.ShellyDevice
import de.visualdigits.kotlin.klanglicht.model.twinkly.TwinklyConfiguration
import de.visualdigits.kotlin.twinkly.model.device.xled.XledArray
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import java.nio.file.Paths


@JsonIgnoreProperties("klanglichtDir", "dmxInterface", "fixtures", "serviceMap", "shellyMap", "stageMap")
@JsonIgnoreProperties("klanglichtDir", "dmxInterface", "fixtures", "serviceMap", "shellyMap", "twinklyMap", "stageMap")
data class Preferences(
val name: String = "",
val theme: String = "",
val services: List<Service> = listOf(),
val shelly: List<ShellyDevice>? = listOf(),
val twinkly: TwinklyConfiguration? = null,
val twinkly: List<TwinklyConfiguration>? = listOf(),
val stage: List<HybridDevice> = listOf(),
val dmx: Dmx? = null
) {
Expand All @@ -41,6 +42,8 @@ data class Preferences(

var shellyMap: Map<String, ShellyDevice> = mapOf()

var twinklyMap: Map<String, TwinklyConfiguration> = mapOf()

var stageMap: Map<String, HybridDevice> = mapOf()

companion object {
Expand Down Expand Up @@ -89,6 +92,8 @@ data class Preferences(

shellyMap = shelly?.associateBy { it.name }?:mapOf()

twinklyMap = twinkly?.map { Pair(it.name, it) }?.toMap()?:mapOf()

dmxInterface = dmx?.interfaceType?.let { DmxInterface.load(it) }
dmx?.port?.let { dmxInterface?.open(it) }
if (dmxInterface?.isOpen() == false) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
package de.visualdigits.kotlin.klanglicht.model.twinkly

import de.visualdigits.kotlin.twinkly.model.device.xled.DeviceOrigin
import de.visualdigits.kotlin.twinkly.model.device.xled.XLedDevice
import de.visualdigits.kotlin.twinkly.model.device.xled.XledArray

class TwinklyConfiguration(
val name: String,
val deviceOrigin: String,
val gain: Float,
val array: Array<Array<XledDeviceConfiguration>>
)
) {

val xledArray: XledArray

init {
xledArray = XledArray(
deviceOrigin = DeviceOrigin.valueOf(deviceOrigin),
xLedDevices = array.map { column ->
column.map { config ->
XLedDevice(
host = config.ipAddress,
width = config.width,
height = config.height
)
}.toTypedArray()
}.toTypedArray()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.visualdigits.kotlin.klanglicht.model.twinkly

import de.visualdigits.kotlin.klanglicht.model.color.RGBColor
import de.visualdigits.kotlin.klanglicht.model.parameter.Fadeable
import de.visualdigits.kotlin.klanglicht.model.preferences.Preferences
import de.visualdigits.kotlin.twinkly.model.playable.XledFrame
import de.visualdigits.kotlin.twinkly.model.color.RGBColor as TwinklyRGBColor

class XledFrameFadeable(
private val deviceId: String,
private var xledFrame: XledFrame,
private var deviceGain: Float
) : Fadeable<XledFrameFadeable> {

override fun getTurnOn(): Boolean = true

override fun getId(): String = deviceId

override fun getGain(): Float = deviceGain

override fun setGain(gain: Float) {
this.deviceGain = gain
}

override fun getRgbColor(): RGBColor {
val twinklyColor = xledFrame[0, 0].toRGB()
return RGBColor(twinklyColor.red, twinklyColor.green, twinklyColor.blue)
}

override fun setRgbColor(rgbColor: RGBColor) {
xledFrame.setColor(TwinklyRGBColor(rgbColor.red, rgbColor.green, rgbColor.blue))
}

override fun write(preferences: Preferences, write: Boolean, transitionDuration: Long) {
val twinklyDevice = preferences.twinklyMap[deviceId]
if (twinklyDevice != null) {
val xledArray = twinklyDevice.xledArray
xledArray.setBrightness(deviceGain)
xledFrame.play(xledArray)
}
}

override fun fade(other: Any, factor: Double): XledFrameFadeable {
return if (other is XledFrameFadeable) {
val xledFrame1 = xledFrame.fade(other.xledFrame, factor)
XledFrameFadeable(deviceId, xledFrame1, deviceGain)
} else {
throw IllegalArgumentException("Cannot not fade another type")
}
}
}
7 changes: 0 additions & 7 deletions klanglicht-rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

<properties>
<version.feign>12.2</version.feign>
<version.twinkly>0.0.1-SNAPSHOT</version.twinkly>
<version.spring-boot>3.1.2</version.spring-boot>
</properties>

Expand All @@ -38,12 +37,6 @@
<version>${revision}</version>
</dependency>

<dependency>
<groupId>de.visualdigits</groupId>
<artifactId>twinkly-api</artifactId>
<version>${version.twinkly}</version>
</dependency>

<!-- common stuff -->
<dependency>
<groupId>org.apache.commons</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import de.visualdigits.kotlin.klanglicht.model.hybrid.HybridScene
import de.visualdigits.kotlin.klanglicht.model.parameter.Fadeable
import de.visualdigits.kotlin.klanglicht.model.preferences.Preferences
import de.visualdigits.kotlin.klanglicht.model.shelly.ShellyDevice
import de.visualdigits.kotlin.klanglicht.model.twinkly.TwinklyConfiguration
import de.visualdigits.kotlin.twinkly.model.device.xled.DeviceOrigin
import de.visualdigits.kotlin.twinkly.model.device.xled.XLedDevice
import de.visualdigits.kotlin.twinkly.model.device.xled.XledArray
Expand All @@ -33,7 +34,7 @@ class ConfigHolder {

var shellyDevices: Map<String, ShellyDevice> = mapOf()

var xledArray: XledArray? = null
var xledArray: Map<String, XledArray> = mapOf()
var xledDevices: Map<String, XLedDevice> = mapOf()

@PostConstruct
Expand Down Expand Up @@ -61,18 +62,10 @@ class ConfigHolder {

// initialize twinkly devices
val twinkly = preferences?.twinkly
val deviceOrigin = twinkly?.deviceOrigin?.let { DeviceOrigin.valueOf(it) }?: DeviceOrigin.TOP_LEFT
val xledDevices: MutableMap<String, XLedDevice> = mutableMapOf()
xledArray = twinkly?.array?.map { column ->
column.map { config ->
val xledDevice = XLedDevice(host = config.ipAddress, config.width, config.height)
xledDevices[config.name] = xledDevice
xledDevice
}.toTypedArray()
}?.toTypedArray()
?.let { devices ->
XledArray(deviceOrigin = deviceOrigin, xLedDevices = devices)
}
xledArray = twinkly?.associate { config ->
Pair(config.name, config.xledArray)
} ?: mapOf()
this.xledDevices = xledDevices
log.info("##### Using twinkly devices '${xledDevices.keys}'")

Expand Down Expand Up @@ -117,6 +110,10 @@ class ConfigHolder {
return preferences?.shellyMap?.get(id)
}

fun getTwinklyDevice(id: String): TwinklyConfiguration? {
return preferences?.twinklyMap?.get(id)
}

fun getDmxDevice(id: String): DmxDevice? {
return preferences?.dmx?.dmxDevices?.get(id)
}
Expand Down
Loading

0 comments on commit 7891164

Please sign in to comment.