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
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
dokka(project(":core"))
dokka(project(":devices"))
dokka(project(":discover"))
dokka(project(":entertainment"))
dokka(project(":events"))
dokka(project(":grouped-lights"))
dokka(project(":homekit"))
Expand Down
53 changes: 53 additions & 0 deletions cli/src/main/kotlin/inkapplications/shade/cli/Arguments.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import com.github.ajalt.clikt.parameters.types.choice
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.colormath.Color
import com.github.ajalt.colormath.parse
import inkapplications.shade.entertainment.parameters.ServiceLocationCreateParameters
import inkapplications.shade.entertainment.parameters.ServiceLocationUpdateParameters
import inkapplications.shade.lights.structures.*
import inkapplications.shade.structures.*
import inkapplications.spondee.measure.ColorTemperature
Expand Down Expand Up @@ -280,3 +282,54 @@ fun NullableOption<String, String>.durations(): NullableOption<List<Duration?>,
.map { it?.milliseconds }
}
}


/**
* Convert a string option into a list of service location create parameters.
*
* Format: id:x,y,z;id:x,y,z
* Example: abc-123:0.5,0.5,0.0;def-456:-0.5,0.5,0.0
*/
fun NullableOption<String, String>.entertainmentServiceLocations(): NullableOption<List<ServiceLocationCreateParameters>, List<ServiceLocationCreateParameters>> {
return convert {
it.split(";")
.map { location ->
val parts = location.split(":")
val id = parts[0].trim()
val coords = parts[1].split(",").map { c -> c.trim().toDouble() }
ServiceLocationCreateParameters(
service = ResourceReference(
id = ResourceId(id),
type = ResourceType.Entertainment,
),
positions = listOf(Position(x = coords[0], y = coords[1], z = coords[2])),
)
}
}
}

/**
* Convert a string option into a list of service location update parameters.
*
* Format: id:x,y,z[:equalizationFactor];id:x,y,z[:equalizationFactor]
* Example: abc-123:0.5,0.5,0.0;def-456:-0.5,0.5,0.0:0.8
*/
fun NullableOption<String, String>.entertainmentServiceLocationsUpdate(): NullableOption<List<ServiceLocationUpdateParameters>, List<ServiceLocationUpdateParameters>> {
return convert {
it.split(";")
.map { location ->
val mainParts = location.split(":")
val id = mainParts[0].trim()
val coords = mainParts[1].split(",").map { c -> c.trim().toDouble() }
val equalizationFactor = if (mainParts.size > 2) mainParts[2].trim().toFloat() else null
ServiceLocationUpdateParameters(
service = ResourceReference(
id = ResourceId(id),
type = ResourceType.Entertainment,
),
positions = listOf(Position(x = coords[0], y = coords[1], z = coords[2])),
equalizationFactor = equalizationFactor,
)
}
}
}
7 changes: 7 additions & 0 deletions cli/src/main/kotlin/inkapplications/shade/cli/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import inkapplications.shade.cli.buttons.*
import inkapplications.shade.cli.connection.AuthorizeCommand
import inkapplications.shade.cli.connection.DiscoverCommand
import inkapplications.shade.cli.devices.*
import inkapplications.shade.cli.entertainment.*
import inkapplications.shade.cli.events.EventsCommand
import inkapplications.shade.cli.groupedlights.GetGroupedLightCommand
import inkapplications.shade.cli.groupedlights.ListGroupedLightsCommand
Expand All @@ -26,17 +27,20 @@ class Main: NoOpCliktCommand() {
init {
subcommands(
AuthorizeCommand,
CreateEntertainmentConfigurationCommand,
CreateRoomCommand,
CreateSceneCommand,
CreateZoneCommand,
DeleteDeviceCommand,
DeleteEntertainmentConfigurationCommand,
DeleteRoomCommand,
DeleteSceneCommand,
DeleteZoneCommand,
DiscoverCommand,
EventsCommand,
GetButtonCommand,
GetDeviceCommand,
GetEntertainmentConfigurationCommand,
GetGroupedLightCommand,
GetHomekitCommand,
GetLightCommand,
Expand All @@ -47,6 +51,8 @@ class Main: NoOpCliktCommand() {
IdentifyDeviceCommand,
ListButtonsCommand,
ListDevicesCommand,
ListEntertainmentCommand,
ListEntertainmentConfigurationsCommand,
ListGroupedLightsCommand,
ListHomekitCommand,
ListLightsCommand,
Expand All @@ -57,6 +63,7 @@ class Main: NoOpCliktCommand() {
ListZonesCommand,
UpdateButtonCommand,
UpdateDeviceCommand,
UpdateEntertainmentConfigurationCommand,
UpdateGroupedLightCommand,
UpdateHomekitCommand,
UpdateLightCommand,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package inkapplications.shade.cli.entertainment

import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import inkapplications.shade.cli.AuthorizedShadeCommand
import inkapplications.shade.cli.entertainmentServiceLocations
import inkapplications.shade.entertainment.parameters.EntertainmentConfigurationCreateParameters
import inkapplications.shade.entertainment.parameters.EntertainmentLocationsCreateParameters
import inkapplications.shade.entertainment.structures.EntertainmentConfigurationMetadata
import inkapplications.shade.entertainment.structures.EntertainmentConfigurationType

object CreateEntertainmentConfigurationCommand: AuthorizedShadeCommand(
help = "Create a new entertainment configuration on the Hue bridge"
) {
private val name by argument(
help = "A human-readable name for the entertainment configuration"
)

private val configurationType by argument(
help = "The type of entertainment configuration (screen, monitor, music, 3dspace, other)"
).enum<ConfigurationTypes>()

private val serviceLocations by option(
help = "Service locations in format: id:x,y,z;id:x,y,z (e.g. abc-123:0.5,0.5,0.0;def-456:-0.5,0.5,0.0)"
).entertainmentServiceLocations()

override suspend fun runCommand(): Int {
val response = shade.entertainmentConfig.createConfiguration(
parameters = EntertainmentConfigurationCreateParameters(
metadata = EntertainmentConfigurationMetadata(
name = name,
),
configurationType = configurationType.type,
locations = EntertainmentLocationsCreateParameters(
serviceLocations = serviceLocations.orEmpty(),
),
)
)

logger.debug("Got response: $response")
echo("Created entertainment configuration: ${response.id}")

return 0
}

private enum class ConfigurationTypes(val type: EntertainmentConfigurationType) {
Screen(EntertainmentConfigurationType.Screen),
Monitor(EntertainmentConfigurationType.Monitor),
Music(EntertainmentConfigurationType.Music),
ThreeDSpace(EntertainmentConfigurationType.ThreeDSpace),
Other(EntertainmentConfigurationType.Other),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package inkapplications.shade.cli.entertainment

import com.github.ajalt.clikt.parameters.arguments.argument
import inkapplications.shade.cli.AuthorizedShadeCommand
import inkapplications.shade.cli.resourceId

object DeleteEntertainmentConfigurationCommand: AuthorizedShadeCommand(
help = "Delete an entertainment configuration from the Hue bridge"
) {
private val configurationId by argument(
help = "The ID of the entertainment configuration to delete"
).resourceId()

override suspend fun runCommand(): Int {
val response = shade.entertainmentConfig.deleteConfiguration(configurationId)

logger.debug("Got response: $response")
echo("Deleted entertainment configuration: ${response.id}")

return 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package inkapplications.shade.cli.entertainment

import com.github.ajalt.clikt.core.CliktCommand
import inkapplications.shade.entertainment.structures.EntertainmentConfiguration

fun CliktCommand.echoEntertainmentConfiguration(config: EntertainmentConfiguration) {
echo("${config.id.value}:")
echo(" Name: ${config.metadata.name}")
echo(" Configuration Type: ${config.configurationType}")
echo(" Status: ${config.status}")
val activeStreamer = config.activeStreamer
if (activeStreamer != null) {
echo(" Active Streamer: $activeStreamer")
}
echo(" Stream Proxy Mode: ${config.streamProxy.mode}")
echo(" Stream Proxy Node: ${config.streamProxy.node}")
echo(" Channels:")
config.channels.forEach { channel ->
echo(" ${channel.channelId}:")
echo(" Position: (${channel.position.x}, ${channel.position.y}, ${channel.position.z})")
echo(" Members:")
channel.members.forEach { member ->
echo(" - ${member.service} [${member.index}]")
}
}
echo(" Locations:")
config.locations.serviceLocations.forEach { location ->
echo(" ${location.service}:")
echo(" Positions: ${location.positions.joinToString { "(${it.x}, ${it.y}, ${it.z})" }}")
echo(" Equalization Factor: ${location.equalizationFactor}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package inkapplications.shade.cli.entertainment

import com.github.ajalt.clikt.core.CliktCommand
import inkapplications.shade.entertainment.structures.Entertainment

fun CliktCommand.echoEntertainment(entertainment: Entertainment) {
echo("${entertainment.id.value}:")
echo(" Owner: ${entertainment.owner}")
echo(" Renderer: ${entertainment.renderer}")
val renderReference = entertainment.rendererReference
if (renderReference != null) {
echo(" Renderer Reference: $renderReference")
}
echo(" Proxy: ${entertainment.proxy}")
echo(" Equalizer: ${entertainment.equalizer}")
val maxStreams = entertainment.maxStreams
if (maxStreams != null) {
echo(" Max Streams: $maxStreams")
}
entertainment.segments?.let { segments ->
echo(" Segments:")
echo(" Configurable: ${segments.configurable}")
echo(" Max Segments: ${segments.maxSegments}")
echo(" Ranges:")
segments.segmentRanges.forEach { range ->
echo(" - ${range.first}:${range.last}")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package inkapplications.shade.cli.entertainment

import com.github.ajalt.clikt.parameters.arguments.argument
import inkapplications.shade.cli.AuthorizedShadeCommand
import inkapplications.shade.cli.resourceId

object GetEntertainmentConfigurationCommand: AuthorizedShadeCommand(
help = "Get data for a specific entertainment configuration"
) {
private val configurationId by argument().resourceId()

override suspend fun runCommand(): Int {
val configuration = shade.entertainmentConfig.getConfiguration(configurationId)

logger.debug("Got Entertainment Configuration: $configuration")
echoEntertainmentConfiguration(configuration)

return 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package inkapplications.shade.cli.entertainment

import inkapplications.shade.cli.AuthorizedShadeCommand

object ListEntertainmentCommand: AuthorizedShadeCommand(
help = "Get all of the entertainment resources configured on the Hue bridge"
) {
override suspend fun runCommand(): Int {
val entertainmentResources = shade.entertainment.listEntertainment()

logger.debug("Got Entertainment: $entertainmentResources")
entertainmentResources.forEach(::echoEntertainment)

return 0
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package inkapplications.shade.cli.entertainment

import inkapplications.shade.cli.AuthorizedShadeCommand

object ListEntertainmentConfigurationsCommand: AuthorizedShadeCommand(
help = "Get all of the entertainment configurations on the Hue bridge"
) {
override suspend fun runCommand(): Int {
val configurations = shade.entertainmentConfig.listConfigurations()

logger.debug("Got Entertainment Configurations: $configurations")
configurations.forEach(::echoEntertainmentConfiguration)

return 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package inkapplications.shade.cli.entertainment

import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import inkapplications.shade.cli.AuthorizedShadeCommand
import inkapplications.shade.cli.entertainmentServiceLocationsUpdate
import inkapplications.shade.cli.resourceId
import inkapplications.shade.entertainment.parameters.EntertainmentConfigurationUpdateParameters
import inkapplications.shade.entertainment.parameters.EntertainmentLocationsUpdateParameters
import inkapplications.shade.entertainment.structures.EntertainmentConfigurationAction
import inkapplications.shade.entertainment.structures.EntertainmentConfigurationMetadata
import inkapplications.shade.entertainment.structures.EntertainmentConfigurationType

object UpdateEntertainmentConfigurationCommand: AuthorizedShadeCommand(
help = "Update an existing entertainment configuration on the Hue bridge"
) {
private val configurationId by argument(
help = "The ID of the entertainment configuration to update"
).resourceId()

private val name by option(
help = "A human-readable name for the entertainment configuration"
)

private val configurationType by option(
help = "The type of entertainment configuration (screen, monitor, music, 3dspace, other)"
).enum<ConfigurationTypes>()

private val action by option(
help = "Action to control streaming (start, stop)"
).enum<StreamActions>()

private val serviceLocations by option(
help = "Service locations in format: id:x,y,z[:equalizationFactor];id:x,y,z[:equalizationFactor] (e.g. abc-123:0.5,0.5,0.0;def-456:-0.5,0.5,0.0:0.8)"
).entertainmentServiceLocationsUpdate()

override suspend fun runCommand(): Int {
val metadata = if (name != null) {
EntertainmentConfigurationMetadata(name = name!!)
} else {
null
}

val locations = if (serviceLocations != null) {
EntertainmentLocationsUpdateParameters(serviceLocations = serviceLocations!!)
} else {
null
}

val response = shade.entertainmentConfig.updateConfiguration(
id = configurationId,
parameters = EntertainmentConfigurationUpdateParameters(
metadata = metadata,
configurationType = configurationType?.type,
action = action?.action,
locations = locations,
)
)

logger.debug("Got response: $response")
echo("Updated entertainment configuration: ${response.id}")

return 0
}

private enum class ConfigurationTypes(val type: EntertainmentConfigurationType) {
Screen(EntertainmentConfigurationType.Screen),
Monitor(EntertainmentConfigurationType.Monitor),
Music(EntertainmentConfigurationType.Music),
ThreeDSpace(EntertainmentConfigurationType.ThreeDSpace),
Other(EntertainmentConfigurationType.Other),
}

private enum class StreamActions(val action: EntertainmentConfigurationAction) {
Start(EntertainmentConfigurationAction.Start),
Stop(EntertainmentConfigurationAction.Stop),
}
}

Loading