├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── api-bugs.md │ ├── cli-bugs.md │ ├── device-bugs.md │ ├── feature-request.md │ └── question.md ├── dependabot.yml └── workflows │ ├── pull_requests.yml │ ├── pushes.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── auth ├── api │ └── auth.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── auth │ │ ├── AuthModule.kt │ │ ├── BridgeAuth.kt │ │ ├── ShadeBridgeAuth.kt │ │ └── structures │ │ ├── AppId.kt │ │ └── AuthRequest.kt │ └── jvmTest │ └── kotlin │ └── inkapplications │ └── shade │ └── auth │ ├── ShadeBridgeAuthTest.kt │ └── structures │ └── AppIdSerializerTest.kt ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── ink.publishing.gradle.kts │ └── library.gradle.kts ├── cli ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── inkapplications │ └── shade │ └── cli │ ├── Arguments.kt │ ├── AuthorizedShadeCommand.kt │ ├── Main.kt │ ├── ShadeCommand.kt │ ├── connection │ ├── AuthorizeCommand.kt │ └── DiscoverCommand.kt │ ├── devices │ ├── DeleteDeviceCommand.kt │ ├── DeviceOutput.kt │ ├── GetDeviceCommand.kt │ ├── IdentifyDeviceCommand.kt │ ├── ListDevicesCommand.kt │ └── UpdateDeviceCommand.kt │ ├── events │ └── EventsCommand.kt │ ├── groupedlights │ ├── GetGroupedLightCommand.kt │ ├── GroupedLightsOutput.kt │ ├── ListGroupedLightsCommand.kt │ └── UpdateGroupedLightCommand.kt │ ├── lights │ ├── GetLightCommand.kt │ ├── LightOutput.kt │ ├── ListLightsCommand.kt │ └── UpdateLightCommand.kt │ ├── resources │ └── ListResourcesCommand.kt │ ├── rooms │ ├── CreateRoomCommand.kt │ ├── DeleteRoomCommand.kt │ ├── GetRoomCommand.kt │ ├── ListRoomsCommand.kt │ ├── RoomOutput.kt │ └── UpdateRoomCommand.kt │ ├── scenes │ ├── Assertions.kt │ ├── CreateSceneCommand.kt │ ├── DeleteSceneCommand.kt │ ├── GetSceneCommand.kt │ ├── ListScenesCommand.kt │ ├── SceneOutput.kt │ └── UpdateSceneCommand.kt │ └── zones │ ├── CreateZoneCommand.kt │ ├── DeleteRoomCommand.kt │ ├── GetZoneCommand.kt │ ├── ListZonesCommand.kt │ ├── UpdateRoomCommand.kt │ └── ZoneOutput.kt ├── core ├── api │ └── core.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── core │ │ └── Shade.kt │ └── jvmMain │ └── kotlin │ └── inkapplications │ └── shade │ └── core │ └── JvmExtensions.kt ├── devices ├── api │ └── devices.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── devices │ │ ├── DeviceControls.kt │ │ ├── ShadeDevices.kt │ │ ├── ShadeDevicesModule.kt │ │ ├── parameters │ │ ├── DeviceMetadataParameters.kt │ │ ├── IdentifyParameters.kt │ │ └── UpdateDeviceParameters.kt │ │ └── structures │ │ ├── Device.kt │ │ ├── HardwarePlatformType.kt │ │ ├── ModelId.kt │ │ ├── ProductArchetype.kt │ │ ├── ProductData.kt │ │ └── ProductMetadata.kt │ └── jvmTest │ └── kotlin │ └── inkapplications │ └── shade │ └── devices │ ├── DeviceSerializationTest.kt │ └── DeviceUpdateParametersSerializationTest.kt ├── discover ├── api │ └── discover.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── discover │ │ ├── BridgeDiscovery.kt │ │ ├── DiscoverModule.kt │ │ ├── KtorDiscovery.kt │ │ ├── PlatformModule.kt │ │ └── structures │ │ ├── Bridge.kt │ │ └── BridgeId.kt │ ├── iosMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── discover │ │ └── PlatformModule.kt │ ├── jsMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── discover │ │ └── PlatformModule.kt │ ├── jvmMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── discover │ │ └── PlatformModule.kt │ ├── nativeMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── discover │ │ └── PlatformModule.kt │ └── windowsMain │ └── kotlin │ └── inkapplications │ └── shade │ └── discover │ └── PlatformModule.kt ├── docs ├── CNAME ├── cli-overview.html ├── css │ ├── atom-one-dark.css │ ├── atom-one-light.css │ ├── dark-1.1.css │ ├── main-1.1.css │ ├── main-2.0.css │ └── reset.css ├── index.html ├── reference │ └── latest │ │ ├── auth │ │ ├── index.html │ │ ├── inkapplications.shade.auth.structures │ │ │ ├── -app-id │ │ │ │ ├── -app-id.html │ │ │ │ ├── app-name.html │ │ │ │ ├── index.html │ │ │ │ └── instance-name.html │ │ │ └── index.html │ │ ├── inkapplications.shade.auth │ │ │ ├── -auth-module │ │ │ │ ├── -auth-module.html │ │ │ │ ├── bridge-auth.html │ │ │ │ └── index.html │ │ │ ├── -bridge-auth │ │ │ │ ├── await-token.html │ │ │ │ └── index.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── core │ │ ├── index.html │ │ ├── inkapplications.shade.core │ │ │ ├── -shade │ │ │ │ ├── -shade.html │ │ │ │ ├── auth.html │ │ │ │ ├── configuration.html │ │ │ │ ├── devices.html │ │ │ │ ├── grouped-lights.html │ │ │ │ ├── index.html │ │ │ │ ├── lights.html │ │ │ │ ├── online-discovery.html │ │ │ │ ├── resources.html │ │ │ │ ├── rooms.html │ │ │ │ ├── scenes.html │ │ │ │ └── zones.html │ │ │ ├── events.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── devices │ │ ├── index.html │ │ ├── inkapplications.shade.devices.parameters │ │ │ ├── -device-metadata-parameters │ │ │ │ ├── -device-metadata-parameters.html │ │ │ │ ├── archetype.html │ │ │ │ ├── index.html │ │ │ │ └── name.html │ │ │ ├── -update-device-parameters │ │ │ │ ├── -update-device-parameters.html │ │ │ │ ├── index.html │ │ │ │ └── metadata.html │ │ │ └── index.html │ │ ├── inkapplications.shade.devices.structures │ │ │ ├── -device │ │ │ │ ├── -device.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── metadata.html │ │ │ │ ├── product-data.html │ │ │ │ ├── services.html │ │ │ │ └── v1-id.html │ │ │ ├── -hardware-platform-type │ │ │ │ ├── -hardware-platform-type.html │ │ │ │ ├── index.html │ │ │ │ ├── to-string.html │ │ │ │ └── value.html │ │ │ ├── -model-id │ │ │ │ ├── -model-id.html │ │ │ │ ├── index.html │ │ │ │ ├── to-string.html │ │ │ │ └── value.html │ │ │ ├── -product-archetype │ │ │ │ ├── -companion │ │ │ │ │ ├── -bollard.html │ │ │ │ │ ├── -bridge-v2.html │ │ │ │ │ ├── -candle-bulb.html │ │ │ │ │ ├── -ceiling-horizontal.html │ │ │ │ │ ├── -ceiling-round.html │ │ │ │ │ ├── -ceiling-square.html │ │ │ │ │ ├── -ceiling-tube.html │ │ │ │ │ ├── -christmas-tree.html │ │ │ │ │ ├── -classic-bulb.html │ │ │ │ │ ├── -double-spot.html │ │ │ │ │ ├── -edison-bulb.html │ │ │ │ │ ├── -ellipse-bulb.html │ │ │ │ │ ├── -flexible-lamp.html │ │ │ │ │ ├── -flood-bulb.html │ │ │ │ │ ├── -floor-lantern.html │ │ │ │ │ ├── -floor-shade.html │ │ │ │ │ ├── -ground-spot.html │ │ │ │ │ ├── -hue-bloom.html │ │ │ │ │ ├── -hue-centris.html │ │ │ │ │ ├── -hue-go.html │ │ │ │ │ ├── -hue-iris.html │ │ │ │ │ ├── -hue-lightstrip-pc.html │ │ │ │ │ ├── -hue-lightstrip-tv.html │ │ │ │ │ ├── -hue-lightstrip.html │ │ │ │ │ ├── -hue-play.html │ │ │ │ │ ├── -hue-signe.html │ │ │ │ │ ├── -hue-tube.html │ │ │ │ │ ├── -large-globe-bulb.html │ │ │ │ │ ├── -luster-bulb.html │ │ │ │ │ ├── -pendant-long.html │ │ │ │ │ ├── -pendant-round.html │ │ │ │ │ ├── -pendant-spot.html │ │ │ │ │ ├── -plug.html │ │ │ │ │ ├── -recessed-ceiling.html │ │ │ │ │ ├── -recessed-floor.html │ │ │ │ │ ├── -single-spot.html │ │ │ │ │ ├── -small-globe-bulb.html │ │ │ │ │ ├── -spot-bulb.html │ │ │ │ │ ├── -string-light.html │ │ │ │ │ ├── -sultan-bulb.html │ │ │ │ │ ├── -table-shade.html │ │ │ │ │ ├── -table-wash.html │ │ │ │ │ ├── -triangle-bulb.html │ │ │ │ │ ├── -unknown-archetype.html │ │ │ │ │ ├── -vintage-bulb.html │ │ │ │ │ ├── -vintage-candle-bulb.html │ │ │ │ │ ├── -wall-lantern.html │ │ │ │ │ ├── -wall-shade.html │ │ │ │ │ ├── -wall-spot.html │ │ │ │ │ ├── -wall-washer.html │ │ │ │ │ └── index.html │ │ │ │ ├── -product-archetype.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -product-data │ │ │ │ ├── -product-data.html │ │ │ │ ├── certified.html │ │ │ │ ├── hardware-platform-type.html │ │ │ │ ├── index.html │ │ │ │ ├── manufacturer-name.html │ │ │ │ ├── model-id.html │ │ │ │ ├── product-archetype.html │ │ │ │ ├── product-name.html │ │ │ │ └── software-version.html │ │ │ ├── -product-metadata │ │ │ │ ├── -product-metadata.html │ │ │ │ ├── archetype.html │ │ │ │ ├── index.html │ │ │ │ └── name.html │ │ │ └── index.html │ │ ├── inkapplications.shade.devices │ │ │ ├── -device-controls │ │ │ │ ├── delete-device.html │ │ │ │ ├── get-device.html │ │ │ │ ├── identify-device.html │ │ │ │ ├── index.html │ │ │ │ ├── list-devices.html │ │ │ │ └── update-device.html │ │ │ ├── -shade-devices-module │ │ │ │ ├── -shade-devices-module.html │ │ │ │ ├── devices.html │ │ │ │ └── index.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── discover │ │ ├── index.html │ │ ├── inkapplications.shade.discover.structures │ │ │ ├── -bridge-id │ │ │ │ ├── -bridge-id.html │ │ │ │ ├── index.html │ │ │ │ ├── to-string.html │ │ │ │ └── value.html │ │ │ ├── -bridge │ │ │ │ ├── -bridge.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── local-ip.html │ │ │ │ └── port.html │ │ │ └── index.html │ │ ├── inkapplications.shade.discover │ │ │ ├── -bridge-discovery │ │ │ │ ├── get-devices.html │ │ │ │ └── index.html │ │ │ ├── -discover-module │ │ │ │ ├── -discover-module.html │ │ │ │ ├── index.html │ │ │ │ └── online-discovery.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── events │ │ ├── index.html │ │ ├── inkapplications.shade.events │ │ │ ├── -event-serializer-container │ │ │ │ ├── index.html │ │ │ │ └── set-deserializer.html │ │ │ ├── -events-module │ │ │ │ ├── -events-module.html │ │ │ │ ├── event-serializer-container.html │ │ │ │ └── index.html │ │ │ ├── -events │ │ │ │ ├── bridge-events.html │ │ │ │ └── index.html │ │ │ └── index.html │ │ ├── navigation.html │ │ └── shade.events │ │ │ ├── events.html │ │ │ └── index.html │ │ ├── grouped-lights │ │ ├── index.html │ │ ├── inkapplications.shade.groupedlights.events │ │ │ ├── -grouped-light-event │ │ │ │ ├── -grouped-light-event.html │ │ │ │ ├── alert-info.html │ │ │ │ ├── dimming-info.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── owner.html │ │ │ │ └── power-info.html │ │ │ └── index.html │ │ ├── inkapplications.shade.groupedlights.parameters │ │ │ ├── -grouped-light-update-parameters │ │ │ │ ├── -grouped-light-update-parameters.html │ │ │ │ ├── alert.html │ │ │ │ ├── color-temperature-delta.html │ │ │ │ ├── color-temperature.html │ │ │ │ ├── color.html │ │ │ │ ├── dimming-delta.html │ │ │ │ ├── dimming.html │ │ │ │ ├── dynamics.html │ │ │ │ ├── index.html │ │ │ │ └── power.html │ │ │ └── index.html │ │ ├── inkapplications.shade.groupedlights.structures │ │ │ ├── -group-dimming-info │ │ │ │ ├── -group-dimming-info.html │ │ │ │ ├── brightness.html │ │ │ │ └── index.html │ │ │ ├── -grouped-light │ │ │ │ ├── -grouped-light.html │ │ │ │ ├── alert-info.html │ │ │ │ ├── dimming-info.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── owner.html │ │ │ │ └── power-info.html │ │ │ └── index.html │ │ ├── inkapplications.shade.groupedlights │ │ │ ├── -grouped-light-controls │ │ │ │ ├── get-group.html │ │ │ │ ├── index.html │ │ │ │ ├── list-groups.html │ │ │ │ └── update-group.html │ │ │ ├── -shade-grouped-lights-module │ │ │ │ ├── -shade-grouped-lights-module.html │ │ │ │ ├── grouped-lights.html │ │ │ │ └── index.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── images │ │ ├── anchor-copy-button.svg │ │ ├── arrow_down.svg │ │ ├── burger.svg │ │ ├── copy-icon.svg │ │ ├── copy-successful-icon.svg │ │ ├── footer-go-to-link.svg │ │ ├── go-to-top-icon.svg │ │ ├── homepage.svg │ │ ├── logo-icon.svg │ │ ├── nav-icons │ │ │ ├── abstract-class-kotlin.svg │ │ │ ├── abstract-class.svg │ │ │ ├── annotation-kotlin.svg │ │ │ ├── annotation.svg │ │ │ ├── class-kotlin.svg │ │ │ ├── class.svg │ │ │ ├── enum-kotlin.svg │ │ │ ├── enum.svg │ │ │ ├── exception-class.svg │ │ │ ├── field-value.svg │ │ │ ├── field-variable.svg │ │ │ ├── function.svg │ │ │ ├── interface-kotlin.svg │ │ │ ├── interface.svg │ │ │ ├── object.svg │ │ │ └── typealias-kotlin.svg │ │ └── theme-toggle.svg │ │ ├── index.html │ │ ├── internals │ │ ├── index.html │ │ ├── inkapplications.shade.internals │ │ │ ├── -cached-property │ │ │ │ ├── -cached-property.html │ │ │ │ ├── factory.html │ │ │ │ ├── get-value.html │ │ │ │ ├── index.html │ │ │ │ └── key-property.html │ │ │ ├── -dummy-configuration-container │ │ │ │ ├── auth-token.html │ │ │ │ ├── hostname.html │ │ │ │ ├── index.html │ │ │ │ ├── security-strategy.html │ │ │ │ ├── set-auth-token.html │ │ │ │ ├── set-hostname.html │ │ │ │ └── set-security-strategy.html │ │ │ ├── -hue-http-client │ │ │ │ ├── index.html │ │ │ │ ├── send-request.html │ │ │ │ └── send-v1-request.html │ │ │ ├── -hue-stub-client │ │ │ │ ├── index.html │ │ │ │ ├── send-request.html │ │ │ │ └── send-v1-request.html │ │ │ ├── -internals-module │ │ │ │ ├── -internals-module.html │ │ │ │ ├── configuration-container.html │ │ │ │ ├── hue-http-client.html │ │ │ │ ├── index.html │ │ │ │ ├── json.html │ │ │ │ └── platform-module.html │ │ │ ├── -platform-module │ │ │ │ ├── -platform-module.html │ │ │ │ ├── apply-platform-configuration.html │ │ │ │ ├── create-engine.html │ │ │ │ ├── index.html │ │ │ │ └── sse-client.html │ │ │ ├── -sse-client │ │ │ │ ├── index.html │ │ │ │ └── open-sse.html │ │ │ ├── delete-data.html │ │ │ ├── get-data.html │ │ │ ├── index.html │ │ │ ├── post-data.html │ │ │ └── put-data.html │ │ └── navigation.html │ │ ├── lights │ │ ├── index.html │ │ ├── inkapplications.shade.lights.events │ │ │ ├── -color-info-event │ │ │ │ ├── -color-info-event.html │ │ │ │ ├── color.html │ │ │ │ └── index.html │ │ │ ├── -color-temperature-info-event │ │ │ │ ├── -color-temperature-info-event.html │ │ │ │ ├── index.html │ │ │ │ ├── temperature.html │ │ │ │ └── valid.html │ │ │ ├── -dimming-info-event │ │ │ │ ├── -dimming-info-event.html │ │ │ │ ├── brightness.html │ │ │ │ └── index.html │ │ │ ├── -light-event │ │ │ │ ├── -light-event.html │ │ │ │ ├── color-info.html │ │ │ │ ├── color-temperature-info.html │ │ │ │ ├── dimming-info.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── owner.html │ │ │ │ └── power-info.html │ │ │ └── index.html │ │ ├── inkapplications.shade.lights.parameters │ │ │ ├── -alert-parameters │ │ │ │ ├── -alert-parameters.html │ │ │ │ ├── action.html │ │ │ │ └── index.html │ │ │ ├── -color-parameters │ │ │ │ ├── -color-parameters.html │ │ │ │ ├── color.html │ │ │ │ └── index.html │ │ │ ├── -color-temperature-delta-parameters │ │ │ │ ├── -color-temperature-delta-parameters.html │ │ │ │ ├── action.html │ │ │ │ ├── index.html │ │ │ │ └── temperature-delta.html │ │ │ ├── -color-temperature-parameters │ │ │ │ ├── -color-temperature-parameters.html │ │ │ │ ├── index.html │ │ │ │ └── temperature.html │ │ │ ├── -delta-action │ │ │ │ ├── -companion │ │ │ │ │ ├── -down.html │ │ │ │ │ ├── -stop.html │ │ │ │ │ ├── -up.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -delta-action.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -dimming-delta-parameters │ │ │ │ ├── -dimming-delta-parameters.html │ │ │ │ ├── action.html │ │ │ │ ├── brightness-delta.html │ │ │ │ └── index.html │ │ │ ├── -dimming-parameters │ │ │ │ ├── -dimming-parameters.html │ │ │ │ ├── brightness.html │ │ │ │ └── index.html │ │ │ ├── -dynamics-parameters │ │ │ │ ├── -dynamics-parameters.html │ │ │ │ ├── duration.html │ │ │ │ ├── index.html │ │ │ │ └── speed.html │ │ │ ├── -effects-parameters │ │ │ │ ├── -effects-parameters.html │ │ │ │ ├── effect.html │ │ │ │ └── index.html │ │ │ ├── -gradient-parameters │ │ │ │ ├── -gradient-parameters.html │ │ │ │ ├── index.html │ │ │ │ └── points.html │ │ │ ├── -light-update-parameters │ │ │ │ ├── -light-update-parameters.html │ │ │ │ ├── alert.html │ │ │ │ ├── color-temperature-delta.html │ │ │ │ ├── color-temperature.html │ │ │ │ ├── color.html │ │ │ │ ├── dimming-delta.html │ │ │ │ ├── dimming.html │ │ │ │ ├── dynamics.html │ │ │ │ ├── effects.html │ │ │ │ ├── gradient.html │ │ │ │ ├── index.html │ │ │ │ ├── power.html │ │ │ │ └── timed-effects.html │ │ │ ├── -timed-effects-parameters │ │ │ │ ├── -timed-effects-parameters.html │ │ │ │ ├── duration.html │ │ │ │ ├── effect.html │ │ │ │ └── index.html │ │ │ └── index.html │ │ ├── inkapplications.shade.lights.structures │ │ │ ├── -alert-effect-type │ │ │ │ ├── -alert-effect-type.html │ │ │ │ ├── -companion │ │ │ │ │ ├── -breathe.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -alert-info │ │ │ │ ├── -alert-info.html │ │ │ │ ├── action-values.html │ │ │ │ └── index.html │ │ │ ├── -color-info │ │ │ │ ├── -color-info.html │ │ │ │ ├── color.html │ │ │ │ ├── gamut-type.html │ │ │ │ ├── gamut.html │ │ │ │ └── index.html │ │ │ ├── -color-palette │ │ │ │ ├── -color-palette.html │ │ │ │ ├── color.html │ │ │ │ ├── dimming.html │ │ │ │ └── index.html │ │ │ ├── -color-temperature-info │ │ │ │ ├── -color-temperature-info.html │ │ │ │ ├── index.html │ │ │ │ ├── range.html │ │ │ │ ├── temperature.html │ │ │ │ └── valid.html │ │ │ ├── -color-temperature-palette │ │ │ │ ├── -color-temperature-palette.html │ │ │ │ ├── color-temperature.html │ │ │ │ ├── dimming.html │ │ │ │ └── index.html │ │ │ ├── -color-temperature-range │ │ │ │ ├── -color-temperature-range.html │ │ │ │ ├── contains.html │ │ │ │ ├── coolest.html │ │ │ │ ├── index.html │ │ │ │ ├── kelvin-range.html │ │ │ │ ├── mired-range.html │ │ │ │ ├── to-string.html │ │ │ │ └── warmest.html │ │ │ ├── -color-temperature-value │ │ │ │ ├── -color-temperature-value.html │ │ │ │ ├── index.html │ │ │ │ └── temperature.html │ │ │ ├── -color-value │ │ │ │ ├── -color-value.html │ │ │ │ ├── color.html │ │ │ │ └── index.html │ │ │ ├── -dimming-info │ │ │ │ ├── -dimming-info.html │ │ │ │ ├── brightness.html │ │ │ │ ├── index.html │ │ │ │ └── minimum.html │ │ │ ├── -dimming-value │ │ │ │ ├── -dimming-value.html │ │ │ │ ├── brightness.html │ │ │ │ └── index.html │ │ │ ├── -dynamics-status │ │ │ │ ├── -companion │ │ │ │ │ ├── -dynamic-palette.html │ │ │ │ │ ├── -none.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -dynamics-status.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -gamut-type │ │ │ │ ├── -companion │ │ │ │ │ ├── -a.html │ │ │ │ │ ├── -b.html │ │ │ │ │ ├── -c.html │ │ │ │ │ ├── -other.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -gamut-type.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -gamut │ │ │ │ ├── -gamut.html │ │ │ │ ├── blue.html │ │ │ │ ├── green.html │ │ │ │ ├── index.html │ │ │ │ ├── red.html │ │ │ │ └── to-color-space.html │ │ │ ├── -gradient-color-info │ │ │ │ ├── -gradient-color-info.html │ │ │ │ ├── color.html │ │ │ │ └── index.html │ │ │ ├── -gradient-mode │ │ │ │ ├── -companion │ │ │ │ │ ├── -interpolated-palette-mirrored.html │ │ │ │ │ ├── -interpolated-palette.html │ │ │ │ │ ├── -random-pixelated.html │ │ │ │ │ └── index.html │ │ │ │ ├── -gradient-mode.html │ │ │ │ ├── index.html │ │ │ │ └── key.html │ │ │ ├── -gradient-point │ │ │ │ ├── -gradient-point.html │ │ │ │ ├── color-info.html │ │ │ │ ├── color-value.html │ │ │ │ └── index.html │ │ │ ├── -gradient-value │ │ │ │ ├── -gradient-value.html │ │ │ │ ├── index.html │ │ │ │ ├── mode.html │ │ │ │ └── points.html │ │ │ ├── -gradient │ │ │ │ ├── -gradient.html │ │ │ │ ├── index.html │ │ │ │ ├── points-capable.html │ │ │ │ └── points.html │ │ │ ├── -light-dynamics │ │ │ │ ├── -light-dynamics.html │ │ │ │ ├── index.html │ │ │ │ ├── speed-valid.html │ │ │ │ ├── speed.html │ │ │ │ ├── status-values.html │ │ │ │ └── status.html │ │ │ ├── -light-effect │ │ │ │ ├── -companion │ │ │ │ │ ├── -candle.html │ │ │ │ │ ├── -fire.html │ │ │ │ │ ├── -none.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -light-effect.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -light-mode │ │ │ │ ├── -companion │ │ │ │ │ ├── -normal.html │ │ │ │ │ ├── -streaming.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -light-mode.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -light-powerup │ │ │ │ ├── -custom │ │ │ │ │ ├── -custom.html │ │ │ │ │ ├── color-state.html │ │ │ │ │ ├── configured.html │ │ │ │ │ ├── dimming-state.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── power-state.html │ │ │ │ ├── -last-on-state │ │ │ │ │ ├── -last-on-state.html │ │ │ │ │ ├── configured.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── power-state.html │ │ │ │ ├── -powerfail │ │ │ │ │ ├── -powerfail.html │ │ │ │ │ ├── configured.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── power-state.html │ │ │ │ ├── -safety │ │ │ │ │ ├── -safety.html │ │ │ │ │ ├── configured.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── power-state.html │ │ │ │ ├── configured.html │ │ │ │ ├── index.html │ │ │ │ └── power-state.html │ │ │ ├── -light-signal-status │ │ │ │ ├── -light-signal-status.html │ │ │ │ ├── estimated-end.html │ │ │ │ ├── index.html │ │ │ │ └── signal.html │ │ │ ├── -light-signal │ │ │ │ ├── -companion │ │ │ │ │ ├── -no-signal.html │ │ │ │ │ ├── -on-off.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -light-signal.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -light-signaling │ │ │ │ ├── -light-signaling.html │ │ │ │ ├── index.html │ │ │ │ └── status.html │ │ │ ├── -light │ │ │ │ ├── -light.html │ │ │ │ ├── alert-info.html │ │ │ │ ├── color-info.html │ │ │ │ ├── color-temperature-info.html │ │ │ │ ├── dimming-info.html │ │ │ │ ├── dynamics.html │ │ │ │ ├── effects.html │ │ │ │ ├── gradient.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── mode.html │ │ │ │ ├── owner.html │ │ │ │ ├── power-info.html │ │ │ │ ├── powerup.html │ │ │ │ ├── signaling.html │ │ │ │ ├── timed-effects.html │ │ │ │ └── v1-id.html │ │ │ ├── -lighting-effect-info │ │ │ │ ├── -lighting-effect-info.html │ │ │ │ ├── index.html │ │ │ │ ├── status.html │ │ │ │ └── values.html │ │ │ ├── -powerup-color-state │ │ │ │ ├── -color-temperature │ │ │ │ │ ├── -color-temperature.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── temperature-value.html │ │ │ │ ├── -color │ │ │ │ │ ├── -color.html │ │ │ │ │ ├── color-value.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── temperature-value.html │ │ │ │ ├── -previous │ │ │ │ │ ├── -previous.html │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -powerup-dimming-state │ │ │ │ ├── -previous │ │ │ │ │ ├── -previous.html │ │ │ │ │ └── index.html │ │ │ │ ├── -static-brightness │ │ │ │ │ ├── -static-brightness.html │ │ │ │ │ ├── dimming-value.html │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -powerup-power-state │ │ │ │ ├── -previous │ │ │ │ │ ├── -previous.html │ │ │ │ │ └── index.html │ │ │ │ ├── -static-power │ │ │ │ │ ├── -static-power.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── power-value.html │ │ │ │ ├── -toggle │ │ │ │ │ ├── -toggle.html │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -standard-temperatures │ │ │ │ ├── -a.html │ │ │ │ ├── -b.html │ │ │ │ ├── -c.html │ │ │ │ ├── -d50.html │ │ │ │ ├── -d55.html │ │ │ │ ├── -d65.html │ │ │ │ ├── -d75.html │ │ │ │ ├── -e.html │ │ │ │ ├── -f1.html │ │ │ │ ├── -f10.html │ │ │ │ ├── -f11.html │ │ │ │ ├── -f12.html │ │ │ │ ├── -f2.html │ │ │ │ ├── -f3.html │ │ │ │ ├── -f4.html │ │ │ │ ├── -f5.html │ │ │ │ ├── -f6.html │ │ │ │ ├── -f7.html │ │ │ │ ├── -f8.html │ │ │ │ ├── -f9.html │ │ │ │ └── index.html │ │ │ ├── -timed-light-effect │ │ │ │ ├── -companion │ │ │ │ │ ├── -none.html │ │ │ │ │ ├── -sunrise.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -timed-light-effect.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -timed-lighting-effect-info │ │ │ │ ├── -timed-lighting-effect-info.html │ │ │ │ ├── index.html │ │ │ │ ├── status.html │ │ │ │ └── values.html │ │ │ └── index.html │ │ ├── inkapplications.shade.lights │ │ │ ├── -light-controls │ │ │ │ ├── get-light.html │ │ │ │ ├── index.html │ │ │ │ ├── list-lights.html │ │ │ │ └── update-light.html │ │ │ ├── -shade-lights-module │ │ │ │ ├── -shade-lights-module.html │ │ │ │ ├── index.html │ │ │ │ └── lights.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── navigation.html │ │ ├── package-list │ │ ├── resources │ │ ├── index.html │ │ ├── inkapplications.shade.resources.structures │ │ │ ├── -resource │ │ │ │ ├── -resource.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── type.html │ │ │ │ └── v1-id.html │ │ │ └── index.html │ │ ├── inkapplications.shade.resources │ │ │ ├── -resource-controls │ │ │ │ ├── index.html │ │ │ │ └── list-resources.html │ │ │ ├── -shade-resources-module │ │ │ │ ├── -shade-resources-module.html │ │ │ │ ├── index.html │ │ │ │ └── resources.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── rooms │ │ ├── index.html │ │ ├── inkapplications.shade.rooms.parameters │ │ │ ├── -room-create-parameters │ │ │ │ ├── -room-create-parameters.html │ │ │ │ ├── children.html │ │ │ │ ├── index.html │ │ │ │ └── metadata.html │ │ │ ├── -room-update-parameters │ │ │ │ ├── -room-update-parameters.html │ │ │ │ ├── children.html │ │ │ │ ├── index.html │ │ │ │ └── metadata.html │ │ │ └── index.html │ │ ├── inkapplications.shade.rooms.structures │ │ │ ├── -room │ │ │ │ ├── -room.html │ │ │ │ ├── children.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── metadata.html │ │ │ │ └── services.html │ │ │ └── index.html │ │ ├── inkapplications.shade.rooms │ │ │ ├── -room-controls │ │ │ │ ├── create-room.html │ │ │ │ ├── delete-room.html │ │ │ │ ├── get-room.html │ │ │ │ ├── index.html │ │ │ │ ├── list-rooms.html │ │ │ │ └── update-room.html │ │ │ ├── -shade-rooms-module │ │ │ │ ├── -shade-rooms-module.html │ │ │ │ ├── index.html │ │ │ │ └── rooms.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── scenes │ │ ├── index.html │ │ ├── inkapplications.shade.scenes.parameters │ │ │ ├── -scene-create-parameters │ │ │ │ ├── -scene-create-parameters.html │ │ │ │ ├── actions.html │ │ │ │ ├── auto-dynamic.html │ │ │ │ ├── group.html │ │ │ │ ├── index.html │ │ │ │ ├── metadata.html │ │ │ │ ├── palette.html │ │ │ │ └── speed.html │ │ │ ├── -scene-update-parameters │ │ │ │ ├── -scene-update-parameters.html │ │ │ │ ├── actions.html │ │ │ │ ├── auto-dynamic.html │ │ │ │ ├── index.html │ │ │ │ ├── metadata.html │ │ │ │ ├── palette.html │ │ │ │ ├── recall.html │ │ │ │ └── speed.html │ │ │ └── index.html │ │ ├── inkapplications.shade.scenes.structures │ │ │ ├── -scene-action-reference │ │ │ │ ├── -scene-action-reference.html │ │ │ │ ├── action.html │ │ │ │ ├── index.html │ │ │ │ └── target.html │ │ │ ├── -scene-action │ │ │ │ ├── -scene-action.html │ │ │ │ ├── color-temperature-value.html │ │ │ │ ├── color-value.html │ │ │ │ ├── dimming-value.html │ │ │ │ ├── dynamics-duration.html │ │ │ │ ├── effect.html │ │ │ │ ├── gradient.html │ │ │ │ ├── index.html │ │ │ │ └── power-value.html │ │ │ ├── -scene-metadata │ │ │ │ ├── -scene-metadata.html │ │ │ │ ├── image.html │ │ │ │ ├── index.html │ │ │ │ └── name.html │ │ │ ├── -scene-palette │ │ │ │ ├── -scene-palette.html │ │ │ │ ├── color-temperature.html │ │ │ │ ├── color.html │ │ │ │ ├── dimming.html │ │ │ │ └── index.html │ │ │ ├── -scene-recall-action │ │ │ │ ├── -companion │ │ │ │ │ ├── -active.html │ │ │ │ │ ├── -dynamic-palette.html │ │ │ │ │ ├── -static.html │ │ │ │ │ └── index.html │ │ │ │ ├── -scene-recall-action.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -scene-recall-status │ │ │ │ ├── -companion │ │ │ │ │ ├── -active.html │ │ │ │ │ ├── -dynamic-palette.html │ │ │ │ │ └── index.html │ │ │ │ ├── -scene-recall-status.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -scene-recall │ │ │ │ ├── -scene-recall.html │ │ │ │ ├── action.html │ │ │ │ ├── dimming.html │ │ │ │ ├── duration.html │ │ │ │ ├── index.html │ │ │ │ └── status.html │ │ │ ├── -scene │ │ │ │ ├── -scene.html │ │ │ │ ├── actions.html │ │ │ │ ├── auto-dynamic.html │ │ │ │ ├── group.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── metadata.html │ │ │ │ ├── palette.html │ │ │ │ ├── speed.html │ │ │ │ └── v1-id.html │ │ │ └── index.html │ │ ├── inkapplications.shade.scenes │ │ │ ├── -scene-controls │ │ │ │ ├── create-scene.html │ │ │ │ ├── delete-scene.html │ │ │ │ ├── get-scene.html │ │ │ │ ├── index.html │ │ │ │ ├── list-scenes.html │ │ │ │ └── update-scene.html │ │ │ ├── -shade-scenes-module │ │ │ │ ├── -shade-scenes-module.html │ │ │ │ ├── index.html │ │ │ │ └── scenes.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── scripts │ │ ├── clipboard.js │ │ ├── main.js │ │ ├── navigation-loader.js │ │ ├── pages.json │ │ ├── platform-content-handler.js │ │ ├── prism.js │ │ ├── sourceset_dependencies.js │ │ └── symbol-parameters-wrapper_deferred.js │ │ ├── serialization │ │ ├── index.html │ │ ├── inkapplications.shade.serialization │ │ │ ├── -delegate-serializer │ │ │ │ ├── -delegate-serializer.html │ │ │ │ ├── descriptor.html │ │ │ │ ├── deserialize.html │ │ │ │ ├── index.html │ │ │ │ └── serialize.html │ │ │ ├── -explicit-mired-serializer │ │ │ │ └── index.html │ │ │ ├── -fractional-percentage-serializer │ │ │ │ └── index.html │ │ │ ├── -hue-error │ │ │ │ ├── -hue-error.html │ │ │ │ ├── description.html │ │ │ │ └── index.html │ │ │ ├── -hue-response │ │ │ │ ├── -error │ │ │ │ │ ├── -error.html │ │ │ │ │ ├── errors.html │ │ │ │ │ └── index.html │ │ │ │ ├── -success │ │ │ │ │ ├── -success.html │ │ │ │ │ ├── data.html │ │ │ │ │ ├── errors.html │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -millisecond-duration-serializer │ │ │ │ └── index.html │ │ │ ├── -mired-serializer │ │ │ │ └── index.html │ │ │ ├── -v1-hue-error │ │ │ │ ├── -error-codes │ │ │ │ │ ├── -b-a-c-k-u-p_-i-n-v-a-l-i-d_-s-t-a-t-e.html │ │ │ │ │ ├── -b-a-c-k-u-p_-s-o-u-r-c-e_-f-a-c-t-o-r-y_-n-e-w.html │ │ │ │ │ ├── -b-a-c-k-u-p_-s-o-u-r-c-e_-m-o-d-e-l_-i-n-v-a-l-i-d.html │ │ │ │ │ ├── -d-h-c-p_-d-i-s-a-b-l-e-d.html │ │ │ │ │ ├── -g-r-o-u-p_-l-i-g-h-t_-a-l-r-e-a-d-y_-i-n_-r-o-o-m.html │ │ │ │ │ ├── -g-r-o-u-p_-l-i-s-t_-f-u-l-l.html │ │ │ │ │ ├── -g-r-o-u-p_-u-p-d-a-t-e_-n-o-t_-a-l-l-o-w-e-d.html │ │ │ │ │ ├── -l-i-g-h-t_-p-a-r-a-m-e-t-e-r_-n-o-t_-m-o-d-i-f-i-a-b-l-e.html │ │ │ │ │ ├── -l-i-g-h-t_-r-a-d-i-o_-f-u-l-l.html │ │ │ │ │ ├── -l-i-n-k_-r-e-q-u-i-r-e-d.html │ │ │ │ │ ├── -r-u-l-e_-a-c-t-i-o-n_-e-r-r-o-r.html │ │ │ │ │ ├── -r-u-l-e_-c-o-n-d-i-t-i-o-n_-n-o-t_-a-l-l-o-w-e-d.html │ │ │ │ │ ├── -r-u-l-e_-l-i-s-t_-f-u-l-l.html │ │ │ │ │ ├── -r-u-l-e_-n-o-t_-a-c-t-i-v-a-t-e-d.html │ │ │ │ │ ├── -s-c-e-n-e_-g-r-o-u-p_-e-m-p-t-y.html │ │ │ │ │ ├── -s-c-e-n-e_-l-i-s-t_-f-u-l-l.html │ │ │ │ │ ├── -s-c-e-n-e_-l-o-c-k-e-d.html │ │ │ │ │ ├── -s-c-h-e-d-u-l-e_-c-o-m-m-a-n-d_-e-r-r-o-r.html │ │ │ │ │ ├── -s-c-h-e-d-u-l-e_-f-u-l-l.html │ │ │ │ │ ├── -s-c-h-e-d-u-l-e_-t-a-g_-i-n-v-a-l-i-d.html │ │ │ │ │ ├── -s-c-h-e-d-u-l-e_-t-i-m-e-z-o-n-e_-i-n-v-a-l-i-d.html │ │ │ │ │ ├── -s-c-h-e-d-u-l-e_-t-i-m-e_-a-m-b-i-g-u-o-u-s.html │ │ │ │ │ ├── -s-c-h-e-d-u-l-e_-t-i-m-e_-p-a-s-t.html │ │ │ │ │ ├── -s-e-n-s-o-r_-l-i-s-t_-f-u-l-l.html │ │ │ │ │ ├── -s-e-n-s-o-r_-n-o-t_-a-l-l-o-w-e-d.html │ │ │ │ │ ├── -s-e-n-s-o-r_-r-a-d-i-o_-f-u-l-l.html │ │ │ │ │ ├── -u-p-d-a-t-e_-i-n-v-a-l-i-d_-s-t-a-t-e.html │ │ │ │ │ └── index.html │ │ │ │ ├── -v1-hue-error.html │ │ │ │ ├── address.html │ │ │ │ ├── description.html │ │ │ │ ├── index.html │ │ │ │ └── type.html │ │ │ ├── -v1-hue-response │ │ │ │ ├── -error │ │ │ │ │ ├── -error.html │ │ │ │ │ ├── error.html │ │ │ │ │ └── index.html │ │ │ │ ├── -success │ │ │ │ │ ├── -success.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── success.html │ │ │ │ └── index.html │ │ │ ├── -whole-percentage-serializer │ │ │ │ └── index.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── structures │ │ ├── index.html │ │ ├── inkapplications.shade.structures.parameters │ │ │ ├── -power-parameters │ │ │ │ ├── -power-parameters.html │ │ │ │ ├── index.html │ │ │ │ └── on.html │ │ │ └── index.html │ │ ├── inkapplications.shade.structures │ │ │ ├── -api-error │ │ │ │ ├── -api-error.html │ │ │ │ └── index.html │ │ │ ├── -api-status-error │ │ │ │ ├── -api-status-error.html │ │ │ │ ├── code.html │ │ │ │ └── index.html │ │ │ ├── -auth-token │ │ │ │ ├── -auth-token.html │ │ │ │ ├── application-key.html │ │ │ │ ├── client-key.html │ │ │ │ ├── index.html │ │ │ │ └── to-string.html │ │ │ ├── -authorization-timeout-exception │ │ │ │ └── index.html │ │ │ ├── -hostname-not-set-exception │ │ │ │ └── index.html │ │ │ ├── -hue-configuration-container │ │ │ │ ├── auth-token.html │ │ │ │ ├── hostname.html │ │ │ │ ├── index.html │ │ │ │ ├── security-strategy.html │ │ │ │ ├── set-auth-token.html │ │ │ │ ├── set-hostname.html │ │ │ │ └── set-security-strategy.html │ │ │ ├── -in-memory-configuration-container │ │ │ │ ├── -in-memory-configuration-container.html │ │ │ │ ├── auth-token.html │ │ │ │ ├── hostname.html │ │ │ │ ├── index.html │ │ │ │ ├── security-strategy.html │ │ │ │ ├── set-auth-token.html │ │ │ │ ├── set-hostname.html │ │ │ │ └── set-security-strategy.html │ │ │ ├── -internal-error-exception │ │ │ │ └── index.html │ │ │ ├── -invalid-configuration-exception │ │ │ │ ├── -invalid-configuration-exception.html │ │ │ │ └── index.html │ │ │ ├── -network-exception │ │ │ │ ├── -network-exception.html │ │ │ │ └── index.html │ │ │ ├── -power-info │ │ │ │ ├── -power-info.html │ │ │ │ ├── index.html │ │ │ │ └── on.html │ │ │ ├── -power-value │ │ │ │ ├── -power-value.html │ │ │ │ ├── index.html │ │ │ │ └── on.html │ │ │ ├── -properties-file-configuration │ │ │ │ ├── -properties-file-configuration.html │ │ │ │ ├── auth-token.html │ │ │ │ ├── hostname.html │ │ │ │ ├── index.html │ │ │ │ ├── security-strategy.html │ │ │ │ ├── set-auth-token.html │ │ │ │ ├── set-hostname.html │ │ │ │ └── set-security-strategy.html │ │ │ ├── -resource-id │ │ │ │ ├── -resource-id.html │ │ │ │ ├── index.html │ │ │ │ ├── to-string.html │ │ │ │ └── value.html │ │ │ ├── -resource-reference │ │ │ │ ├── -resource-reference.html │ │ │ │ ├── id.html │ │ │ │ ├── index.html │ │ │ │ ├── to-string.html │ │ │ │ └── type.html │ │ │ ├── -resource-type │ │ │ │ ├── -companion │ │ │ │ │ ├── -auth-v1.html │ │ │ │ │ ├── -behavior-instance.html │ │ │ │ │ ├── -behavior-script.html │ │ │ │ │ ├── -bridge-home.html │ │ │ │ │ ├── -bridge.html │ │ │ │ │ ├── -button.html │ │ │ │ │ ├── -device-power.html │ │ │ │ │ ├── -device.html │ │ │ │ │ ├── -entertainment-configuration.html │ │ │ │ │ ├── -entertainment.html │ │ │ │ │ ├── -geofence-client.html │ │ │ │ │ ├── -geofence.html │ │ │ │ │ ├── -geolocation.html │ │ │ │ │ ├── -grouped-light.html │ │ │ │ │ ├── -homekit.html │ │ │ │ │ ├── -light-level.html │ │ │ │ │ ├── -light.html │ │ │ │ │ ├── -motion.html │ │ │ │ │ ├── -public-image.html │ │ │ │ │ ├── -room.html │ │ │ │ │ ├── -scene.html │ │ │ │ │ ├── -temperature.html │ │ │ │ │ ├── -zgp-connectivity.html │ │ │ │ │ ├── -zigbee-bridge-connectivity.html │ │ │ │ │ ├── -zigbee-connectivity.html │ │ │ │ │ ├── -zone.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -resource-type.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -security-strategy │ │ │ │ ├── -custom-ca │ │ │ │ │ ├── -custom-ca.html │ │ │ │ │ ├── certificate-pem.html │ │ │ │ │ ├── hostname.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── ip.html │ │ │ │ ├── -hue-ca │ │ │ │ │ ├── -hue-ca.html │ │ │ │ │ └── index.html │ │ │ │ ├── -insecure │ │ │ │ │ ├── -insecure.html │ │ │ │ │ ├── hostname.html │ │ │ │ │ └── index.html │ │ │ │ ├── -platform-trust │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -segment-archetype │ │ │ │ ├── -companion │ │ │ │ │ ├── -attic.html │ │ │ │ │ ├── -balcony.html │ │ │ │ │ ├── -barbecue.html │ │ │ │ │ ├── -bathroom.html │ │ │ │ │ ├── -bedroom.html │ │ │ │ │ ├── -carport.html │ │ │ │ │ ├── -closet.html │ │ │ │ │ ├── -computer.html │ │ │ │ │ ├── -dining.html │ │ │ │ │ ├── -downstairs.html │ │ │ │ │ ├── -driveway.html │ │ │ │ │ ├── -front-door.html │ │ │ │ │ ├── -garage.html │ │ │ │ │ ├── -garden.html │ │ │ │ │ ├── -guest-room.html │ │ │ │ │ ├── -gym.html │ │ │ │ │ ├── -hallway.html │ │ │ │ │ ├── -home.html │ │ │ │ │ ├── -kids-bedroom.html │ │ │ │ │ ├── -kitchen.html │ │ │ │ │ ├── -laundry-room.html │ │ │ │ │ ├── -living-room.html │ │ │ │ │ ├── -lounge.html │ │ │ │ │ ├── -man-cave.html │ │ │ │ │ ├── -music.html │ │ │ │ │ ├── -nursery.html │ │ │ │ │ ├── -office.html │ │ │ │ │ ├── -other.html │ │ │ │ │ ├── -pool.html │ │ │ │ │ ├── -porch.html │ │ │ │ │ ├── -reading.html │ │ │ │ │ ├── -recreation.html │ │ │ │ │ ├── -staircase.html │ │ │ │ │ ├── -storage.html │ │ │ │ │ ├── -studio.html │ │ │ │ │ ├── -terrace.html │ │ │ │ │ ├── -toilet.html │ │ │ │ │ ├── -top-floor.html │ │ │ │ │ ├── -tv.html │ │ │ │ │ ├── -upstairs.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── value-of.html │ │ │ │ │ └── values.html │ │ │ │ ├── -segment-archetype.html │ │ │ │ ├── index.html │ │ │ │ ├── key.html │ │ │ │ └── to-string.html │ │ │ ├── -segment-metadata-update │ │ │ │ ├── -segment-metadata-update.html │ │ │ │ ├── archetype.html │ │ │ │ ├── index.html │ │ │ │ └── name.html │ │ │ ├── -segment-metadata │ │ │ │ ├── -segment-metadata.html │ │ │ │ ├── archetype.html │ │ │ │ ├── index.html │ │ │ │ └── name.html │ │ │ ├── -serialization-error │ │ │ │ ├── -serialization-error.html │ │ │ │ └── index.html │ │ │ ├── -shade-exception │ │ │ │ └── index.html │ │ │ ├── -unauthorized-exception │ │ │ │ ├── -unauthorized-exception.html │ │ │ │ └── index.html │ │ │ ├── -undocumented-api │ │ │ │ └── index.html │ │ │ ├── -unexpected-state-exception │ │ │ │ ├── -unexpected-state-exception.html │ │ │ │ └── index.html │ │ │ ├── -unknown-event │ │ │ │ ├── -unknown-event.html │ │ │ │ ├── index.html │ │ │ │ ├── json.html │ │ │ │ └── type.html │ │ │ ├── -user-error │ │ │ │ └── index.html │ │ │ ├── -version-string │ │ │ │ ├── -version-string.html │ │ │ │ ├── full.html │ │ │ │ ├── index.html │ │ │ │ ├── major.html │ │ │ │ ├── minor.html │ │ │ │ ├── patch.html │ │ │ │ └── to-string.html │ │ │ └── index.html │ │ └── navigation.html │ │ ├── styles │ │ ├── font-jb-sans-auto.css │ │ ├── jetbrains-mono.css │ │ ├── logo-styles.css │ │ ├── main.css │ │ ├── prism.css │ │ └── style.css │ │ ├── ui-kit │ │ ├── assets │ │ │ ├── abstract-class-kotlin.svg │ │ │ ├── abstract-class.svg │ │ │ ├── annotation-kotlin.svg │ │ │ ├── annotation.svg │ │ │ ├── arrow-down.svg │ │ │ ├── burger.svg │ │ │ ├── checkbox-off.svg │ │ │ ├── checkbox-on.svg │ │ │ ├── class-kotlin.svg │ │ │ ├── class.svg │ │ │ ├── cross.svg │ │ │ ├── enum-kotlin.svg │ │ │ ├── enum.svg │ │ │ ├── exception-class.svg │ │ │ ├── field-value.svg │ │ │ ├── field-variable.svg │ │ │ ├── filter.svg │ │ │ ├── function.svg │ │ │ ├── homepage.svg │ │ │ ├── interface-kotlin.svg │ │ │ ├── interface.svg │ │ │ ├── object.svg │ │ │ ├── placeholder.svg │ │ │ ├── theme-toggle.svg │ │ │ └── typealias-kotlin.svg │ │ ├── ui-kit.min.css │ │ └── ui-kit.min.js │ │ └── zones │ │ ├── index.html │ │ ├── inkapplications.shade.zones.parameters │ │ ├── -zone-create-parameters │ │ │ ├── -zone-create-parameters.html │ │ │ ├── children.html │ │ │ ├── index.html │ │ │ └── metadata.html │ │ ├── -zone-update-parameters │ │ │ ├── -zone-update-parameters.html │ │ │ ├── children.html │ │ │ ├── index.html │ │ │ └── metadata.html │ │ └── index.html │ │ ├── inkapplications.shade.zones.structures │ │ ├── -zone │ │ │ ├── -zone.html │ │ │ ├── children.html │ │ │ ├── id.html │ │ │ ├── index.html │ │ │ ├── metadata.html │ │ │ └── services.html │ │ └── index.html │ │ ├── inkapplications.shade.zones │ │ ├── -shade-zones-module │ │ │ ├── -shade-zones-module.html │ │ │ ├── index.html │ │ │ └── zones.html │ │ ├── -zone-controls │ │ │ ├── create-zone.html │ │ │ ├── delete-zone.html │ │ │ ├── get-zone.html │ │ │ ├── index.html │ │ │ ├── list-zones.html │ │ │ └── update-zone.html │ │ └── index.html │ │ └── navigation.html ├── sdk-initialization.html ├── sdk-overview.html └── svg │ ├── logo-full-dark.svg │ └── logo-full.svg ├── events ├── api │ └── events.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── events │ │ ├── CompositeEventDeserializer.kt │ │ ├── EventSerializerContainer.kt │ │ ├── Events.kt │ │ └── EventsModule.kt │ └── jvmMain │ └── kotlin │ └── shade │ └── events │ ├── EventsModule.kt │ └── ShadeEvents.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── grouped-lights ├── api │ └── grouped-lights.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── groupedlights │ │ ├── GroupedLightControls.kt │ │ ├── ShadeGroupedLights.kt │ │ ├── ShadeGroupedLightsModule.kt │ │ ├── events │ │ └── GroupedLightEvent.kt │ │ ├── parameters │ │ └── GroupedLightUpdateParameters.kt │ │ └── structures │ │ ├── GroupDimmingInfo.kt │ │ └── GroupedLight.kt │ └── jvmTest │ └── kotlin │ └── inkapplications │ └── shade │ └── groupedlights │ ├── GroupedLightSerializationTest.kt │ └── GroupedLightUpdateParametersSerializationTest.kt ├── internals ├── api │ └── internals.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── internals │ │ ├── BaseUrl.kt │ │ ├── CachedProperty.kt │ │ ├── ConfigurableHttpClient.kt │ │ ├── DummyConfigurationContainer.kt │ │ ├── HueHttpClient.kt │ │ ├── HueStubClient.kt │ │ ├── InternalsModule.kt │ │ ├── PlatformModule.kt │ │ └── SseClient.kt │ ├── commonTest │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── internals │ │ └── CachedPropertyTest.kt │ ├── iosMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── internals │ │ └── PlatformModule.kt │ ├── jsMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── internals │ │ └── PlatformModule.kt │ ├── jvmMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── internals │ │ ├── OkHttpSseClient.kt │ │ └── PlatformModule.kt │ ├── nativeMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── internals │ │ └── PlatformModule.kt │ └── windowsMain │ └── kotlin │ └── inkapplications │ └── shade │ └── internals │ └── PlatformModule.kt ├── kotlin-js-store └── yarn.lock ├── lights ├── api │ └── lights.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── lights │ │ ├── LightControls.kt │ │ ├── ShadeLights.kt │ │ ├── ShadeLightsModule.kt │ │ ├── events │ │ ├── ColorInfoEvent.kt │ │ ├── ColorTemperatureInfoEvent.kt │ │ ├── DimmingInfoEvent.kt │ │ └── LightEvent.kt │ │ ├── parameters │ │ ├── AlertParameters.kt │ │ ├── ColorParameters.kt │ │ ├── ColorTemperatureDeltaParameters.kt │ │ ├── ColorTemperatureParameters.kt │ │ ├── DeltaAction.kt │ │ ├── DimmingDeltaParameters.kt │ │ ├── DimmingParameters.kt │ │ ├── DynamicsParameters.kt │ │ ├── EffectsParameters.kt │ │ ├── GradientParameters.kt │ │ ├── LightUpdateParameters.kt │ │ └── TimedEffectsParameters.kt │ │ └── structures │ │ ├── AlertEffectType.kt │ │ ├── AlertInfo.kt │ │ ├── Chromaticity.kt │ │ ├── ColorInfo.kt │ │ ├── ColorPalette.kt │ │ ├── ColorTemperatureInfo.kt │ │ ├── ColorTemperaturePalette.kt │ │ ├── ColorTemperatureRange.kt │ │ ├── ColorTemperatureValue.kt │ │ ├── ColorValue.kt │ │ ├── DimmingInfo.kt │ │ ├── DimmingValue.kt │ │ ├── DynamicsStatus.kt │ │ ├── Gamut.kt │ │ ├── GamutType.kt │ │ ├── Gradient.kt │ │ ├── GradientMode.kt │ │ ├── GradientPoint.kt │ │ ├── GradientValue.kt │ │ ├── Light.kt │ │ ├── LightDynamics.kt │ │ ├── LightEffect.kt │ │ ├── LightMode.kt │ │ ├── LightPowerup.kt │ │ ├── LightSignal.kt │ │ ├── LightSignalStatus.kt │ │ ├── LightSignaling.kt │ │ ├── LightingEffectInfo.kt │ │ ├── PowerupColorState.kt │ │ ├── PowerupDimmingState.kt │ │ ├── PowerupPowerState.kt │ │ ├── StandardTemperatures.kt │ │ ├── TimedLightEffect.kt │ │ └── TimedLightingEffectInfo.kt │ └── jvmTest │ └── kotlin │ └── inkapplications │ └── shade │ └── lights │ ├── LightSerializationTest.kt │ └── LightUpdateParametersSerializationTest.kt ├── resources ├── api │ └── resources.api ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── inkapplications │ └── shade │ └── resources │ ├── ResourceControls.kt │ ├── ShadeResources.kt │ ├── ShadeResourcesModule.kt │ └── structures │ └── Resource.kt ├── rooms ├── api │ └── rooms.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── rooms │ │ ├── RoomControls.kt │ │ ├── ShadeRooms.kt │ │ ├── ShadeRoomsModule.kt │ │ ├── parameters │ │ ├── RoomCreateParameters.kt │ │ └── RoomUpdateParameters.kt │ │ └── structures │ │ └── Room.kt │ └── jvmTest │ └── kotlin │ └── inkapplications │ └── shade │ └── rooms │ ├── RoomCreateParametersSerializationTest.kt │ ├── RoomSerializationTest.kt │ └── RoomUpdateParametersSerializationTest.kt ├── scenes ├── api │ └── scenes.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── scenes │ │ ├── SceneControls.kt │ │ ├── ShadeScenes.kt │ │ ├── ShadeScenesModule.kt │ │ ├── parameters │ │ ├── SceneCreateParameters.kt │ │ └── SceneUpdateParameters.kt │ │ └── structures │ │ ├── Scene.kt │ │ ├── SceneAction.kt │ │ ├── SceneActionReference.kt │ │ ├── SceneMetadata.kt │ │ ├── ScenePalette.kt │ │ ├── SceneRecall.kt │ │ ├── SceneRecallAction.kt │ │ └── SceneRecallStatus.kt │ └── jvmTest │ └── kotlin │ └── inkapplications │ └── shade │ └── scenes │ ├── parameters │ ├── SceneCreateSerializerTest.kt │ └── SceneUpdateSerializerTest.kt │ └── structures │ └── SceneSerializerTest.kt ├── serialization ├── api │ └── serialization.api ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── inkapplications │ └── shade │ └── serialization │ ├── DelegateSerializer.kt │ ├── ExplicitMiredSerializer.kt │ ├── FractionalPercentageSerializer.kt │ ├── HueError.kt │ ├── HueResponse.kt │ ├── MillisecondDurationSerializer.kt │ ├── MiredSerializer.kt │ ├── V1HueError.kt │ ├── V1Response.kt │ └── WholePercentageSerializer.kt ├── settings.gradle.kts ├── shade-cli ├── structures ├── api │ └── structures.api ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── structures │ │ ├── AuthToken.kt │ │ ├── HueConfigurationContainer.kt │ │ ├── InMemoryConfigurationContainer.kt │ │ ├── PowerInfo.kt │ │ ├── PowerValue.kt │ │ ├── ResourceId.kt │ │ ├── ResourceReference.kt │ │ ├── ResourceType.kt │ │ ├── SecurityStrategy.kt │ │ ├── SegmentArchetype.kt │ │ ├── SegmentMetadata.kt │ │ ├── SegmentMetadataUpdate.kt │ │ ├── ShadeExceptions.kt │ │ ├── UndocumentedApi.kt │ │ ├── UnknownEvent.kt │ │ ├── VersionString.kt │ │ └── parameters │ │ └── PowerParameters.kt │ ├── jvmMain │ └── kotlin │ │ └── inkapplications │ │ └── shade │ │ └── structures │ │ └── PropertiesFileConfiguration.kt │ └── jvmTest │ └── kotlin │ └── inkapplications │ └── shade │ └── structures │ └── VersionStringTest.kt └── zones ├── api └── zones.api ├── build.gradle.kts └── src ├── commonMain └── kotlin │ └── inkapplications │ └── shade │ └── zones │ ├── ShadeZones.kt │ ├── ShadeZonesModule.kt │ ├── ZoneControls.kt │ ├── parameters │ ├── ZoneCreateParameters.kt │ └── ZoneUpdateParameters.kt │ └── structures │ └── Zone.kt └── jvmTest └── kotlin └── inkapplications └── shade └── zones ├── ZoneCreateParametersSerializationTest.kt ├── ZoneSerializationTest.kt └── ZoneUpdateParametersSerializationTest.kt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ReneeVandervelde 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/api-bugs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: SDK Bug Report 3 | about: Unexpected problems or exceptions in the SDK 4 | title: '' 5 | labels: bug, SDK 6 | assignees: '' 7 | --- 8 | 9 | ### Shade Info: 10 | - Version: [1.0.0, etc] 11 | 12 | ### How to Reproduce the Issue: 13 | 14 | Example: 15 | ```kotlin 16 | fun main() { 17 | val shade = Shade() 18 | 19 | // TODO: Show what caused the issue. 20 | } 21 | ``` 22 | 23 | Expected behavior: 24 | 25 | Actual behavior: 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/cli-bugs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: CLI Bug Report 3 | about: Unexpected problems controlling lights on the command line. 4 | title: '' 5 | labels: bug, CLI 6 | assignees: '' 7 | --- 8 | 9 | ### Shade Info: 10 | - Version: [1.0.0, etc] 11 | - OS: [Mac/Windows/Linux] 12 | 13 | ### How to Reproduce the Issue: 14 | 15 | Example command: 16 | ```shell 17 | [TODO - Show the commands you used when seeing this issue.] 18 | ``` 19 | 20 | Expected behavior: 21 | 22 | Actual behavior: 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/device-bugs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Device Support 3 | about: Problems connecting or controlling a specific Hue device. 4 | title: '' 5 | labels: bug, device support 6 | assignees: '' 7 | --- 8 | 9 | ### What Device is Having Issues? 10 | - Product Name: [Color Bulb, Light Strip, etc.] 11 | - Product Part Number: [046677548483 / any ID that isn't the serial number] 12 | 13 | 14 | ### What's the Issue? 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request / Suggestion 3 | about: Request support for a new feature or change to Shade's API. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ### Proposed Change: 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a usage or documentation question. 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | ### Question: 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/pull_requests.yml: -------------------------------------------------------------------------------- 1 | on: [pull_request] 2 | jobs: 3 | tests: 4 | uses: inkapplications/.github/.github/workflows/kmp-checks.yml@1.2.0 5 | -------------------------------------------------------------------------------- /.github/workflows/pushes.yml: -------------------------------------------------------------------------------- 1 | name: Latest Build 2 | on: 3 | push: 4 | branches: [master] 5 | jobs: 6 | tests: 7 | uses: inkapplications/.github/.github/workflows/kmp-checks.yml@1.2.0 8 | build: 9 | needs: [tests] 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v4.2.2 15 | - 16 | name: Assemble 17 | run: ./gradlew assembleDist 18 | - 19 | name: Prepare Archives 20 | run: cp cli/build/distributions/shade-*.zip cli/build/distributions/shade.zip && cp cli/build/distributions/shade-*.tar cli/build/distributions/shade.tar 21 | - 22 | name: Archive CLI Tar 23 | uses: actions/upload-artifact@v4.6.2 24 | with: 25 | name: shade.tar 26 | path: cli/build/distributions/shade.tar 27 | - 28 | name: Archive CLI Zip 29 | uses: actions/upload-artifact@v4.6.2 30 | with: 31 | name: shade.zip 32 | path: cli/build/distributions/shade.zip 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .gradle/ 3 | .shade-config.properties 4 | .kotlin/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019-2023 Ink Applications 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shade 2 | 3 | ![Shade Logo](docs/svg/logo-full.svg) 4 | 5 | _Cross Platform CLI and Multiplatform Kotlin SDK for controlling Hue devices._ 6 | 7 | ## Cross-Platform Command Line 8 | 9 | Shade's CLI application for Windows, MacOS and Linux provides commands for 10 | controlling your lighing directly from the terminal. 11 | 12 | ```shell 13 | $ shade update-light $lightId --brightness=10% 14 | ``` 15 | 16 | [Get Started](https://shade.lighting) 17 | 18 | ## Kotlin Multiplatform SDK 19 | 20 | Shade's Kotlin SDK provides API's for controlling your lighting on Java, 21 | Android and Javascript platforms. 22 | 23 | ```kotlin 24 | shade.lights.updateLight( 25 | id = lightId, 26 | parameters = LightUpdateParameters( 27 | brightness = 10.percent, 28 | ), 29 | ) 30 | ``` 31 | 32 | [Get Started](https://shade.lighting) 33 | 34 | ## Free + Open Source 35 | Shade is free under the [MIT License], the project is Open Source, actively 36 | maintained, and always looking for contributions. 37 | 38 | There are many Hue devices and things to do with them. Testing all that can 39 | be difficult. Please [report] any issues you find. 40 | 41 | [MIT License]:LICENSE 42 | [report]:https://github.com/InkApplications/Shade/issues/new/choose 43 | 44 | ## Trademarks 45 | 46 | "Hue" and "Philips" are trademarks of Signify Holding and are not affiliated 47 | with this project. 48 | -------------------------------------------------------------------------------- /auth/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | api(libs.datetime) 16 | 17 | api(libs.coroutines.core) 18 | } 19 | } 20 | 21 | val commonTest by getting { 22 | dependencies { 23 | implementation(libs.test.core) 24 | implementation(libs.test.annotations) 25 | implementation(libs.coroutines.test) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /auth/src/commonMain/kotlin/inkapplications/shade/auth/AuthModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.auth 2 | 3 | import inkapplications.shade.internals.InternalsModule 4 | import kimchi.logger.KimchiLogger 5 | 6 | /** 7 | * Provides access to authorization services 8 | */ 9 | class AuthModule( 10 | internalsModule: InternalsModule, 11 | logger: KimchiLogger, 12 | ) { 13 | val bridgeAuth: BridgeAuth = ShadeBridgeAuth( 14 | client = internalsModule.hueHttpClient, 15 | configurationContainer = internalsModule.configurationContainer, 16 | logger = logger, 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /auth/src/commonMain/kotlin/inkapplications/shade/auth/BridgeAuth.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.auth 2 | 3 | import inkapplications.shade.auth.structures.AppId 4 | import inkapplications.shade.structures.AuthToken 5 | import kotlin.time.Duration 6 | import kotlin.time.Duration.Companion.seconds 7 | import kotlin.time.ExperimentalTime 8 | 9 | /** 10 | * Handles retrieving a token for authorization with the Hue Bridge. 11 | */ 12 | interface BridgeAuth { 13 | /** 14 | * Wait for the user to hit the confirmation button to get a token. 15 | * 16 | * @param retries How many times to ask the hue bridge for a token 17 | * before giving up and timing out. (Default 50) 18 | * @param timeout The amount of time to wait in-between requests. (Default 5 seconds) 19 | * @return A bearer token to be used with requests to the Hue API. 20 | * These do not appear to expire. Store it safely. 21 | */ 22 | @ExperimentalTime 23 | suspend fun awaitToken( 24 | appId: AppId, 25 | retries: Int = 50, 26 | timeout: Duration = 5.seconds 27 | ): AuthToken 28 | } 29 | 30 | -------------------------------------------------------------------------------- /auth/src/commonMain/kotlin/inkapplications/shade/auth/structures/AppId.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.auth.structures 2 | 3 | import inkapplications.shade.serialization.DelegateSerializer 4 | import kotlinx.serialization.Serializable 5 | import kotlinx.serialization.builtins.serializer 6 | 7 | /** 8 | * Identifies an app connection upon auth 9 | */ 10 | @Serializable(with = AppId.Serializer::class) 11 | data class AppId( 12 | val appName: String, 13 | val instanceName: String, 14 | ) { 15 | internal object Serializer: DelegateSerializer(String.serializer()) { 16 | override fun serialize(data: AppId): String = "${data.appName}#${data.instanceName}" 17 | override fun deserialize(data: String): AppId = data.split('#').let { 18 | AppId( 19 | appName = it[0], 20 | instanceName = it[1], 21 | ) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /auth/src/commonMain/kotlin/inkapplications/shade/auth/structures/AuthRequest.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.auth.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Request used when authenticating with the hue API 8 | */ 9 | @Serializable 10 | internal class AuthRequest( 11 | @SerialName("devicetype") 12 | val appId: AppId, 13 | @SerialName("generateclientkey") 14 | val generateClientKey: Boolean, 15 | ) 16 | -------------------------------------------------------------------------------- /auth/src/jvmTest/kotlin/inkapplications/shade/auth/structures/AppIdSerializerTest.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.auth.structures 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import kotlinx.serialization.encodeToString 5 | import kotlinx.serialization.json.Json 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | 9 | class AppIdSerializerTest { 10 | @Test 11 | fun serializer() { 12 | val appId = AppId( 13 | appName = "test-name", 14 | instanceName = "test-instance", 15 | ) 16 | val json = """"test-name#test-instance"""".trimIndent() 17 | 18 | val encodedResult = Json.encodeToString(appId) 19 | val decodedResult = Json.decodeFromString(json) 20 | 21 | assertEquals(json, encodedResult, "Encoded result") 22 | assertEquals(appId, decodedResult, "Decoding result") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.api.tasks.testing.logging.TestExceptionFormat 2 | 3 | plugins { 4 | id("org.jetbrains.dokka") 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | subprojects { 12 | repositories { 13 | mavenCentral() 14 | } 15 | tasks.withType(Test::class) { 16 | testLogging.exceptionFormat = TestExceptionFormat.FULL 17 | } 18 | } 19 | 20 | tasks.dokkaHtmlMultiModule.configure { 21 | outputDirectory.set(rootDir.resolve("docs/reference/${project.version}")) 22 | } 23 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | gradlePluginPortal() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation(libs.gradle) 12 | implementation(libs.serialization.plugin) 13 | implementation(libs.dokka) 14 | implementation(libs.kotlinx.binary.compatibility) 15 | } 16 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | versionCatalogs { 3 | create("libs") { 4 | from(files("../gradle/libs.versions.toml")) 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /cli/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | kotlin("jvm") 4 | } 5 | 6 | application { 7 | applicationName = "shade" 8 | mainClass.set("inkapplications.shade.cli.MainKt") 9 | } 10 | 11 | dependencies { 12 | implementation(libs.coroutines.core) 13 | implementation("com.github.ajalt.clikt:clikt:4.4.0") 14 | implementation("org.slf4j:slf4j-nop:2.0.16") 15 | implementation(projects.core) 16 | } 17 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/AuthorizedShadeCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli 2 | 3 | import com.github.ajalt.clikt.parameters.options.option 4 | import inkapplications.shade.structures.AuthToken 5 | import kotlinx.coroutines.flow.MutableStateFlow 6 | import kotlinx.coroutines.flow.StateFlow 7 | 8 | abstract class AuthorizedShadeCommand( 9 | help: String, 10 | ): ShadeCommand(help) { 11 | private val key by option( 12 | help = "Application API Key/Token for connecting to the hue bridge" 13 | ) 14 | 15 | override val authToken: StateFlow by lazy { 16 | key?.let { AuthToken(applicationKey = it) }?.let(::MutableStateFlow) 17 | ?: fileProperties.authToken 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/connection/DiscoverCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.connection 2 | 3 | import inkapplications.shade.cli.ShadeCommand 4 | 5 | object DiscoverCommand: ShadeCommand( 6 | help = "Discover hue bridges on the network", 7 | ) { 8 | override suspend fun runCommand(): Int { 9 | val devices = shade.onlineDiscovery.getDevices() 10 | 11 | devices.forEach { 12 | echo("${it.id}:") 13 | echo(" ip: ${it.localIp}") 14 | echo(" port: ${it.port}") 15 | } 16 | 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/devices/DeleteDeviceCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.devices 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object DeleteDeviceCommand: AuthorizedShadeCommand( 8 | help = "Delete a device from the hue bridge", 9 | ) { 10 | private val deviceId by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val response = shade.devices.deleteDevice(deviceId) 14 | 15 | logger.debug("Got response: $response") 16 | 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/devices/DeviceOutput.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.devices 2 | 3 | import com.github.ajalt.clikt.core.CliktCommand 4 | import com.github.ajalt.clikt.output.TermUi 5 | import inkapplications.shade.devices.structures.Device 6 | 7 | /** 8 | * Echo a device's properties in a human readable format. 9 | */ 10 | fun CliktCommand.echoDevice(device: Device) { 11 | echo("${device.id.value}:") 12 | echo(" Name: ${device.metadata.name}") 13 | echo(" Archetype: ${device.metadata.archetype}") 14 | echo(" Model ID: ${device.productData.modelId}") 15 | echo(" Manufacturer: ${device.productData.manufacturerName}") 16 | echo(" Product Name: ${device.productData.productName}") 17 | echo(" Product Archetype: ${device.productData.productArchetype}") 18 | echo(" Certified: ${device.productData.certified}") 19 | echo(" Software Version: ${device.productData.softwareVersion}") 20 | device.productData.hardwarePlatformType?.run { 21 | echo(" Hardware Platform Type: $this") 22 | } 23 | echo(" Services:") 24 | device.services.forEach { 25 | echo(" - $it") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/devices/GetDeviceCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.devices 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object GetDeviceCommand: AuthorizedShadeCommand( 8 | help = "Get a specific device by ID", 9 | ) { 10 | private val deviceId by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val device = shade.devices.getDevice(deviceId) 14 | 15 | logger.debug("Got Device: $device") 16 | echoDevice(device) 17 | 18 | return 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/devices/IdentifyDeviceCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.devices 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object IdentifyDeviceCommand: AuthorizedShadeCommand( 8 | help = "Trigger a visual identification sequence on a specified device", 9 | ) { 10 | private val deviceId by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val response = shade.devices.identifyDevice(deviceId) 14 | 15 | logger.debug("Got response: $response") 16 | 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/devices/ListDevicesCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.devices 2 | 3 | import inkapplications.shade.cli.AuthorizedShadeCommand 4 | 5 | object ListDevicesCommand: AuthorizedShadeCommand( 6 | help = "Get all of the devices configured on the hue bridge", 7 | ) { 8 | override suspend fun runCommand(): Int { 9 | val devices = shade.devices.listDevices() 10 | 11 | logger.debug("Got Devices: $devices") 12 | devices.forEach(::echoDevice) 13 | 14 | return 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/devices/UpdateDeviceCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.devices 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import com.github.ajalt.clikt.parameters.options.convert 5 | import com.github.ajalt.clikt.parameters.options.option 6 | import inkapplications.shade.cli.AuthorizedShadeCommand 7 | import inkapplications.shade.cli.resourceId 8 | import inkapplications.shade.devices.parameters.DeviceMetadataParameters 9 | import inkapplications.shade.devices.parameters.UpdateDeviceParameters 10 | import inkapplications.shade.devices.structures.ProductArchetype 11 | 12 | object UpdateDeviceCommand: AuthorizedShadeCommand( 13 | help = "Update a device on the hue bridge", 14 | ) { 15 | private val deviceId by argument().resourceId() 16 | private val name by option(help = "Human readable name to assign to the device") 17 | private val archetype by option(help = "The product type of device to assign to the device") 18 | .convert { ProductArchetype(it) } 19 | 20 | override suspend fun runCommand(): Int { 21 | val response = shade.devices.updateDevice( 22 | deviceId = deviceId, 23 | parameters = UpdateDeviceParameters( 24 | metadata = DeviceMetadataParameters( 25 | name = name, 26 | archetype = archetype, 27 | ), 28 | ), 29 | ) 30 | 31 | logger.debug("Got response: $response") 32 | 33 | return 0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/groupedlights/GetGroupedLightCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.groupedlights 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object GetGroupedLightCommand: AuthorizedShadeCommand( 8 | help = "Get data for a specific grouped light" 9 | ) { 10 | private val id by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val group = shade.groupedLights.getGroup(id) 14 | 15 | logger.debug("Got Group: $group") 16 | echoGroup(group) 17 | 18 | return 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/groupedlights/GroupedLightsOutput.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.groupedlights 2 | 3 | import com.github.ajalt.clikt.core.CliktCommand 4 | import com.github.ajalt.clikt.output.TermUi 5 | import inkapplications.shade.groupedlights.structures.GroupedLight 6 | 7 | fun CliktCommand.echoGroup(group: GroupedLight) { 8 | echo("${group.id.value}:") 9 | echo(" Owner: ${group.owner}") 10 | group.powerInfo?.run { 11 | echo(" On: $on") 12 | } 13 | group.dimmingInfo?.run { 14 | echo(" Dimming: $brightness") 15 | } 16 | group.alertInfo?.run { 17 | echo(" Alerts: ${actionValues.joinToString()}") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/groupedlights/ListGroupedLightsCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.groupedlights 2 | 3 | import inkapplications.shade.cli.AuthorizedShadeCommand 4 | 5 | object ListGroupedLightsCommand: AuthorizedShadeCommand( 6 | help = "Get all grouped lights configured on the Hue bridge", 7 | ) { 8 | override suspend fun runCommand(): Int { 9 | val groups = shade.groupedLights.listGroups() 10 | 11 | logger.debug("Got Groups: $groups") 12 | groups.forEach { echoGroup(it) } 13 | 14 | return 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/lights/GetLightCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.lights 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object GetLightCommand: AuthorizedShadeCommand( 8 | help = "Get data for a specific light" 9 | ) { 10 | private val lightId by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val light = shade.lights.getLight(lightId) 14 | 15 | logger.debug("Got Light: $light") 16 | echoLight(light) 17 | 18 | return 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/lights/LightOutput.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.lights 2 | 3 | import com.github.ajalt.clikt.core.CliktCommand 4 | import com.github.ajalt.clikt.output.TermUi 5 | import inkapplications.shade.lights.structures.Light 6 | import inkapplications.spondee.scalar.toWholePercentage 7 | import inkapplications.spondee.structure.format 8 | 9 | fun CliktCommand.echoLight(light: Light) { 10 | echo("${light.id.value}:") 11 | echo(" On: ${light.powerInfo.on}") 12 | echo(" Mode: ${light.mode}") 13 | light.colorTemperatureInfo?.run { 14 | val temperatureString = temperature?.toKelvin()?.format() ?: "--" 15 | echo(" Temperature: $temperatureString") 16 | echo(" Temperature Range: ${range}") 17 | } 18 | light.dimmingInfo?.run { 19 | echo(" Brightness: ${brightness.toWholePercentage().format()}") 20 | } 21 | light.colorInfo?.run { 22 | echo(" Color (rgb): ${color.toSRGB().toHex()}") 23 | echo(" Color (xy): [${color.toXYZ().toCIExyY().x},${color.toXYZ().toCIExyY().y}]") 24 | } 25 | light.dynamics?.run { 26 | echo(" Current Dynamics: ${status}") 27 | echo(" Available Dynamics: ${statusValues.joinToString()}") 28 | echo(" Dynamics Speed: ${speed.toWholePercentage().format()}${"*".takeUnless { speedValid }.orEmpty()}") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/lights/ListLightsCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.lights 2 | 3 | import inkapplications.shade.cli.AuthorizedShadeCommand 4 | 5 | object ListLightsCommand: AuthorizedShadeCommand( 6 | help = "Get all lights connected to the bridge" 7 | ) { 8 | override suspend fun runCommand(): Int { 9 | val lights = shade.lights.listLights() 10 | 11 | logger.debug("Got Lights: $lights") 12 | lights.forEach { echoLight(it) } 13 | 14 | return 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/resources/ListResourcesCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.resources 2 | 3 | import inkapplications.shade.cli.AuthorizedShadeCommand 4 | 5 | object ListResourcesCommand: AuthorizedShadeCommand( 6 | help = "Get all resources for the bridge" 7 | ) { 8 | override suspend fun runCommand(): Int { 9 | val resources = shade.resources.listResources() 10 | 11 | logger.debug("Got Resources: $resources") 12 | resources.forEach { resource -> 13 | echo("${resource.id}:") 14 | echo(" Type: ${resource.type}") 15 | } 16 | 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/rooms/CreateRoomCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.rooms 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import com.github.ajalt.clikt.parameters.options.option 5 | import inkapplications.shade.cli.AuthorizedShadeCommand 6 | import inkapplications.shade.cli.deviceResourceReferences 7 | import inkapplications.shade.cli.segmentArchetype 8 | import inkapplications.shade.rooms.parameters.RoomCreateParameters 9 | import inkapplications.shade.structures.SegmentMetadata 10 | 11 | object CreateRoomCommand: AuthorizedShadeCommand( 12 | help = "Create a new room on the Hue bridge" 13 | ) { 14 | val name by argument( 15 | help = "A human-readable name for the room" 16 | ) 17 | 18 | val archetype by argument( 19 | help = "The type of room" 20 | ).segmentArchetype() 21 | 22 | val childrenDeviceIds by option( 23 | help = "A comma-separated list of device ID's to add as children for the room." 24 | ).deviceResourceReferences() 25 | 26 | override suspend fun runCommand(): Int { 27 | val response = shade.rooms.createRoom( 28 | parameters = RoomCreateParameters( 29 | metadata = SegmentMetadata( 30 | archetype = archetype, 31 | name = name, 32 | ), 33 | children = childrenDeviceIds.orEmpty(), 34 | ) 35 | ) 36 | 37 | logger.debug("Got response $response") 38 | 39 | return 0 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/rooms/DeleteRoomCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.rooms 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object DeleteRoomCommand: AuthorizedShadeCommand( 8 | help = "Delete a room from the hue bridge" 9 | ) { 10 | private val roomId by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val response = shade.rooms.deleteRoom(roomId) 14 | 15 | logger.debug("Got response: $response") 16 | 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/rooms/GetRoomCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.rooms 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object GetRoomCommand: AuthorizedShadeCommand( 8 | help = "Get data for a specific room" 9 | ) { 10 | private val roomId by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val room = shade.rooms.getRoom(roomId) 14 | 15 | logger.debug("Got Room: $room") 16 | echoRoom(room) 17 | 18 | return 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/rooms/ListRoomsCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.rooms 2 | 3 | import inkapplications.shade.cli.AuthorizedShadeCommand 4 | 5 | object ListRoomsCommand: AuthorizedShadeCommand( 6 | help = "Get all of the rooms configured on the Hue bridge" 7 | ) { 8 | override suspend fun runCommand(): Int { 9 | val rooms = shade.rooms.listRooms() 10 | 11 | logger.debug("Got Rooms: $rooms") 12 | rooms.forEach(::echoRoom) 13 | 14 | return 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/rooms/RoomOutput.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.rooms 2 | 3 | import com.github.ajalt.clikt.core.CliktCommand 4 | import com.github.ajalt.clikt.output.TermUi 5 | import inkapplications.shade.rooms.structures.Room 6 | 7 | fun CliktCommand.echoRoom(room: Room) { 8 | echo("${room.id.value}:") 9 | echo(" Name: ${room.metadata.name}") 10 | echo(" Archetype: ${room.metadata.archetype}") 11 | echo(" Children:") 12 | room.children.forEach { 13 | echo(" - $it") 14 | } 15 | echo(" Services:") 16 | room.services.forEach { 17 | echo(" - $it") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/scenes/Assertions.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.scenes 2 | 3 | /** 4 | * Assert that two lists are the same size. 5 | */ 6 | fun assertSameSize(expected: List<*>?, actual: List<*>?, message: String) { 7 | if (expected?.size != actual?.size) { 8 | throw IllegalArgumentException(message) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/scenes/DeleteSceneCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.scenes 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object DeleteSceneCommand: AuthorizedShadeCommand( 8 | help = "Delete a scene from the hue bridge" 9 | ) { 10 | private val sceneId by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val response = shade.scenes.deleteScene(sceneId) 14 | 15 | logger.debug("Got response: $response") 16 | 17 | echo(response) 18 | 19 | return 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/scenes/GetSceneCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.scenes 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object GetSceneCommand: AuthorizedShadeCommand( 8 | help = "Get information for a specific Scene", 9 | ) { 10 | private val id by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val scene = shade.scenes.getScene(id) 14 | 15 | echoScene(scene) 16 | 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/scenes/ListScenesCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.scenes 2 | 3 | import inkapplications.shade.cli.AuthorizedShadeCommand 4 | 5 | object ListScenesCommand: AuthorizedShadeCommand( 6 | help = "Get all of the scenes configured on the Hue bridge", 7 | ) { 8 | override suspend fun runCommand(): Int { 9 | val scenes = shade.scenes.listScenes() 10 | 11 | logger.debug("Got Scenes: $scenes") 12 | scenes.forEach(::echoScene) 13 | 14 | return 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/scenes/SceneOutput.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.scenes 2 | 3 | import com.github.ajalt.clikt.core.CliktCommand 4 | import com.github.ajalt.clikt.output.TermUi 5 | import inkapplications.shade.scenes.structures.Scene 6 | import inkapplications.spondee.scalar.toWholePercentage 7 | import inkapplications.spondee.structure.format 8 | 9 | fun CliktCommand.echoScene(scene: Scene) { 10 | echo("${scene.id}:") 11 | echo(" Name: ${scene.metadata.name}") 12 | echo(" Group: ${scene.group}") 13 | echo(" Speed: ${scene.speed.toWholePercentage().format()}") 14 | echo(" Auto Dynamic: ${scene.autoDynamic}") 15 | echo(" Actions:") 16 | scene.actions.forEach { action -> 17 | echo(" - ${action.target}:") 18 | echo(" Power: ${action.action.powerValue}") 19 | echo(" Brightness: ${action.action.dimmingValue?.brightness?.toWholePercentage()?.format()}") 20 | echo(" Color: ${action.action.colorValue?.color?.toSRGB()?.toHex()}") 21 | echo(" Color Temperature: ${action.action.colorTemperatureValue?.temperature?.toKelvin()?.format()}") 22 | echo(" Effect: ${action.action.effect}") 23 | echo(" Duration: ${action.action.dynamicsDuration}") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/zones/CreateZoneCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.zones 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import com.github.ajalt.clikt.parameters.options.option 5 | import inkapplications.shade.cli.AuthorizedShadeCommand 6 | import inkapplications.shade.cli.deviceResourceReferences 7 | import inkapplications.shade.cli.segmentArchetype 8 | import inkapplications.shade.structures.SegmentMetadata 9 | import inkapplications.shade.zones.parameters.ZoneCreateParameters 10 | 11 | object CreateZoneCommand: AuthorizedShadeCommand( 12 | help = "Create a new zone on the Hue bridge" 13 | ) { 14 | val name by argument( 15 | help = "A human-readable name for the zone" 16 | ) 17 | 18 | val archetype by argument( 19 | help = "The type of zone" 20 | ).segmentArchetype() 21 | 22 | val childrenDeviceIds by option( 23 | help = "A comma-separated list of device ID's to add as children for the zone." 24 | ).deviceResourceReferences() 25 | 26 | override suspend fun runCommand(): Int { 27 | val response = shade.zones.createZone( 28 | parameters = ZoneCreateParameters( 29 | metadata = SegmentMetadata( 30 | archetype = archetype, 31 | name = name, 32 | ), 33 | children = childrenDeviceIds.orEmpty(), 34 | ) 35 | ) 36 | 37 | logger.debug("Got response $response") 38 | 39 | return 0 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/zones/DeleteRoomCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.zones 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object DeleteZoneCommand: AuthorizedShadeCommand( 8 | help = "Delete a zone from the hue bridge" 9 | ) { 10 | private val zoneId by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val response = shade.zones.deleteZone(zoneId) 14 | 15 | logger.debug("Got response: $response") 16 | 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/zones/GetZoneCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.zones 2 | 3 | import com.github.ajalt.clikt.parameters.arguments.argument 4 | import inkapplications.shade.cli.AuthorizedShadeCommand 5 | import inkapplications.shade.cli.resourceId 6 | 7 | object GetZoneCommand: AuthorizedShadeCommand( 8 | help = "Get information for a specific Zone" 9 | ) { 10 | private val id by argument().resourceId() 11 | 12 | override suspend fun runCommand(): Int { 13 | val zone = shade.zones.getZone(id) 14 | 15 | echoZone(zone) 16 | 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/zones/ListZonesCommand.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.zones 2 | 3 | import inkapplications.shade.cli.AuthorizedShadeCommand 4 | 5 | object ListZonesCommand: AuthorizedShadeCommand( 6 | help = "Get all of the zones configured on the Hue bridge" 7 | ) { 8 | override suspend fun runCommand(): Int { 9 | val zones = shade.zones.listZones() 10 | 11 | logger.debug("Got Zones: $zones") 12 | zones.forEach(::echoZone) 13 | 14 | return 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cli/src/main/kotlin/inkapplications/shade/cli/zones/ZoneOutput.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.cli.zones 2 | 3 | import com.github.ajalt.clikt.core.CliktCommand 4 | import com.github.ajalt.clikt.output.TermUi 5 | import inkapplications.shade.zones.structures.Zone 6 | 7 | fun CliktCommand.echoZone(zone: Zone) { 8 | echo("${zone.id.value}:") 9 | echo(" Name: ${zone.metadata.name}") 10 | echo(" Archetype: ${zone.metadata.archetype}") 11 | echo(" Children:") 12 | zone.children.forEach { 13 | echo(" - $it") 14 | } 15 | echo(" Services:") 16 | zone.services.forEach { 17 | echo(" - $it") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | id("ink.publishing") 4 | } 5 | 6 | kotlin { 7 | sourceSets { 8 | val commonMain by getting { 9 | dependencies { 10 | implementation(projects.internals) 11 | api(projects.auth) 12 | api(projects.discover) 13 | api(projects.devices) 14 | api(projects.events) 15 | api(projects.groupedLights) 16 | api(projects.lights) 17 | api(projects.resources) 18 | api(projects.rooms) 19 | api(projects.scenes) 20 | api(projects.structures) 21 | api(projects.zones) 22 | 23 | api(libs.kimchi.logger) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/inkapplications/shade/core/JvmExtensions.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.core 2 | 3 | import inkapplications.shade.events.Events 4 | import inkapplications.shade.structures.UndocumentedApi 5 | import shade.events.events 6 | 7 | @UndocumentedApi 8 | val Shade.events: Events get() = eventsModule.events 9 | -------------------------------------------------------------------------------- /devices/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | 16 | api(libs.coroutines.core) 17 | } 18 | } 19 | 20 | val commonTest by getting { 21 | dependencies { 22 | implementation(libs.test.core) 23 | implementation(libs.test.annotations) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/ShadeDevicesModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices 2 | 3 | import inkapplications.shade.internals.InternalsModule 4 | 5 | /** 6 | * Module for accessing Device information. 7 | */ 8 | class ShadeDevicesModule( 9 | internalsModule: InternalsModule, 10 | ) { 11 | val devices: DeviceControls = ShadeDevices(internalsModule.hueHttpClient) 12 | } 13 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/parameters/DeviceMetadataParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices.parameters 2 | 3 | import inkapplications.shade.devices.structures.ProductArchetype 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * User-configured metadata configurable for the device. 8 | */ 9 | @Serializable 10 | data class DeviceMetadataParameters( 11 | /** 12 | * Human readable name to assign to the device. 13 | */ 14 | val name: String? = null, 15 | 16 | /** 17 | * User-assigned product type of device to assign to the device. 18 | */ 19 | val archetype: ProductArchetype? = null, 20 | ) 21 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/parameters/IdentifyParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices.parameters 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Internal object used to trigger an identify event. 7 | */ 8 | @Serializable 9 | internal class IdentifyParameters private constructor( 10 | val action: String, 11 | ) { 12 | constructor(): this("identify") 13 | } 14 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/parameters/UpdateDeviceParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices.parameters 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Configurable data for a device. 7 | */ 8 | @Serializable 9 | data class UpdateDeviceParameters internal constructor( 10 | /** 11 | * User metadata for the device. 12 | */ 13 | val metadata: DeviceMetadataParameters? = null, 14 | internal val identify: IdentifyParameters? = null, 15 | ) { 16 | constructor( 17 | metadata: DeviceMetadataParameters? = null, 18 | ): this( 19 | metadata = metadata, 20 | identify = null, 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/structures/Device.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices.structures 2 | 3 | import inkapplications.shade.structures.ResourceId 4 | import inkapplications.shade.structures.ResourceReference 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Attributes for a device configured on the hue bridge. 10 | */ 11 | @Serializable 12 | data class Device( 13 | /** 14 | * Unique ID for the device. 15 | */ 16 | val id: ResourceId, 17 | 18 | /** 19 | * Clip v1 resource identifier. 20 | */ 21 | @Deprecated("V1 Resource. Left for migration purposes only, may be removed at any point by API or SDK.") 22 | @SerialName("id_v1") 23 | val v1Id: String? = null, 24 | 25 | /** 26 | * Information about the hardware itself. 27 | */ 28 | @SerialName("product_data") 29 | val productData: ProductData, 30 | 31 | /** 32 | * Configured metadata for this device. 33 | */ 34 | val metadata: ProductMetadata, 35 | 36 | /** 37 | * References all services providing control and state of the device. 38 | */ 39 | val services: List, 40 | ) 41 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/structures/HardwarePlatformType.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Hardware type as identified by Manufacturer. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class HardwarePlatformType(val value: String) { 12 | override fun toString(): String = value 13 | } 14 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/structures/ModelId.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Identifier for a specific model of hardware. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class ModelId(val value: String) { 12 | override fun toString(): String = value 13 | } 14 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/structures/ProductData.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices.structures 2 | 3 | import inkapplications.shade.structures.VersionString 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Manufacturer specified information about a device. 9 | */ 10 | @Serializable 11 | data class ProductData( 12 | /** 13 | * Unique Identification of the device model. 14 | */ 15 | @SerialName("model_id") 16 | val modelId: ModelId, 17 | 18 | /** 19 | * Name of the device's manufacturer. 20 | */ 21 | @SerialName("manufacturer_name") 22 | val manufacturerName: String, 23 | 24 | /** 25 | * Name of the product. 26 | */ 27 | @SerialName("product_name") 28 | val productName: String, 29 | 30 | /** 31 | * General archetype of the device, as specified by the manufacturer. 32 | */ 33 | @SerialName("product_archetype") 34 | val productArchetype: ProductArchetype, 35 | 36 | /** 37 | * Whether the device is hue certified. 38 | */ 39 | val certified: Boolean, 40 | 41 | /** 42 | * Version of the software running on the device. 43 | */ 44 | @SerialName("software_version") 45 | val softwareVersion: VersionString, 46 | 47 | /** 48 | * Hardware type; identified by Manufacturer code and ImageType. 49 | */ 50 | @SerialName("hardware_platform_type") 51 | val hardwarePlatformType: HardwarePlatformType? = null, 52 | ) 53 | -------------------------------------------------------------------------------- /devices/src/commonMain/kotlin/inkapplications/shade/devices/structures/ProductMetadata.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.devices.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Configured metadata for a specific [Device]. 7 | */ 8 | @Serializable 9 | data class ProductMetadata( 10 | /** 11 | * Human readable name for the device. 12 | */ 13 | val name: String, 14 | 15 | /** 16 | * User-configured archetype for the device or default given by the 17 | * manufacturer. 18 | */ 19 | val archetype: ProductArchetype, 20 | ) 21 | -------------------------------------------------------------------------------- /discover/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | api(libs.coroutines.core) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | 16 | implementation(libs.ktor.client.core) 17 | implementation(libs.ktor.client.contentnegotiation) 18 | implementation(libs.ktor.serialization.json) 19 | } 20 | } 21 | 22 | val commonTest by getting { 23 | dependencies { 24 | implementation(libs.test.core) 25 | implementation(libs.test.annotations) 26 | } 27 | } 28 | 29 | val jvmMain by getting { 30 | dependencies { 31 | implementation(libs.ktor.client.okhttp) 32 | } 33 | } 34 | 35 | val nativeMain by getting { 36 | dependencies { 37 | implementation(libs.ktor.client.cio) 38 | } 39 | } 40 | 41 | val windowsMain by getting { 42 | dependencies { 43 | implementation(libs.ktor.client.winhttp) 44 | } 45 | } 46 | 47 | val iosMain by getting { 48 | dependencies { 49 | implementation(libs.ktor.client.darwin) 50 | } 51 | } 52 | 53 | val jsMain by getting { 54 | dependencies { 55 | implementation(libs.ktor.client.js) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /discover/src/commonMain/kotlin/inkapplications/shade/discover/BridgeDiscovery.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import inkapplications.shade.discover.structures.Bridge 4 | 5 | /** 6 | * Hue bridge discovery functions 7 | */ 8 | interface BridgeDiscovery { 9 | /** 10 | * Get a list of Hue Bridges available on the network. 11 | */ 12 | suspend fun getDevices(): List 13 | } 14 | -------------------------------------------------------------------------------- /discover/src/commonMain/kotlin/inkapplications/shade/discover/DiscoverModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.plugins.contentnegotiation.* 5 | import io.ktor.serialization.kotlinx.json.* 6 | import kotlinx.serialization.json.Json 7 | 8 | /** 9 | * Provides access to Hue's discovery services. 10 | */ 11 | class DiscoverModule { 12 | private val platformModule = PlatformModule() 13 | 14 | private val json = Json { 15 | ignoreUnknownKeys = true 16 | } 17 | 18 | private val client = HttpClient(platformModule.createEngine()) { 19 | install(ContentNegotiation) { 20 | json(json) 21 | } 22 | } 23 | 24 | /** 25 | * Discovery implementation using Hue's online discovery protocol. 26 | * 27 | * Note that this implementation requires an active internet connection 28 | * on both the client and bridge device to function. 29 | */ 30 | val onlineDiscovery: BridgeDiscovery = KtorDiscovery(client) 31 | } 32 | -------------------------------------------------------------------------------- /discover/src/commonMain/kotlin/inkapplications/shade/discover/KtorDiscovery.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import inkapplications.shade.discover.structures.Bridge 4 | import inkapplications.shade.structures.ApiStatusError 5 | import inkapplications.shade.structures.NetworkException 6 | import inkapplications.shade.structures.SerializationError 7 | import io.ktor.client.* 8 | import io.ktor.client.call.* 9 | import io.ktor.client.request.* 10 | import io.ktor.http.* 11 | 12 | /** 13 | * Implements bridge discovery using a local ktor client 14 | */ 15 | internal class KtorDiscovery( 16 | private val client: HttpClient, 17 | ): BridgeDiscovery { 18 | override suspend fun getDevices(): List { 19 | val httpResponse = try { 20 | client.get { 21 | url { 22 | host = "discovery.meethue.com" 23 | protocol = URLProtocol.HTTPS 24 | } 25 | accept(ContentType.Application.Json) 26 | } 27 | } catch (e: Throwable) { 28 | throw NetworkException("Unknown Error making API Request for discovery", e) 29 | } 30 | 31 | if (!httpResponse.status.isSuccess()) throw ApiStatusError( 32 | code = httpResponse.status.value, 33 | ) 34 | 35 | return try { 36 | httpResponse.body() 37 | } catch (e: Throwable) { 38 | throw SerializationError("Unable to parse discovery response", e) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /discover/src/commonMain/kotlin/inkapplications/shade/discover/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import io.ktor.client.engine.* 4 | 5 | /** 6 | * Provides platform-specific dependencies for the SDK. 7 | */ 8 | internal expect class PlatformModule() { 9 | /** 10 | * Create Ktor http engine based on the platform. 11 | */ 12 | fun createEngine(): HttpClientEngineFactory<*> 13 | } 14 | -------------------------------------------------------------------------------- /discover/src/commonMain/kotlin/inkapplications/shade/discover/structures/Bridge.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * A discovered Hue Bridge. 8 | */ 9 | @Serializable 10 | data class Bridge( 11 | /** 12 | * The unique ID of this hue bridge. 13 | */ 14 | val id: BridgeId, 15 | 16 | /** 17 | * The local network IP address of the bridge. 18 | */ 19 | @SerialName("internalipaddress") 20 | val localIp: String, 21 | 22 | /** 23 | * The port to be used for the Hue API. 24 | * 25 | * Note: This field was not always available on older bridges, and will 26 | * default to an insecure 80 HTTP port if not specified. 27 | */ 28 | val port: Int = 80, 29 | ) 30 | -------------------------------------------------------------------------------- /discover/src/commonMain/kotlin/inkapplications/shade/discover/structures/BridgeId.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Wraps a bridge ID value. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class BridgeId(val value: String) { 12 | override fun toString(): String = value 13 | } 14 | -------------------------------------------------------------------------------- /discover/src/iosMain/kotlin/inkapplications/shade/discover/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import io.ktor.client.engine.* 4 | import io.ktor.client.engine.darwin.* 5 | 6 | internal actual class PlatformModule actual constructor() { 7 | actual fun createEngine(): HttpClientEngineFactory<*> = Darwin 8 | } 9 | -------------------------------------------------------------------------------- /discover/src/jsMain/kotlin/inkapplications/shade/discover/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import io.ktor.client.engine.* 4 | import io.ktor.client.engine.js.* 5 | 6 | internal actual class PlatformModule actual constructor() { 7 | actual fun createEngine(): HttpClientEngineFactory<*> = Js 8 | } 9 | -------------------------------------------------------------------------------- /discover/src/jvmMain/kotlin/inkapplications/shade/discover/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import io.ktor.client.engine.* 4 | import io.ktor.client.engine.okhttp.* 5 | 6 | internal actual class PlatformModule actual constructor() { 7 | actual fun createEngine(): HttpClientEngineFactory<*> = OkHttp 8 | } 9 | -------------------------------------------------------------------------------- /discover/src/nativeMain/kotlin/inkapplications/shade/discover/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import io.ktor.client.engine.* 4 | import io.ktor.client.engine.cio.* 5 | 6 | internal actual class PlatformModule actual constructor() { 7 | actual fun createEngine(): HttpClientEngineFactory<*> = CIO 8 | } 9 | -------------------------------------------------------------------------------- /discover/src/windowsMain/kotlin/inkapplications/shade/discover/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.discover 2 | 3 | import io.ktor.client.engine.* 4 | import io.ktor.client.engine.winhttp.* 5 | 6 | internal actual class PlatformModule actual constructor() { 7 | actual fun createEngine(): HttpClientEngineFactory<*> = WinHttp 8 | } 9 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | shade.lighting -------------------------------------------------------------------------------- /docs/css/atom-one-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Atom One Dark by Daniel Gamage 4 | Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax 5 | 6 | base: #282c34 7 | mono-1: #abb2bf 8 | mono-2: #818896 9 | mono-3: #5c6370 10 | hue-1: #56b6c2 11 | hue-2: #61aeee 12 | hue-3: #c678dd 13 | hue-4: #98c379 14 | hue-5: #e06c75 15 | hue-5-2: #be5046 16 | hue-6: #d19a66 17 | hue-6-2: #e6c07b 18 | 19 | */ 20 | 21 | .hljs { 22 | display: block; 23 | overflow-x: auto; 24 | padding: 0.5em; 25 | color: #abb2bf; 26 | background: #282c34; 27 | } 28 | 29 | .hljs-comment, 30 | .hljs-quote { 31 | color: #5c6370; 32 | font-style: italic; 33 | } 34 | 35 | .hljs-doctag, 36 | .hljs-keyword, 37 | .hljs-formula { 38 | color: #c678dd; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name, 43 | .hljs-selector-tag, 44 | .hljs-deletion, 45 | .hljs-subst { 46 | color: #e06c75; 47 | } 48 | 49 | .hljs-literal { 50 | color: #56b6c2; 51 | } 52 | 53 | .hljs-string, 54 | .hljs-regexp, 55 | .hljs-addition, 56 | .hljs-attribute, 57 | .hljs-meta-string { 58 | color: #98c379; 59 | } 60 | 61 | .hljs-built_in, 62 | .hljs-class .hljs-title { 63 | color: #e6c07b; 64 | } 65 | 66 | .hljs-attr, 67 | .hljs-variable, 68 | .hljs-template-variable, 69 | .hljs-type, 70 | .hljs-selector-class, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-number { 74 | color: #d19a66; 75 | } 76 | 77 | .hljs-symbol, 78 | .hljs-bullet, 79 | .hljs-link, 80 | .hljs-meta, 81 | .hljs-selector-id, 82 | .hljs-title { 83 | color: #61aeee; 84 | } 85 | 86 | .hljs-emphasis { 87 | font-style: italic; 88 | } 89 | 90 | .hljs-strong { 91 | font-weight: bold; 92 | } 93 | 94 | .hljs-link { 95 | text-decoration: underline; 96 | } 97 | -------------------------------------------------------------------------------- /docs/css/atom-one-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Atom One Light by Daniel Gamage 4 | Original One Light Syntax theme from https://github.com/atom/one-light-syntax 5 | 6 | base: #fafafa 7 | mono-1: #383a42 8 | mono-2: #686b77 9 | mono-3: #a0a1a7 10 | hue-1: #0184bb 11 | hue-2: #4078f2 12 | hue-3: #a626a4 13 | hue-4: #50a14f 14 | hue-5: #e45649 15 | hue-5-2: #c91243 16 | hue-6: #986801 17 | hue-6-2: #c18401 18 | 19 | */ 20 | 21 | .hljs { 22 | display: block; 23 | overflow-x: auto; 24 | padding: 0.5em; 25 | color: #383a42; 26 | background: #fafafa; 27 | } 28 | 29 | .hljs-comment, 30 | .hljs-quote { 31 | color: #a0a1a7; 32 | font-style: italic; 33 | } 34 | 35 | .hljs-doctag, 36 | .hljs-keyword, 37 | .hljs-formula { 38 | color: #a626a4; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name, 43 | .hljs-selector-tag, 44 | .hljs-deletion, 45 | .hljs-subst { 46 | color: #e45649; 47 | } 48 | 49 | .hljs-literal { 50 | color: #0184bb; 51 | } 52 | 53 | .hljs-string, 54 | .hljs-regexp, 55 | .hljs-addition, 56 | .hljs-attribute, 57 | .hljs-meta-string { 58 | color: #50a14f; 59 | } 60 | 61 | .hljs-built_in, 62 | .hljs-class .hljs-title { 63 | color: #c18401; 64 | } 65 | 66 | .hljs-attr, 67 | .hljs-variable, 68 | .hljs-template-variable, 69 | .hljs-type, 70 | .hljs-selector-class, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-number { 74 | color: #986801; 75 | } 76 | 77 | .hljs-symbol, 78 | .hljs-bullet, 79 | .hljs-link, 80 | .hljs-meta, 81 | .hljs-selector-id, 82 | .hljs-title { 83 | color: #4078f2; 84 | } 85 | 86 | .hljs-emphasis { 87 | font-style: italic; 88 | } 89 | 90 | .hljs-strong { 91 | font-weight: bold; 92 | } 93 | 94 | .hljs-link { 95 | text-decoration: underline; 96 | } 97 | -------------------------------------------------------------------------------- /docs/css/dark-1.1.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: #212121; 3 | color: #fff; 4 | } 5 | h1, 6 | h2, 7 | h3 8 | { 9 | color: #fff; 10 | } 11 | 12 | a, 13 | a:visited, 14 | a:hover 15 | { 16 | color: #fff; 17 | } 18 | 19 | code, 20 | .hljs { 21 | background-color: #292929; 22 | } 23 | 24 | .only-light { 25 | display: none; 26 | } 27 | .only-dark { 28 | display: initial; 29 | } 30 | 31 | .hero > article { 32 | background-color: #292929; 33 | } 34 | -------------------------------------------------------------------------------- /docs/css/main-2.0.css: -------------------------------------------------------------------------------- 1 | @import url("https://assets.inkapplications.com/css/main-v1.css"); 2 | @import url("https://fonts.googleapis.com/css?family=Alegreya+Sans+SC:400"); 3 | 4 | :root 5 | { 6 | --color-accent: #ec6238; 7 | } 8 | 9 | .logo 10 | { 11 | margin: 0 auto; 12 | height: 4rem; 13 | display: block; 14 | } 15 | 16 | section 17 | { 18 | border: 1px solid transparent; 19 | } 20 | section:has(header > a:target) 21 | { 22 | border: 1px solid var(--color-accent); 23 | } 24 | 25 | h1, 26 | h2, 27 | h3 28 | { 29 | font-family: 'Alegreya Sans SC', monospace; 30 | font-weight: 400; 31 | } 32 | 33 | section > *:last-child 34 | { 35 | margin-bottom: 0; 36 | } 37 | 38 | section nav ul 39 | { 40 | margin: 1.5rem 0; 41 | } 42 | 43 | section nav ul > li 44 | { 45 | margin: .75rem 0; 46 | } 47 | 48 | section nav ul > li::before 49 | { 50 | content: none; 51 | } 52 | 53 | section nav a:not(.button):after 54 | { 55 | content: ' >'; 56 | } 57 | 58 | 59 | @media not (prefers-color-scheme: dark) { 60 | .logo.only-darkmode 61 | { 62 | display: none; 63 | } 64 | } 65 | @media (prefers-color-scheme: dark) { 66 | .logo.only-lightmode 67 | { 68 | display: none; 69 | } 70 | } 71 | 72 | .button 73 | { 74 | display: inline-block; 75 | } 76 | -------------------------------------------------------------------------------- /docs/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | -------------------------------------------------------------------------------- /docs/reference/latest/images/anchor-copy-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/reference/latest/images/arrow_down.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/latest/images/burger.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/reference/latest/images/copy-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/images/copy-successful-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/images/footer-go-to-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/images/go-to-top-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/reference/latest/images/homepage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/images/logo-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/abstract-class.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/class-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/class.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/enum-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/enum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/exception-class.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/field-value.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/field-variable.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/function.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/interface-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/interface.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/object.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/images/nav-icons/typealias-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/images/theme-toggle.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/latest/package-list: -------------------------------------------------------------------------------- 1 | $dokka.format:html-v1 2 | $dokka.linkExtension:html 3 | 4 | module:auth 5 | inkapplications.shade.auth 6 | inkapplications.shade.auth.structures 7 | module:core 8 | inkapplications.shade.core 9 | module:devices 10 | inkapplications.shade.devices 11 | inkapplications.shade.devices.parameters 12 | inkapplications.shade.devices.structures 13 | module:discover 14 | inkapplications.shade.discover 15 | inkapplications.shade.discover.structures 16 | module:events 17 | inkapplications.shade.events 18 | shade.events 19 | module:grouped-lights 20 | inkapplications.shade.groupedlights 21 | inkapplications.shade.groupedlights.events 22 | inkapplications.shade.groupedlights.parameters 23 | inkapplications.shade.groupedlights.structures 24 | module:internals 25 | inkapplications.shade.internals 26 | module:lights 27 | inkapplications.shade.lights 28 | inkapplications.shade.lights.events 29 | inkapplications.shade.lights.parameters 30 | inkapplications.shade.lights.structures 31 | module:resources 32 | inkapplications.shade.resources 33 | inkapplications.shade.resources.structures 34 | module:rooms 35 | inkapplications.shade.rooms 36 | inkapplications.shade.rooms.parameters 37 | inkapplications.shade.rooms.structures 38 | module:scenes 39 | inkapplications.shade.scenes 40 | inkapplications.shade.scenes.parameters 41 | inkapplications.shade.scenes.structures 42 | module:serialization 43 | inkapplications.shade.serialization 44 | module:structures 45 | inkapplications.shade.structures 46 | inkapplications.shade.structures.parameters 47 | module:zones 48 | inkapplications.shade.zones 49 | inkapplications.shade.zones.parameters 50 | inkapplications.shade.zones.structures 51 | -------------------------------------------------------------------------------- /docs/reference/latest/styles/jetbrains-mono.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'JetBrains Mono'; 3 | src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/web/JetBrainsMono-Regular.eot') format('embedded-opentype'), 4 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Regular.woff2') format('woff2'), 5 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Regular.ttf') format('truetype'); 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | @font-face{ 11 | font-family: 'JetBrains Mono'; 12 | src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/web/JetBrainsMono-Bold.eot') format('embedded-opentype'), 13 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Bold.woff2') format('woff2'), 14 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Bold.ttf') format('truetype'); 15 | font-weight: bold; 16 | font-style: bold; 17 | } -------------------------------------------------------------------------------- /docs/reference/latest/styles/logo-styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | :root { 6 | --dokka-logo-image-url: url('../images/logo-icon.svg'); 7 | --dokka-logo-height: 28px; 8 | --dokka-logo-width: 28px; 9 | } 10 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/abstract-class.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/burger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/checkbox-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/checkbox-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/class-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/class.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/enum-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/enum.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/exception-class.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/field-value.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/field-variable.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/function.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/homepage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/interface-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/interface.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/object.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/theme-toggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/reference/latest/ui-kit/assets/typealias-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /events/api/events.api: -------------------------------------------------------------------------------- 1 | public abstract interface class inkapplications/shade/events/EventSerializerContainer { 2 | public abstract fun setDeserializer (Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;)V 3 | } 4 | 5 | public abstract interface class inkapplications/shade/events/Events { 6 | public abstract fun bridgeEvents ()Lkotlinx/coroutines/flow/Flow; 7 | } 8 | 9 | public final class inkapplications/shade/events/EventsModule { 10 | public fun (Linkapplications/shade/internals/InternalsModule;Lkimchi/logger/KimchiLogger;)V 11 | public final fun getEventSerializerContainer ()Linkapplications/shade/events/EventSerializerContainer; 12 | } 13 | 14 | public final class shade/events/EventsModuleKt { 15 | public static final fun getEvents (Linkapplications/shade/events/EventsModule;)Linkapplications/shade/events/Events; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /events/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | 16 | api(libs.coroutines.core) 17 | } 18 | } 19 | 20 | val commonTest by getting { 21 | dependencies { 22 | implementation(libs.test.core) 23 | implementation(libs.test.annotations) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /events/src/commonMain/kotlin/inkapplications/shade/events/EventSerializerContainer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.events 2 | 3 | import kotlinx.serialization.DeserializationStrategy 4 | 5 | /** 6 | * Stores the serializers used for deserializing event stream data. 7 | */ 8 | interface EventSerializerContainer { 9 | /** 10 | * Register a deserializer for a specific event type. 11 | * 12 | * @param type The key used to designate this type on the json object. 13 | * @param deserializer Deserializer used for events of [type] 14 | */ 15 | fun setDeserializer(type: String, deserializer: DeserializationStrategy) 16 | } 17 | -------------------------------------------------------------------------------- /events/src/commonMain/kotlin/inkapplications/shade/events/Events.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.events 2 | 3 | import inkapplications.shade.structures.UndocumentedApi 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | /** 7 | * Event streams available from the shade client. 8 | */ 9 | @UndocumentedApi 10 | interface Events { 11 | /** 12 | * Events emitted by the hue bridge. 13 | */ 14 | fun bridgeEvents(): Flow> 15 | } 16 | -------------------------------------------------------------------------------- /events/src/commonMain/kotlin/inkapplications/shade/events/EventsModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.events 2 | 3 | import inkapplications.shade.internals.InternalsModule 4 | import kimchi.logger.KimchiLogger 5 | 6 | class EventsModule( 7 | internal val internalsModule: InternalsModule, 8 | private val logger: KimchiLogger, 9 | ) { 10 | internal val eventDeserializer = CompositeEventDeserializer(internalsModule.json, logger) 11 | /** 12 | * Provides access to the container that configures SSE serialization 13 | * for the event stream. 14 | */ 15 | val eventSerializerContainer: EventSerializerContainer = eventDeserializer 16 | } 17 | -------------------------------------------------------------------------------- /events/src/jvmMain/kotlin/shade/events/EventsModule.kt: -------------------------------------------------------------------------------- 1 | package shade.events 2 | 3 | import inkapplications.shade.events.Events 4 | import inkapplications.shade.events.EventsModule 5 | import inkapplications.shade.structures.UndocumentedApi 6 | 7 | @UndocumentedApi 8 | val EventsModule.events: Events 9 | get() = ShadeEvents( 10 | sseClient = internalsModule.platformModule.sseClient, 11 | eventDeserializer = eventDeserializer, 12 | ) 13 | -------------------------------------------------------------------------------- /events/src/jvmMain/kotlin/shade/events/ShadeEvents.kt: -------------------------------------------------------------------------------- 1 | package shade.events 2 | 3 | import inkapplications.shade.events.Events 4 | import inkapplications.shade.internals.SseClient 5 | import inkapplications.shade.structures.UndocumentedApi 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.serialization.DeserializationStrategy 8 | 9 | @UndocumentedApi 10 | internal class ShadeEvents( 11 | private val sseClient: SseClient, 12 | private val eventDeserializer: DeserializationStrategy>, 13 | ): Events { 14 | override fun bridgeEvents(): Flow> { 15 | return sseClient.openSse( 16 | pathSegments = arrayOf("eventstream", "clip", "v2"), 17 | dataDeserializer = eventDeserializer, 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=2.0-SNAPSHOT 2 | group=com.inkapplications.shade 3 | org.gradle.jvmargs=-Xmx4g 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InkApplications/Shade/cdd75cd3fb2dc1050aab115b243741040745e53b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /grouped-lights/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | api(projects.lights) 16 | 17 | api(libs.coroutines.core) 18 | } 19 | } 20 | 21 | val commonTest by getting { 22 | dependencies { 23 | implementation(libs.test.core) 24 | implementation(libs.test.annotations) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /grouped-lights/src/commonMain/kotlin/inkapplications/shade/groupedlights/GroupedLightControls.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.groupedlights 2 | 3 | import inkapplications.shade.groupedlights.parameters.GroupedLightUpdateParameters 4 | import inkapplications.shade.groupedlights.structures.GroupedLight 5 | import inkapplications.shade.structures.ResourceId 6 | import inkapplications.shade.structures.ResourceReference 7 | 8 | /** 9 | * Actions to get and control grouped lights. 10 | */ 11 | interface GroupedLightControls { 12 | /** 13 | * Get a list of the grouped lights configured on the Hue bridge 14 | * 15 | * @return A list of all grouped lights configured on the bridge. 16 | */ 17 | suspend fun listGroups(): List 18 | 19 | /** 20 | * Get details about a single light group by ID. 21 | * 22 | * @param id The v2 unique id of the grouped light to fetch information for. 23 | * @return State and capability information about the grouped light. 24 | */ 25 | suspend fun getGroup(id: ResourceId): GroupedLight 26 | 27 | /** 28 | * Update the state or details of a grouped light 29 | * 30 | * @param id The v2 unique ID of the grouped light to be updated. 31 | * @return a Reference to the updated grouped light 32 | */ 33 | suspend fun updateGroup(id: ResourceId, parameters: GroupedLightUpdateParameters): ResourceReference 34 | } 35 | -------------------------------------------------------------------------------- /grouped-lights/src/commonMain/kotlin/inkapplications/shade/groupedlights/ShadeGroupedLights.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.groupedlights 2 | 3 | import inkapplications.shade.groupedlights.parameters.GroupedLightUpdateParameters 4 | import inkapplications.shade.groupedlights.structures.GroupedLight 5 | import inkapplications.shade.internals.HueHttpClient 6 | import inkapplications.shade.internals.getData 7 | import inkapplications.shade.internals.putData 8 | import inkapplications.shade.structures.ResourceId 9 | import inkapplications.shade.structures.ResourceReference 10 | 11 | internal class ShadeGroupedLights( 12 | private val hueHttpClient: HueHttpClient, 13 | ): GroupedLightControls { 14 | override suspend fun listGroups(): List { 15 | return hueHttpClient.getData("resource", "grouped_light") 16 | } 17 | 18 | override suspend fun getGroup(id: ResourceId): GroupedLight { 19 | return hueHttpClient.getData>("resource", "grouped_light", id.value).single() 20 | } 21 | 22 | override suspend fun updateGroup(id: ResourceId, parameters: GroupedLightUpdateParameters): ResourceReference { 23 | val response: List = hueHttpClient.putData( 24 | body = parameters, 25 | pathSegments = arrayOf("resource", "grouped_light", id.value), 26 | ) 27 | 28 | return response.single() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /grouped-lights/src/commonMain/kotlin/inkapplications/shade/groupedlights/ShadeGroupedLightsModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.groupedlights 2 | 3 | import inkapplications.shade.events.EventsModule 4 | import inkapplications.shade.groupedlights.events.GroupedLightEvent 5 | import inkapplications.shade.internals.InternalsModule 6 | import inkapplications.shade.lights.events.LightEvent 7 | import inkapplications.shade.structures.UndocumentedApi 8 | 9 | /** 10 | * Provides Access to grouped light services. 11 | */ 12 | @OptIn(UndocumentedApi::class) 13 | class ShadeGroupedLightsModule( 14 | internalsModule: InternalsModule, 15 | eventsModule: EventsModule, 16 | ) { 17 | val groupedLights: GroupedLightControls = ShadeGroupedLights(internalsModule.hueHttpClient) 18 | 19 | init { 20 | eventsModule.eventSerializerContainer.setDeserializer("grouped_light", GroupedLightEvent.serializer()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /grouped-lights/src/commonMain/kotlin/inkapplications/shade/groupedlights/events/GroupedLightEvent.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.groupedlights.events 2 | 3 | import inkapplications.shade.lights.structures.AlertInfo 4 | import inkapplications.shade.lights.structures.DimmingInfo 5 | import inkapplications.shade.structures.PowerInfo 6 | import inkapplications.shade.structures.ResourceId 7 | import inkapplications.shade.structures.ResourceReference 8 | import inkapplications.shade.structures.UndocumentedApi 9 | import kotlinx.serialization.SerialName 10 | import kotlinx.serialization.Serializable 11 | 12 | /** 13 | * Data sent for a light group event. 14 | */ 15 | @Serializable 16 | @UndocumentedApi 17 | data class GroupedLightEvent( 18 | /** 19 | * The V2 unique identifier for the group. 20 | */ 21 | val id: ResourceId, 22 | 23 | /** 24 | * Owner of the service 25 | * 26 | * In case the owner service is deleted, the service also gets deleted. 27 | */ 28 | val owner: ResourceReference, 29 | 30 | /** 31 | * Information about the power-state of the light group. 32 | */ 33 | @SerialName("on") 34 | val powerInfo: PowerInfo? = null, 35 | 36 | /** 37 | * Information about the group's dimming, if supported. 38 | */ 39 | @SerialName("dimming") 40 | val dimmingInfo: DimmingInfo? = null, 41 | 42 | /** 43 | * Joined alert control for the light group. 44 | */ 45 | @SerialName("alert") 46 | val alertInfo: AlertInfo? = null, 47 | ) 48 | -------------------------------------------------------------------------------- /grouped-lights/src/commonMain/kotlin/inkapplications/shade/groupedlights/structures/GroupDimmingInfo.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.groupedlights.structures 2 | 3 | import inkapplications.shade.serialization.WholePercentageSerializer 4 | import inkapplications.spondee.scalar.Percentage 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Info about a light group's dimming status and capabilities. 9 | */ 10 | @Serializable 11 | data class GroupDimmingInfo( 12 | /** 13 | * Current brightness value. 14 | */ 15 | @Serializable(with = WholePercentageSerializer::class) 16 | val brightness: Percentage, 17 | ) 18 | -------------------------------------------------------------------------------- /grouped-lights/src/commonMain/kotlin/inkapplications/shade/groupedlights/structures/GroupedLight.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.groupedlights.structures 2 | 3 | import inkapplications.shade.lights.structures.AlertInfo 4 | import inkapplications.shade.structures.PowerInfo 5 | import inkapplications.shade.structures.ResourceId 6 | import inkapplications.shade.structures.ResourceReference 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * A configured light group for a room, zone or home. 12 | */ 13 | @Serializable 14 | data class GroupedLight( 15 | /** 16 | * The V2 unique identifier for the group. 17 | */ 18 | val id: ResourceId, 19 | 20 | /** 21 | * Owner of the service 22 | * 23 | * In case the owner service is deleted, the service also gets deleted. 24 | */ 25 | val owner: ResourceReference, 26 | 27 | /** 28 | * Information about the power-state of the light group. 29 | */ 30 | @SerialName("on") 31 | val powerInfo: PowerInfo? = null, 32 | 33 | /** 34 | * Information about the group's dimming, if supported. 35 | */ 36 | @SerialName("dimming") 37 | val dimmingInfo: GroupDimmingInfo? = null, 38 | 39 | /** 40 | * Joined alert control for the light group. 41 | */ 42 | @SerialName("alert") 43 | val alertInfo: AlertInfo? = null, 44 | ) 45 | -------------------------------------------------------------------------------- /internals/src/commonMain/kotlin/inkapplications/shade/internals/BaseUrl.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | /** 4 | * Builds the base path of API requests 5 | */ 6 | internal object BaseUrl { 7 | private val API_V2 = arrayOf("clip", "v2") 8 | private val API_V1 = arrayOf("api") 9 | 10 | /** 11 | * Build a V1 API Request's base-url 12 | * 13 | * @param pathSegments The path segments in the request to add after the base-url 14 | */ 15 | fun v1(vararg pathSegments: String): Array = API_V1 + pathSegments 16 | 17 | /** 18 | * Build a V2 API Request's base-url 19 | * 20 | * @param pathSegments The path segments in the request to add after the base-url 21 | */ 22 | fun v2(vararg pathSegments: String): Array = API_V2 + pathSegments 23 | } 24 | -------------------------------------------------------------------------------- /internals/src/commonMain/kotlin/inkapplications/shade/internals/CachedProperty.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | import kotlin.properties.ReadOnlyProperty 5 | import kotlin.reflect.KProperty 6 | 7 | /** 8 | * Caches a property value until a key changes. 9 | * 10 | * @param keyProperty Function that provides the latest value of the cache key. 11 | * @param lazy Whether to initialize the value immediately, or upon first invocation. 12 | * @param factory Function to create the property value based on its current cache key. 13 | */ 14 | class CachedProperty( 15 | val keyProperty: () -> KEY, 16 | lazy: Boolean = false, 17 | val factory: (KEY) -> VALUE, 18 | ): ReadOnlyProperty { 19 | private sealed interface State { 20 | class Null: State 21 | data class Set(val key: Any, val value: T): State 22 | } 23 | 24 | private val defaultState: State = if (lazy) State.Null() else create() 25 | private val cache: MutableStateFlow> = MutableStateFlow(defaultState) 26 | 27 | override fun getValue(thisRef: Any?, property: KProperty<*>): VALUE { 28 | return cache.value.let { currentState -> 29 | when { 30 | currentState is State.Set && currentState.key == keyProperty() -> currentState 31 | else -> create().also { cache.value = it } 32 | } 33 | }.value 34 | } 35 | 36 | private fun create(): State.Set = keyProperty().let { newKey -> 37 | State.Set(newKey, factory(newKey)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internals/src/commonMain/kotlin/inkapplications/shade/internals/DummyConfigurationContainer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import inkapplications.shade.structures.AuthToken 4 | import inkapplications.shade.structures.HueConfigurationContainer 5 | import inkapplications.shade.structures.SecurityStrategy 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.StateFlow 8 | 9 | /** 10 | * An empty implementation of the configuration container. 11 | * 12 | * This can be useful either for testing or for scenarios where you do not 13 | * want the configurations to be saved. 14 | */ 15 | object DummyConfigurationContainer: HueConfigurationContainer { 16 | override val hostname: StateFlow = MutableStateFlow(null) 17 | override val securityStrategy: StateFlow = MutableStateFlow(SecurityStrategy.PlatformTrust) 18 | override val authToken: StateFlow = MutableStateFlow(null) 19 | override suspend fun setHostname(hostname: String?) = Unit 20 | override suspend fun setAuthToken(token: AuthToken?) = Unit 21 | override suspend fun setSecurityStrategy(securityStrategy: SecurityStrategy) = Unit 22 | } 23 | -------------------------------------------------------------------------------- /internals/src/commonMain/kotlin/inkapplications/shade/internals/HueStubClient.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import inkapplications.shade.serialization.HueResponse 4 | import inkapplications.shade.serialization.V1HueResponse 5 | import kotlinx.serialization.KSerializer 6 | 7 | /** 8 | * A stubbed out client used for testing delegation 9 | */ 10 | object HueStubClient: HueHttpClient { 11 | override suspend fun sendRequest( 12 | method: String, 13 | pathSegments: Array, 14 | responseSerializer: KSerializer>, 15 | body: REQUEST?, 16 | requestSerializer: KSerializer? 17 | ): RESPONSE = TODO("Not yet implemented") 18 | 19 | override suspend fun sendV1Request( 20 | method: String, 21 | pathSegments: Array, 22 | responseSerializer: KSerializer>>, 23 | body: REQUEST?, 24 | requestSerializer: KSerializer? 25 | ): RESPONSE = TODO("Not yet implemented") 26 | } 27 | -------------------------------------------------------------------------------- /internals/src/commonMain/kotlin/inkapplications/shade/internals/InternalsModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import inkapplications.shade.structures.HueConfigurationContainer 4 | import kimchi.logger.EmptyLogger 5 | import kimchi.logger.KimchiLogger 6 | import kotlinx.serialization.json.Json 7 | 8 | /** 9 | * Provides access to services in the internals module. 10 | * 11 | * @param configurationContainer Container for storing Host/Token information on the API. 12 | * @param logger Logger used in network and internal operations 13 | */ 14 | class InternalsModule( 15 | val configurationContainer: HueConfigurationContainer, 16 | logger: KimchiLogger = EmptyLogger, 17 | ) { 18 | val json = Json { 19 | ignoreUnknownKeys = true 20 | } 21 | val platformModule = PlatformModule(configurationContainer, json, logger) 22 | private val configurableHttpClient = ConfigurableHttpClient( 23 | platformModule = platformModule, 24 | configurationContainer = configurationContainer, 25 | json = json, 26 | logger = logger, 27 | ) 28 | 29 | /** 30 | * HttpClient initialized to be configured by the [configurationContainer]. 31 | */ 32 | val hueHttpClient: HueHttpClient = configurableHttpClient 33 | } 34 | -------------------------------------------------------------------------------- /internals/src/commonMain/kotlin/inkapplications/shade/internals/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import inkapplications.shade.structures.HueConfigurationContainer 4 | import inkapplications.shade.structures.SecurityStrategy 5 | import io.ktor.client.engine.* 6 | import kimchi.logger.EmptyLogger 7 | import kimchi.logger.KimchiLogger 8 | import kotlinx.serialization.json.Json 9 | 10 | /** 11 | * Provides platform-specific dependencies for the SDK. 12 | */ 13 | expect class PlatformModule( 14 | configurationContainer: HueConfigurationContainer, 15 | json: Json, 16 | logger: KimchiLogger = EmptyLogger, 17 | ) { 18 | /** 19 | * Create Ktor http engine based on the platform. 20 | */ 21 | fun createEngine(securityStrategy: SecurityStrategy): HttpClientEngineFactory<*> 22 | } 23 | -------------------------------------------------------------------------------- /internals/src/commonMain/kotlin/inkapplications/shade/internals/SseClient.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.serialization.DeserializationStrategy 5 | 6 | /** 7 | * Internal client used for listening to event streams from the Hue bridge. 8 | */ 9 | interface SseClient { 10 | /** 11 | * Open a server-side-event stream. 12 | * 13 | * @param pathSegments The URL path to the event stream to listen to. 14 | * @param dataDeserializer deserializer to use for all events emitted. 15 | */ 16 | fun openSse( 17 | pathSegments: Array, 18 | dataDeserializer: DeserializationStrategy, 19 | ): Flow 20 | } 21 | 22 | -------------------------------------------------------------------------------- /internals/src/iosMain/kotlin/inkapplications/shade/internals/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import inkapplications.shade.structures.HueConfigurationContainer 4 | import inkapplications.shade.structures.SecurityStrategy 5 | import io.ktor.client.engine.* 6 | import io.ktor.client.engine.darwin.* 7 | import kimchi.logger.KimchiLogger 8 | import kotlinx.serialization.json.Json 9 | 10 | actual class PlatformModule actual constructor( 11 | configurationContainer: HueConfigurationContainer, 12 | json: Json, 13 | logger: KimchiLogger 14 | ) { 15 | actual fun createEngine(securityStrategy: SecurityStrategy): HttpClientEngineFactory<*> { 16 | if (securityStrategy !is SecurityStrategy.PlatformTrust) { 17 | throw IllegalArgumentException("iOS client cannot change security settings and must rely on the platform's trust.") 18 | } 19 | 20 | return Darwin 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internals/src/jsMain/kotlin/inkapplications/shade/internals/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import inkapplications.shade.structures.HueConfigurationContainer 4 | import inkapplications.shade.structures.SecurityStrategy 5 | import io.ktor.client.engine.* 6 | import io.ktor.client.engine.js.* 7 | import kimchi.logger.KimchiLogger 8 | import kotlinx.serialization.json.Json 9 | 10 | actual class PlatformModule actual constructor( 11 | configurationContainer: HueConfigurationContainer, 12 | json: Json, 13 | logger: KimchiLogger 14 | ) { 15 | actual fun createEngine(securityStrategy: SecurityStrategy): HttpClientEngineFactory<*> { 16 | if (securityStrategy !is SecurityStrategy.PlatformTrust) { 17 | throw IllegalArgumentException("Javascript client cannot change security settings and must rely on the platform's trust.") 18 | } 19 | 20 | return Js 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internals/src/nativeMain/kotlin/inkapplications/shade/internals/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import inkapplications.shade.structures.HueConfigurationContainer 4 | import inkapplications.shade.structures.SecurityStrategy 5 | import io.ktor.client.engine.* 6 | import io.ktor.client.engine.cio.* 7 | import kimchi.logger.KimchiLogger 8 | import kotlinx.serialization.json.Json 9 | 10 | actual class PlatformModule actual constructor( 11 | configurationContainer: HueConfigurationContainer, 12 | json: Json, 13 | logger: KimchiLogger 14 | ) { 15 | actual fun createEngine(securityStrategy: SecurityStrategy): HttpClientEngineFactory<*> { 16 | if (securityStrategy !is SecurityStrategy.PlatformTrust) { 17 | throw IllegalArgumentException("Native client cannot change security settings and must rely on the platform's trust.") 18 | } 19 | 20 | return CIO 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internals/src/windowsMain/kotlin/inkapplications/shade/internals/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.internals 2 | 3 | import inkapplications.shade.structures.HueConfigurationContainer 4 | import inkapplications.shade.structures.SecurityStrategy 5 | import io.ktor.client.engine.* 6 | import io.ktor.client.engine.winhttp.* 7 | import kimchi.logger.KimchiLogger 8 | import kotlinx.serialization.json.Json 9 | 10 | actual class PlatformModule actual constructor( 11 | configurationContainer: HueConfigurationContainer, 12 | json: Json, 13 | logger: KimchiLogger 14 | ) { 15 | actual fun createEngine(securityStrategy: SecurityStrategy): HttpClientEngineFactory<*> { 16 | if (securityStrategy !is SecurityStrategy.PlatformTrust) { 17 | throw IllegalArgumentException("Windows client cannot change security settings and must rely on the platform's trust.") 18 | } 19 | 20 | return WinHttp 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lights/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | api(projects.events) 16 | 17 | api(libs.coroutines.core) 18 | } 19 | } 20 | 21 | val commonTest by getting { 22 | dependencies { 23 | implementation(libs.test.core) 24 | implementation(libs.test.annotations) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/LightControls.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights 2 | 3 | import inkapplications.shade.lights.parameters.LightUpdateParameters 4 | import inkapplications.shade.lights.structures.Light 5 | import inkapplications.shade.structures.ResourceId 6 | import inkapplications.shade.structures.ResourceReference 7 | 8 | /** 9 | * Actions to get and control lighting state. 10 | */ 11 | interface LightControls { 12 | /** 13 | * Get the state of a single light 14 | * 15 | * @param id The v2 unique ID of the light to fetch information for. 16 | */ 17 | suspend fun getLight(id: ResourceId): Light 18 | 19 | /** 20 | * Get a list of all lights connected to the bridge 21 | */ 22 | suspend fun listLights(): List 23 | 24 | /** 25 | * Update a light's state. 26 | * 27 | * @param id The v2 unique ID of the light to fetch information for. 28 | * @param parameters State to be changed on the light. 29 | * @return A reference to the updated resource. 30 | */ 31 | suspend fun updateLight(id: ResourceId, parameters: LightUpdateParameters): ResourceReference 32 | } 33 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/ShadeLights.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights 2 | 3 | import inkapplications.shade.internals.HueHttpClient 4 | import inkapplications.shade.internals.getData 5 | import inkapplications.shade.internals.putData 6 | import inkapplications.shade.lights.parameters.LightUpdateParameters 7 | import inkapplications.shade.lights.structures.Light 8 | import inkapplications.shade.structures.ResourceId 9 | import inkapplications.shade.structures.ResourceReference 10 | 11 | /** 12 | * Implements lighting controls via the hue client 13 | */ 14 | internal class ShadeLights( 15 | private val hueClient: HueHttpClient, 16 | ): LightControls { 17 | override suspend fun getLight(id: ResourceId): Light { 18 | return hueClient.getData>("resource", "light", id.value).single() 19 | } 20 | 21 | override suspend fun listLights(): List { 22 | return hueClient.getData("resource", "light") 23 | } 24 | 25 | override suspend fun updateLight(id: ResourceId, parameters: LightUpdateParameters): ResourceReference { 26 | val response: List = hueClient.putData( 27 | body = parameters, 28 | pathSegments = arrayOf("resource", "light", id.value), 29 | ) 30 | 31 | return response.single() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/ShadeLightsModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights 2 | 3 | import inkapplications.shade.events.EventsModule 4 | import inkapplications.shade.internals.InternalsModule 5 | import inkapplications.shade.lights.events.LightEvent 6 | import inkapplications.shade.structures.UndocumentedApi 7 | 8 | /** 9 | * Provides Access to Light control services. 10 | */ 11 | @OptIn(UndocumentedApi::class) 12 | class ShadeLightsModule( 13 | internalsModule: InternalsModule, 14 | eventsModule: EventsModule, 15 | ) { 16 | val lights: LightControls = ShadeLights(internalsModule.hueHttpClient) 17 | 18 | init { 19 | eventsModule.eventSerializerContainer.setDeserializer("light", LightEvent.serializer()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/events/ColorInfoEvent.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.events 2 | 3 | import com.github.ajalt.colormath.Color 4 | import inkapplications.shade.lights.structures.Chromaticity 5 | import inkapplications.shade.structures.UndocumentedApi 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | 9 | /** 10 | * Information about a light's color and color capabilities. 11 | */ 12 | @Serializable 13 | @UndocumentedApi 14 | data class ColorInfoEvent( 15 | /** 16 | * Current color of the light 17 | */ 18 | @SerialName("xy") 19 | @Serializable(with = Chromaticity.ColorSerializer::class) 20 | val color: Color, 21 | ) 22 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/events/ColorTemperatureInfoEvent.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.events 2 | 3 | import inkapplications.shade.serialization.MiredSerializer 4 | import inkapplications.shade.structures.UndocumentedApi 5 | import inkapplications.spondee.measure.ColorTemperature 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | 9 | /** 10 | * Information about the bulb's color temperature and capabilities. 11 | */ 12 | @Serializable 13 | @UndocumentedApi 14 | data class ColorTemperatureInfoEvent( 15 | /** 16 | * Current color temperature of the bulb, 17 | * 18 | * This value can be null if the color is not in the ct spectrum 19 | */ 20 | @SerialName("mirek") 21 | @Serializable(with = MiredSerializer::class) 22 | val temperature: ColorTemperature?, 23 | 24 | /** 25 | * Indication whether the value is valid for this hardware. 26 | */ 27 | @SerialName("mirek_valid") 28 | val valid: Boolean, 29 | ) 30 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/events/DimmingInfoEvent.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.events 2 | 3 | import inkapplications.shade.serialization.WholePercentageSerializer 4 | import inkapplications.shade.structures.UndocumentedApi 5 | import inkapplications.spondee.scalar.Percentage 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Info about a light's dimming status and capabilities. 10 | */ 11 | @Serializable 12 | @UndocumentedApi 13 | data class DimmingInfoEvent( 14 | /** 15 | * Current brightness value. 16 | */ 17 | @Serializable(with = WholePercentageSerializer::class) 18 | val brightness: Percentage, 19 | ) 20 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/events/LightEvent.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.events 2 | 3 | import inkapplications.shade.structures.PowerInfo 4 | import inkapplications.shade.structures.ResourceId 5 | import inkapplications.shade.structures.ResourceReference 6 | import inkapplications.shade.structures.UndocumentedApi 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | 10 | /** 11 | * Data sent during a light update on the events API. 12 | */ 13 | @Serializable 14 | @UndocumentedApi 15 | data class LightEvent( 16 | /** 17 | * Unique identifier representing a specific resource instance 18 | */ 19 | val id: ResourceId, 20 | 21 | /** 22 | * Owner of the service 23 | * 24 | * In case the owner service is deleted, the service also gets deleted. 25 | */ 26 | val owner: ResourceReference, 27 | 28 | /** 29 | * On/Off state of the light 30 | */ 31 | @SerialName("on") 32 | val powerInfo: PowerInfo? = null, 33 | 34 | /** 35 | * Information about the light's dimming, if supported. 36 | */ 37 | @SerialName("dimming") 38 | val dimmingInfo: DimmingInfoEvent? = null, 39 | 40 | /** 41 | * Information about the color temperature and capabilities of the light. 42 | */ 43 | @SerialName("color_temperature") 44 | val colorTemperatureInfo: ColorTemperatureInfoEvent? = null, 45 | 46 | /** 47 | * Information about the bulb's color and color capabilities. 48 | */ 49 | @SerialName("color") 50 | val colorInfo: ColorInfoEvent? = null, 51 | ) 52 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/AlertParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.lights.structures.AlertEffectType 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Change alert state of the light 8 | */ 9 | @Serializable 10 | data class AlertParameters( 11 | /** 12 | * Alert effect to set on the light. 13 | */ 14 | val action: AlertEffectType, 15 | ) 16 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/ColorParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import com.github.ajalt.colormath.Color 4 | import inkapplications.shade.lights.structures.Chromaticity 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Color setting parameters for a light 10 | */ 11 | @Serializable 12 | data class ColorParameters( 13 | /** 14 | * Color to set the light to 15 | */ 16 | @SerialName("xy") 17 | @Serializable(with = Chromaticity.ColorSerializer::class) 18 | val color: Color? = null, 19 | ) 20 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/ColorTemperatureDeltaParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.serialization.ExplicitMiredSerializer 4 | import inkapplications.spondee.measure.Mireds 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Relative color temperature change for a light 10 | */ 11 | @Serializable 12 | data class ColorTemperatureDeltaParameters( 13 | /** 14 | * The type of delta defined 15 | */ 16 | val action: DeltaAction, 17 | 18 | /** 19 | * Delta of color temperature to be added or removed from the light's 20 | * current state. 21 | */ 22 | @SerialName("mirek_delta") 23 | @Serializable(with = ExplicitMiredSerializer::class) 24 | val temperatureDelta: Mireds? = null, 25 | ) 26 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/ColorTemperatureParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.serialization.MiredSerializer 4 | import inkapplications.spondee.measure.ColorTemperature 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Color temperature setting for a light 10 | */ 11 | @Serializable 12 | data class ColorTemperatureParameters( 13 | /** 14 | * Color temperature to set the light to 15 | */ 16 | @Serializable(with = MiredSerializer::class) 17 | @SerialName("mirek") 18 | val temperature: ColorTemperature? = null, 19 | ) 20 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/DeltaAction.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Defines the type of delta argument provided in a parameter. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class DeltaAction(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val Up = DeltaAction("up") 16 | val Down = DeltaAction("down") 17 | val Stop = DeltaAction("stop") 18 | 19 | @Deprecated("This is an unbounded set of values. The values provided here are not exhaustive and will be removed in a future release.") 20 | fun values(): Array = arrayOf(Up, Down, Stop) 21 | 22 | @Deprecated( 23 | message = "Deprecated in favor of constructor", 24 | replaceWith = ReplaceWith("DeltaAction(key)"), 25 | ) 26 | fun valueOf(key: String) = values().single { it.key == key } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/DimmingDeltaParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.serialization.WholePercentageSerializer 4 | import inkapplications.spondee.scalar.Percentage 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Relative brightness changes for a light. 10 | */ 11 | @Serializable 12 | data class DimmingDeltaParameters( 13 | /** 14 | * Type of delta being defined 15 | */ 16 | val action: DeltaAction, 17 | 18 | /** 19 | * Percentage brightness to be added to the light. 20 | * 21 | * Note that this percentage is relative addition, not multiplicative. 22 | * e.g. Specifying 10% here for light at 50% brightness will result in 23 | * the light being 60% brightness. 24 | */ 25 | @Serializable(with = WholePercentageSerializer::class) 26 | @SerialName("brightness_delta") 27 | val brightnessDelta: Percentage? = null, 28 | ) 29 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/DimmingParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.serialization.WholePercentageSerializer 4 | import inkapplications.spondee.scalar.Percentage 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Brightness changes for a light 9 | */ 10 | @Serializable 11 | data class DimmingParameters( 12 | /** 13 | * Brightness percentage. 14 | * 15 | * Note: This cannot be zero. Specifying zero will use the lowest supported 16 | * brightness by the light. 17 | */ 18 | @Serializable(with = WholePercentageSerializer::class) 19 | val brightness: Percentage, 20 | ) 21 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/DynamicsParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.serialization.FractionalPercentageSerializer 4 | import inkapplications.shade.serialization.MillisecondDurationSerializer 5 | import inkapplications.spondee.scalar.Percentage 6 | import kotlinx.serialization.Serializable 7 | import kotlin.time.Duration 8 | 9 | /** 10 | * Settings for dynamics during a light setting change 11 | */ 12 | @Serializable 13 | data class DynamicsParameters( 14 | /** 15 | * Duration of a light transition or timed effects in ms. 16 | */ 17 | @Serializable(with = MillisecondDurationSerializer::class) 18 | val duration: Duration? = null, 19 | 20 | /** 21 | * speed of dynamic palette or effect. 22 | * 23 | * The speed is valid for the dynamic palette if the status is 24 | * `dynamic_palette` or for the corresponding effect listed in status. 25 | * In case of status `none`, the speed is not valid 26 | */ 27 | @Serializable(with = FractionalPercentageSerializer::class) 28 | val speed: Percentage? = null, 29 | ) 30 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/EffectsParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.lights.structures.Light 4 | import inkapplications.shade.lights.structures.LightEffect 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Basic feature containing effect properties. 9 | */ 10 | @Serializable 11 | data class EffectsParameters( 12 | /** 13 | * Effect to set the light to. 14 | * 15 | * Note: this should be an effect supported by the light. The list of 16 | * supported effects is in the [Light.effects] property. 17 | */ 18 | val effect: LightEffect? = null, 19 | ) 20 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/GradientParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.lights.structures.GradientPoint 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Basic feature containing gradient properties. 8 | */ 9 | @Serializable 10 | data class GradientParameters( 11 | /** 12 | * Collection of gradients points. 13 | * 14 | * Note: minimum of 2 points need to be provided. 15 | */ 16 | val points: List, 17 | ) { 18 | init { 19 | if (points.size < 2) throw IllegalArgumentException("Gradient must contain at least two points") 20 | if (points.size > 5) throw IllegalArgumentException("Gradient cannot contain more than 5 points") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/parameters/TimedEffectsParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.parameters 2 | 3 | import inkapplications.shade.lights.structures.Light 4 | import inkapplications.shade.lights.structures.TimedLightEffect 5 | import inkapplications.shade.serialization.MillisecondDurationSerializer 6 | import kotlinx.serialization.Serializable 7 | import kotlin.time.Duration 8 | 9 | /** 10 | * Basic feature containing timed effect properties. 11 | */ 12 | @Serializable 13 | data class TimedEffectsParameters( 14 | /** 15 | * Effect to set the light to. 16 | * 17 | * Note: this should be an effect supported by the light. The list of 18 | * supported effects is in the [Light.timedEffects] property. 19 | */ 20 | val effect: TimedLightEffect? = null, 21 | @Serializable(with = MillisecondDurationSerializer::class) 22 | val duration: Duration? = null, 23 | ) 24 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/AlertEffectType.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Wraps alert effects for lighting 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class AlertEffectType(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val Breathe = AlertEffectType("breathe") 16 | 17 | @Deprecated("This is an unbounded set of values. The values provided here are not exhaustive and will be removed in a future release.") 18 | fun values(): Array = arrayOf(Breathe) 19 | 20 | @Deprecated( 21 | message = "Deprecated in favor of constructor", 22 | replaceWith = ReplaceWith("AlertEffectType(key)"), 23 | ) 24 | fun valueOf(key: String) = values().single { it.key == key } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/AlertInfo.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Information on Alert effects for a light 8 | */ 9 | @Serializable 10 | data class AlertInfo( 11 | /** 12 | * Alert effects that the light supports. 13 | */ 14 | @SerialName("action_values") 15 | val actionValues: List 16 | ) 17 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/Chromaticity.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import com.github.ajalt.colormath.Color 4 | import com.github.ajalt.colormath.model.XYZ 5 | import com.github.ajalt.colormath.model.xyY 6 | import inkapplications.shade.serialization.DelegateSerializer 7 | import kotlinx.serialization.Serializable 8 | 9 | /** 10 | * X/Y chromaticity to represent a color 11 | */ 12 | @Serializable 13 | internal data class Chromaticity( 14 | val x: Float, 15 | val y: Float, 16 | ) { 17 | val xyY: xyY get() = xyY(x, y) 18 | 19 | /** 20 | * Serialize an xyY color as an X-Y chromaticity value 21 | */ 22 | internal object CieChromaticitySerializer: DelegateSerializer(serializer()) { 23 | override fun serialize(data: xyY): Chromaticity = Chromaticity(x = data.x, y = data.y) 24 | override fun deserialize(data: Chromaticity): xyY = xyY(x = data.x, y = data.y) 25 | } 26 | 27 | /** 28 | * Serialize a color as an X-Y chromaticity value 29 | */ 30 | internal object ColorSerializer: DelegateSerializer(serializer()) { 31 | override fun serialize(data: Color): Chromaticity { 32 | return Chromaticity( 33 | x = data.toXYZ().toCIExyY().x, 34 | y = data.toXYZ().toCIExyY().y, 35 | ) 36 | } 37 | 38 | override fun deserialize(data: Chromaticity): Color { 39 | val chromaticity = xyY(data.x, data.y) 40 | 41 | return XYZ.invoke(chromaticity.X, chromaticity.Y, chromaticity.Z) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/ColorInfo.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import com.github.ajalt.colormath.Color 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Information about a light's color and color capabilities. 9 | */ 10 | @Serializable 11 | data class ColorInfo( 12 | /** 13 | * Current color of the light 14 | */ 15 | @SerialName("xy") 16 | @Serializable(with = Chromaticity.ColorSerializer::class) 17 | val color: Color, 18 | 19 | /** 20 | * Simple Hue gamut type 21 | */ 22 | @SerialName("gamut_type") 23 | val gamutType: GamutType, 24 | 25 | /** 26 | * Color gamut of color bulb. Some bulbs do not properly return the Gamut information. In this case this is not present. 27 | */ 28 | @Serializable 29 | val gamut: Gamut? = null, 30 | ) 31 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/ColorPalette.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Color/brightness pair reference. 7 | */ 8 | @Serializable 9 | data class ColorPalette( 10 | val color: ColorValue, 11 | val dimming: DimmingValue, 12 | ) 13 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/ColorTemperaturePalette.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Color temperature/brightness pair reference. 8 | */ 9 | @Serializable 10 | data class ColorTemperaturePalette( 11 | @SerialName("color_temperature") 12 | val colorTemperature: ColorTemperatureValue, 13 | val dimming: DimmingValue, 14 | ) 15 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/ColorTemperatureRange.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import inkapplications.spondee.measure.ColorTemperature 4 | import inkapplications.spondee.measure.toMireds 5 | import inkapplications.spondee.structure.convert 6 | import kotlin.math.roundToInt 7 | import kotlin.math.roundToLong 8 | 9 | /** 10 | * Represents a range of color temperatures. 11 | */ 12 | data class ColorTemperatureRange( 13 | val coolest: ColorTemperature, 14 | val warmest: ColorTemperature, 15 | ) { 16 | /** 17 | * Express the color temperatures as a range of Kelvin units. 18 | */ 19 | val kelvinRange = warmest.toKelvin().convert { roundToLong() }..coolest.toKelvin().convert { roundToLong() } 20 | 21 | /** 22 | * Express the color temperatures as a range of Mireds. 23 | */ 24 | val miredRange = coolest.toMireds().convert { roundToInt() }..warmest.toMireds().convert { roundToInt() } 25 | 26 | operator fun contains(other: ColorTemperature): Boolean { 27 | val warmestKelvin = warmest.toKelvin().value.toDouble() 28 | val coolestKelvin = coolest.toKelvin().value.toDouble() 29 | val otherKelvin = other.toKelvin().value.toDouble() 30 | 31 | return otherKelvin in warmestKelvin..coolestKelvin 32 | } 33 | 34 | override fun toString(): String { 35 | return "$warmest..$coolest" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/ColorTemperatureValue.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import inkapplications.shade.serialization.MiredSerializer 4 | import inkapplications.spondee.measure.ColorTemperature 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Describes the current color temperature of a light. 10 | */ 11 | @Serializable 12 | data class ColorTemperatureValue( 13 | /** 14 | * Current color temperature of the bulb, 15 | * 16 | * This value can be null if the color is not in the ct spectrum 17 | */ 18 | @SerialName("mirek") 19 | @Serializable(with = MiredSerializer::class) 20 | val temperature: ColorTemperature?, 21 | ) 22 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/ColorValue.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import com.github.ajalt.colormath.Color 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Information about a light's color. 9 | */ 10 | @Serializable 11 | data class ColorValue( 12 | /** 13 | * Current color of the light 14 | */ 15 | @SerialName("xy") 16 | @Serializable(with = Chromaticity.ColorSerializer::class) 17 | val color: Color, 18 | ) 19 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/DimmingInfo.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import inkapplications.shade.serialization.WholePercentageSerializer 4 | import inkapplications.spondee.scalar.Percentage 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Info about a light's dimming status and capabilities. 10 | */ 11 | @Serializable 12 | data class DimmingInfo( 13 | /** 14 | * Current brightness value. 15 | */ 16 | @Serializable(with = WholePercentageSerializer::class) 17 | val brightness: Percentage, 18 | 19 | /** 20 | * Percentage of the maximum lumen the device outputs on minimum brightness. 21 | */ 22 | @Serializable(with = WholePercentageSerializer::class) 23 | @SerialName("min_dim_level") 24 | val minimum: Percentage? = null, 25 | ) 26 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/DimmingValue.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import inkapplications.shade.serialization.WholePercentageSerializer 4 | import inkapplications.spondee.scalar.Percentage 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Info about a light's dimming status. 9 | */ 10 | @Serializable 11 | data class DimmingValue( 12 | /** 13 | * Current brightness value. 14 | */ 15 | @Serializable(with = WholePercentageSerializer::class) 16 | val brightness: Percentage, 17 | ) 18 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/DynamicsStatus.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Wraps potential values for [LightDynamics] state. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class DynamicsStatus(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val DynamicPalette = DynamicsStatus("dynamic_palette") 16 | val None = DynamicsStatus("none") 17 | 18 | @Deprecated("This is an unbounded set of values. The values provided here are not exhaustive and will be removed in a future release.") 19 | fun values(): Array = arrayOf(None, DynamicPalette) 20 | 21 | @Deprecated( 22 | message = "Deprecated in favor of constructor", 23 | replaceWith = ReplaceWith("DynamicsStatus(key)"), 24 | ) 25 | fun valueOf(key: String) = values().single { it.key == key } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/Gamut.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import com.github.ajalt.colormath.Illuminant 4 | import com.github.ajalt.colormath.model.RGBColorSpace 5 | import com.github.ajalt.colormath.model.xyY 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * RGB Chromaticity values defining a color space gamut 10 | */ 11 | @Serializable 12 | data class Gamut( 13 | @Serializable(with = Chromaticity.CieChromaticitySerializer::class) 14 | val red: xyY, 15 | @Serializable(with = Chromaticity.CieChromaticitySerializer::class) 16 | val green: xyY, 17 | @Serializable(with = Chromaticity.CieChromaticitySerializer::class) 18 | val blue: xyY, 19 | ) { 20 | fun toColorSpace() = RGBColorSpace( 21 | name = "Hue Specified Colorspace", 22 | whitePoint = Illuminant.D65, 23 | transferFunctions = RGBColorSpace.LinearTransferFunctions, 24 | r = red, 25 | g = green, 26 | b = blue, 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/GamutType.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Simple Gamut Type definition used by Hue products. 8 | */ 9 | @Serializable 10 | @JvmInline 11 | value class GamutType(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | /** 16 | * Gamut of early Philips color-only products 17 | */ 18 | val A = GamutType("A") 19 | 20 | /** 21 | * Limited gamut of first Hue color products 22 | */ 23 | val B = GamutType("B") 24 | 25 | /** 26 | * Richer color gamut of Hue white and color ambiance products 27 | */ 28 | val C = GamutType("C") 29 | 30 | /** 31 | * Color gamut of non-hue products with non-hue gamuts resp w/o gamut 32 | */ 33 | val Other = GamutType("other") 34 | 35 | @Deprecated("This is an unbounded set of values. The values provided here are not exhaustive and will be removed in a future release.") 36 | fun values(): Array = arrayOf(A, B, C, Other) 37 | 38 | @Deprecated( 39 | message = "Deprecated in favor of constructor", 40 | replaceWith = ReplaceWith("GamutType(key)"), 41 | ) 42 | fun valueOf(key: String) = values().single { it.key == key } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/Gradient.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Basic feature containing gradient properties. 8 | */ 9 | @Serializable 10 | data class Gradient( 11 | /** 12 | * Collection of gradients points. 13 | */ 14 | val points: List, 15 | 16 | /** 17 | * Number of color points that gradient lamp is capable of showing with gradience. 18 | */ 19 | @SerialName("points_capable") 20 | val pointsCapable: Int, 21 | ) { 22 | init { 23 | if (points.size > 5) throw IllegalArgumentException("Gradient cannot contain more than 5 points") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/GradientMode.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Mode in which gradient points are currently being deployed. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class GradientMode(val key: String) { 12 | companion object { 13 | val InterpolatedPalette = GradientMode("interpolated_palette") 14 | val InterpolatedPaletteMirrored = GradientMode("interpolated_palette_mirrored") 15 | val RandomPixelated = GradientMode("random_pixelated") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/GradientPoint.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Contains information on a single gradient point. 8 | */ 9 | @Serializable 10 | data class GradientPoint( 11 | @SerialName("color") 12 | val colorValue: ColorValue, 13 | ) 14 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/GradientValue.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Basic feature containing gradient properties. 7 | */ 8 | @Serializable 9 | data class GradientValue( 10 | /** 11 | * Collection of gradients points. 12 | */ 13 | val points: List, 14 | 15 | /** 16 | * Mode in which the points are currently being deployed. 17 | */ 18 | val mode: GradientMode, 19 | ) { 20 | init { 21 | if (points.size > 5) throw IllegalArgumentException("Gradient cannot contain more than 5 points") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/LightDynamics.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import inkapplications.shade.serialization.FractionalPercentageSerializer 4 | import inkapplications.spondee.scalar.Percentage 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Dynamic Lighting Effects information 10 | */ 11 | @Serializable 12 | data class LightDynamics( 13 | /** 14 | * Current status of the lamp with dynamics. 15 | */ 16 | val status: DynamicsStatus, 17 | 18 | /** 19 | * Statuses in which a lamp could be when playing dynamics. 20 | */ 21 | @SerialName("status_values") 22 | val statusValues: List, 23 | 24 | /** 25 | * Speed of dynamic palette or effect. 26 | * 27 | * The speed is valid for the dynamic palette if the status is 28 | * [DynamicsStatus.DynamicPalette] or for the corresponding effect listed 29 | * in status. 30 | * In case of status [DynamicsStatus.None], the speed is not valid 31 | */ 32 | @Serializable(with = FractionalPercentageSerializer::class) 33 | val speed: Percentage, 34 | 35 | /** 36 | * Indicates whether the value presented in speed is valid 37 | */ 38 | @SerialName("speed_valid") 39 | val speedValid: Boolean, 40 | ) 41 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/LightEffect.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Static effect applied to a light. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class LightEffect(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val Fire = LightEffect("fire") 16 | val Candle = LightEffect("candle") 17 | val None = LightEffect("no_effect") 18 | 19 | @Deprecated("This is an unbounded set of values. The values provided here are not exhaustive and will be removed in a future release.") 20 | fun values(): Array = arrayOf(Fire, Candle, None) 21 | 22 | @Deprecated( 23 | message = "Deprecated in favor of constructor", 24 | replaceWith = ReplaceWith("LightEffect(key)"), 25 | ) 26 | fun valueOf(key: String) = values().single { it.key == key } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/LightMode.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Wrap's a light mode flag/values 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class LightMode(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val Normal = LightMode("normal") 16 | val Streaming = LightMode("streaming") 17 | 18 | @Deprecated("This is an unbounded set of values. The values provided here are not exhaustive and will be removed in a future release.") 19 | fun values(): Array = arrayOf(Normal, Streaming) 20 | 21 | @Deprecated( 22 | message = "Deprecated in favor of constructor", 23 | replaceWith = ReplaceWith("LightMode(key)"), 24 | ) 25 | fun valueOf(key: String) = values().single { it.key == key } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/LightSignal.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Indicates which signal is currently active for a light signal. 8 | */ 9 | @Serializable 10 | @JvmInline 11 | value class LightSignal(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val NoSignal = LightSignal("no_signal") 16 | val OnOff = LightSignal("on_off") 17 | 18 | @Deprecated("This is an unbounded set of values. The values provided here are not exhaustive and will be removed in a future release.") 19 | fun values(): Array = arrayOf(NoSignal, OnOff) 20 | 21 | @Deprecated( 22 | message = "Deprecated in favor of constructor", 23 | replaceWith = ReplaceWith("LightSignal(key)"), 24 | ) 25 | fun valueOf(key: String) = values().single { it.key == key } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/LightSignalStatus.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.datetime.Instant 4 | import kotlinx.datetime.serializers.InstantIso8601Serializer 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Status of active light signal. 10 | */ 11 | @Serializable 12 | data class LightSignalStatus( 13 | /** 14 | * Indicates which signal is currently active. 15 | */ 16 | val signal: LightSignal, 17 | /** 18 | * Timestamp indicating when the active signal is expected to end. 19 | * Value is not set if there is no_signal 20 | */ 21 | @SerialName("estimated_end") 22 | @Serializable(with = InstantIso8601Serializer::class) 23 | val estimatedEnd: Instant? = null, 24 | ) 25 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/LightSignaling.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Signaling properties for a light. 7 | */ 8 | @Serializable 9 | data class LightSignaling( 10 | /** 11 | * Indicates status of active signal. Not available when inactive. 12 | */ 13 | val status: LightSignalStatus? = null, 14 | ) 15 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/LightingEffectInfo.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Basic feature containing effect properties. 8 | */ 9 | @Serializable 10 | data class LightingEffectInfo( 11 | /** 12 | * Current lighting effect 13 | */ 14 | val status: LightEffect, 15 | 16 | /** 17 | * List of supported lighting effects on this device. 18 | */ 19 | @SerialName("effect_values") 20 | val values: List, 21 | ) 22 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/TimedLightEffect.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Timed Effect applied to a light 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class TimedLightEffect(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val Sunrise = TimedLightEffect("sunrise") 16 | val None = TimedLightEffect("no_effect") 17 | 18 | @Deprecated("This is an unbounded set of values. The values provided here are not exhaustive and will be removed in a future release.") 19 | fun values(): Array = arrayOf(Sunrise, None) 20 | 21 | @Deprecated( 22 | message = "Deprecated in favor of constructor", 23 | replaceWith = ReplaceWith("TimedLightEffect(key)"), 24 | ) 25 | fun valueOf(key: String) = values().single { it.key == key } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lights/src/commonMain/kotlin/inkapplications/shade/lights/structures/TimedLightingEffectInfo.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.lights.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Basic feature containing timed effect properties. 8 | */ 9 | @Serializable 10 | data class TimedLightingEffectInfo( 11 | /** 12 | * Current lighting effect 13 | */ 14 | val status: TimedLightEffect, 15 | 16 | /** 17 | * List of supported lighting effects on this device. 18 | */ 19 | @SerialName("effect_values") 20 | val values: List 21 | ) 22 | -------------------------------------------------------------------------------- /resources/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | 16 | api(libs.coroutines.core) 17 | } 18 | } 19 | 20 | val commonTest by getting { 21 | dependencies { 22 | implementation(libs.test.core) 23 | implementation(libs.test.annotations) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /resources/src/commonMain/kotlin/inkapplications/shade/resources/ResourceControls.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.resources 2 | 3 | import inkapplications.shade.resources.structures.Resource 4 | 5 | /** 6 | * Actions for API resources on the hue bridge 7 | */ 8 | interface ResourceControls { 9 | /** 10 | * Retrieve all known API resources 11 | */ 12 | suspend fun listResources(): List 13 | } 14 | -------------------------------------------------------------------------------- /resources/src/commonMain/kotlin/inkapplications/shade/resources/ShadeResources.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.resources 2 | 3 | import inkapplications.shade.internals.HueHttpClient 4 | import inkapplications.shade.internals.getData 5 | import inkapplications.shade.resources.structures.Resource 6 | 7 | /** 8 | * Implements Resource Controls using the hue client. 9 | */ 10 | internal class ShadeResources( 11 | private val hueHttpClient: HueHttpClient, 12 | ): ResourceControls { 13 | override suspend fun listResources(): List { 14 | return hueHttpClient.getData("resource") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /resources/src/commonMain/kotlin/inkapplications/shade/resources/ShadeResourcesModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.resources 2 | 3 | import inkapplications.shade.internals.InternalsModule 4 | 5 | /** 6 | * Module for accessing Resource information. 7 | */ 8 | class ShadeResourcesModule( 9 | internalsModule: InternalsModule, 10 | ) { 11 | val resources: ResourceControls = ShadeResources(internalsModule.hueHttpClient) 12 | } 13 | -------------------------------------------------------------------------------- /resources/src/commonMain/kotlin/inkapplications/shade/resources/structures/Resource.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.resources.structures 2 | 3 | import inkapplications.shade.structures.ResourceId 4 | import inkapplications.shade.structures.ResourceType 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * Hue API resource. 10 | */ 11 | @Serializable 12 | data class Resource( 13 | /** 14 | * Unique ID for the resource. 15 | */ 16 | val id: ResourceId, 17 | 18 | /** 19 | * Resource object type. 20 | */ 21 | val type: ResourceType? = null, 22 | 23 | @Deprecated("V1 Resource. Left for migration purposes only, may be removed at any point by API or SDK.") 24 | @SerialName("id_v1") 25 | val v1Id: String? = null, 26 | ) 27 | -------------------------------------------------------------------------------- /rooms/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | 16 | api(libs.coroutines.core) 17 | } 18 | } 19 | 20 | val commonTest by getting { 21 | dependencies { 22 | implementation(libs.test.core) 23 | implementation(libs.test.annotations) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rooms/src/commonMain/kotlin/inkapplications/shade/rooms/RoomControls.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.rooms 2 | 3 | import inkapplications.shade.rooms.parameters.RoomCreateParameters 4 | import inkapplications.shade.rooms.parameters.RoomUpdateParameters 5 | import inkapplications.shade.rooms.structures.Room 6 | import inkapplications.shade.structures.ResourceId 7 | import inkapplications.shade.structures.ResourceReference 8 | 9 | /** 10 | * Actions to get and control rooms in the hue system 11 | */ 12 | interface RoomControls { 13 | /** 14 | * Get the state of a single room 15 | * 16 | * @param id The v2 resource ID of the room to fetch data about 17 | */ 18 | suspend fun getRoom(id: ResourceId): Room 19 | 20 | /** 21 | * Get a list of rooms configured on the hue service. 22 | */ 23 | suspend fun listRooms(): List 24 | 25 | /** 26 | * Create a new room on the hue bridge. 27 | */ 28 | suspend fun createRoom(parameters: RoomCreateParameters): ResourceReference 29 | 30 | /** 31 | * Update and existing room on the hue bridge. 32 | * 33 | * @param id The v2 resource ID of the room to be updated 34 | * @param parameters data about the room to be updated. 35 | */ 36 | suspend fun updateRoom(id: ResourceId, parameters: RoomUpdateParameters): ResourceReference 37 | 38 | /** 39 | * Delete an existing room from the hue bridge. 40 | * 41 | * @param id The v2 resource ID of the room to be deleted 42 | */ 43 | suspend fun deleteRoom(id: ResourceId): ResourceReference 44 | } 45 | -------------------------------------------------------------------------------- /rooms/src/commonMain/kotlin/inkapplications/shade/rooms/ShadeRoomsModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.rooms 2 | 3 | import inkapplications.shade.internals.InternalsModule 4 | 5 | /** 6 | * Provides Access to room control services. 7 | */ 8 | class ShadeRoomsModule( 9 | internalsModule: InternalsModule, 10 | ) { 11 | val rooms: RoomControls = ShadeRooms(internalsModule.hueHttpClient) 12 | } 13 | 14 | -------------------------------------------------------------------------------- /rooms/src/commonMain/kotlin/inkapplications/shade/rooms/parameters/RoomCreateParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.rooms.parameters 2 | 3 | import inkapplications.shade.structures.ResourceReference 4 | import inkapplications.shade.structures.SegmentMetadata 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Data used for creating a new room type via the bridge. 9 | */ 10 | @Serializable 11 | data class RoomCreateParameters( 12 | /** 13 | * Configuration data for the room. 14 | */ 15 | val metadata: SegmentMetadata, 16 | 17 | /** 18 | * Devices to group by the Room 19 | */ 20 | val children: List, 21 | ) 22 | -------------------------------------------------------------------------------- /rooms/src/commonMain/kotlin/inkapplications/shade/rooms/parameters/RoomUpdateParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.rooms.parameters 2 | 3 | import inkapplications.shade.structures.ResourceReference 4 | import inkapplications.shade.structures.SegmentMetadataUpdate 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Parameters that can be specified when updating a room on the hue bridge. 9 | */ 10 | @Serializable 11 | data class RoomUpdateParameters( 12 | /** 13 | * Configuration data for the room. 14 | */ 15 | val metadata: SegmentMetadataUpdate? = null, 16 | 17 | /** 18 | * Devices to group by the Room 19 | */ 20 | val children: List? = null, 21 | ) 22 | -------------------------------------------------------------------------------- /rooms/src/commonMain/kotlin/inkapplications/shade/rooms/structures/Room.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.rooms.structures 2 | 3 | import inkapplications.shade.structures.ResourceId 4 | import inkapplications.shade.structures.ResourceReference 5 | import inkapplications.shade.structures.SegmentMetadata 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * State and capabilities of a room resource. 10 | */ 11 | @Serializable 12 | data class Room( 13 | /** 14 | * Unique identifier representing a specific room instance. 15 | */ 16 | val id: ResourceId, 17 | 18 | /** 19 | * References all services aggregating control and state of children 20 | * in the group. 21 | * 22 | * This includes all services grouped in the group hierarchy given by child 23 | * relation This includes all services of a device grouped in the group 24 | * hierarchy given by child relation Aggregation is per service type, 25 | * ie every service type which can be grouped has a corresponding definition 26 | * of grouped type Supported types “light” 27 | */ 28 | val services: List, 29 | 30 | /** 31 | * Configuration object for a room. 32 | */ 33 | val metadata: SegmentMetadata, 34 | 35 | /** 36 | * Devices to group by the Room. 37 | */ 38 | val children: List, 39 | ) 40 | -------------------------------------------------------------------------------- /scenes/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | api(projects.lights) 16 | 17 | api(libs.coroutines.core) 18 | } 19 | } 20 | 21 | val commonTest by getting { 22 | dependencies { 23 | implementation(libs.test.core) 24 | implementation(libs.test.annotations) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/ShadeScenesModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes 2 | 3 | import inkapplications.shade.internals.InternalsModule 4 | 5 | /** 6 | * Provides Access to scene services. 7 | */ 8 | class ShadeScenesModule( 9 | internalsModule: InternalsModule, 10 | ) { 11 | val scenes: SceneControls = ShadeScenes(internalsModule.hueHttpClient) 12 | } 13 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/parameters/SceneCreateParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes.parameters 2 | 3 | import inkapplications.shade.scenes.structures.SceneActionReference 4 | import inkapplications.shade.scenes.structures.SceneMetadata 5 | import inkapplications.shade.scenes.structures.ScenePalette 6 | import inkapplications.shade.serialization.FractionalPercentageSerializer 7 | import inkapplications.shade.structures.ResourceReference 8 | import inkapplications.spondee.scalar.Percentage 9 | import kotlinx.serialization.SerialName 10 | import kotlinx.serialization.Serializable 11 | 12 | /** 13 | * Parameters used for creating a new Scene on the Hue bridge. 14 | */ 15 | @Serializable 16 | data class SceneCreateParameters( 17 | /** 18 | * Actions to be applied to each device in the scene's group. 19 | */ 20 | val actions: List, 21 | 22 | /** 23 | * User-supplied configuration info for the scene. 24 | */ 25 | val metadata: SceneMetadata, 26 | 27 | /** 28 | * The group of lights in the scene. 29 | */ 30 | val group: ResourceReference, 31 | 32 | /** 33 | * Group of colors that describe the palette of colors used to be used 34 | * when playing dynamics. 35 | */ 36 | val palette: ScenePalette? = null, 37 | 38 | /** 39 | * The speed of the dynamic palette. 40 | */ 41 | @Serializable(with = FractionalPercentageSerializer::class) 42 | val speed: Percentage? = null, 43 | 44 | /** 45 | * Indicates whether to automatically start the scene dynamically on 46 | * active recall. 47 | */ 48 | @SerialName("auto_dynamic") 49 | val autoDynamic: Boolean? = null, 50 | ) 51 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/parameters/SceneUpdateParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes.parameters 2 | 3 | import inkapplications.shade.scenes.structures.SceneActionReference 4 | import inkapplications.shade.scenes.structures.SceneMetadata 5 | import inkapplications.shade.scenes.structures.ScenePalette 6 | import inkapplications.shade.scenes.structures.SceneRecall 7 | import inkapplications.shade.serialization.FractionalPercentageSerializer 8 | import inkapplications.spondee.scalar.Percentage 9 | import kotlinx.serialization.SerialName 10 | import kotlinx.serialization.Serializable 11 | 12 | @Serializable 13 | data class SceneUpdateParameters( 14 | /** 15 | * Actions to be applied to each device in the scene's group. 16 | */ 17 | val actions: List? = null, 18 | 19 | val recall: SceneRecall? = null, 20 | 21 | /** 22 | * User-supplied configuration info for the scene. 23 | */ 24 | val metadata: SceneMetadata? = null, 25 | 26 | /** 27 | * Group of colors that describe the palette of colors used to be used 28 | * when playing dynamics. 29 | */ 30 | val palette: ScenePalette? = null, 31 | 32 | /** 33 | * The speed of the dynamic palette. 34 | */ 35 | @Serializable(with = FractionalPercentageSerializer::class) 36 | val speed: Percentage? = null, 37 | 38 | /** 39 | * Indicates whether to automatically start the scene dynamically on 40 | * active recall. 41 | */ 42 | @SerialName("auto_dynamic") 43 | val autoDynamic: Boolean? = null, 44 | ) 45 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/structures/SceneActionReference.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes.structures 2 | 3 | import inkapplications.shade.structures.ResourceReference 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Action to be applied to a specified resource for a scene. 8 | */ 9 | @Serializable 10 | data class SceneActionReference( 11 | /** 12 | * The resource to apply the action to. 13 | */ 14 | val target: ResourceReference, 15 | 16 | /** 17 | * The action to be applied to the resource. 18 | */ 19 | val action: SceneAction, 20 | ) 21 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/structures/SceneMetadata.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes.structures 2 | 3 | import inkapplications.shade.structures.ResourceReference 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * User-configured metadata for the scene. 8 | */ 9 | @Serializable 10 | data class SceneMetadata( 11 | /** 12 | * Human readable name of a resource 13 | */ 14 | val name: String, 15 | 16 | /** 17 | * Reference with unique identifier for the image representing the scene only accepting “rtype”: “public_image” on creation 18 | */ 19 | val image: ResourceReference? = null, 20 | ) 21 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/structures/ScenePalette.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes.structures 2 | 3 | import inkapplications.shade.lights.structures.ColorPalette 4 | import inkapplications.shade.lights.structures.ColorTemperaturePalette 5 | import inkapplications.shade.lights.structures.DimmingValue 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | 9 | /** 10 | * Group of colors that describe the palette of colors to be used when playing dynamics 11 | */ 12 | @Serializable 13 | data class ScenePalette( 14 | /** 15 | * List of colors to be used when playing dynamics. 16 | */ 17 | val color: List? = null, 18 | 19 | /** 20 | * List of colors to be used when playing dynamics. 21 | */ 22 | val dimming: List? = null, 23 | 24 | /** 25 | * List of colors to be used when playing dynamics. 26 | */ 27 | @SerialName("color_temperature") 28 | val colorTemperature: List? = null, 29 | ) 30 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/structures/SceneRecall.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes.structures 2 | 3 | import inkapplications.shade.lights.structures.DimmingValue 4 | import inkapplications.shade.serialization.MillisecondDurationSerializer 5 | import kotlinx.serialization.Serializable 6 | import kotlin.time.Duration 7 | 8 | @Serializable 9 | data class SceneRecall( 10 | val action: SceneRecallAction, 11 | val status: SceneRecallStatus, 12 | @Serializable(with = MillisecondDurationSerializer::class) 13 | val duration: Duration, 14 | val dimming: DimmingValue, 15 | ) 16 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/structures/SceneRecallAction.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * When writing active, the actions in the scene are executed on the target. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class SceneRecallAction(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val Active = SceneRecallAction("active") 16 | 17 | /** 18 | * Starts dynamic scene with colors in the Palette object. 19 | */ 20 | val DynamicPalette = SceneRecallAction("dynamic_palette") 21 | val Static = SceneRecallAction("static") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scenes/src/commonMain/kotlin/inkapplications/shade/scenes/structures/SceneRecallStatus.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.scenes.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * When writing active, the actions in the scene are executed on the target. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class SceneRecallStatus(val key: String) { 12 | override fun toString(): String = key 13 | 14 | companion object { 15 | val Active = SceneRecallStatus("active") 16 | 17 | /** 18 | * Starts dynamic scene with colors in the Palette object. 19 | */ 20 | val DynamicPalette = SceneRecallStatus("dynamic_palette") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /serialization/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | api(libs.spondee) 13 | } 14 | } 15 | 16 | val jvmTest by getting { 17 | dependencies { 18 | implementation(libs.test.core) 19 | implementation(libs.test.junit) 20 | implementation(libs.test.annotations) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/inkapplications/shade/serialization/DelegateSerializer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.serialization 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.descriptors.SerialDescriptor 5 | import kotlinx.serialization.encoding.Decoder 6 | import kotlinx.serialization.encoding.Encoder 7 | 8 | /** 9 | * Delegates kotlin serialization to a backing type for simpler encoding. 10 | */ 11 | abstract class DelegateSerializer( 12 | private val delegate: KSerializer, 13 | ): KSerializer { 14 | final override val descriptor: SerialDescriptor = delegate.descriptor 15 | final override fun deserialize(decoder: Decoder): DESERIALIZED = delegate.deserialize(decoder).let(::deserialize) 16 | final override fun serialize(encoder: Encoder, value: DESERIALIZED) = delegate.serialize(encoder, serialize(value)) 17 | 18 | protected abstract fun serialize(data: DESERIALIZED): SERIALIZED 19 | protected abstract fun deserialize(data: SERIALIZED): DESERIALIZED 20 | } 21 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/inkapplications/shade/serialization/ExplicitMiredSerializer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.serialization 2 | 3 | import inkapplications.spondee.measure.Mireds 4 | import inkapplications.spondee.measure.mireds 5 | import inkapplications.spondee.structure.convert 6 | import kotlinx.serialization.builtins.serializer 7 | import kotlin.math.roundToInt 8 | 9 | /** 10 | * Convert a mired color temperature to/from an int value 11 | * 12 | * This is used for color temperature delta values that require the unit in 13 | * mireds. Since mireds and kelvin do not have a linear relationship, a delta 14 | * value cannot be translated between the two units. So this converter 15 | * takes and produces an explicit mired unit only. 16 | */ 17 | object ExplicitMiredSerializer: DelegateSerializer(Int.serializer()) { 18 | override fun serialize(data: Mireds): Int = data.convert { roundToInt() } 19 | override fun deserialize(data: Int): Mireds = data.mireds 20 | } 21 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/inkapplications/shade/serialization/FractionalPercentageSerializer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.serialization 2 | 3 | import inkapplications.spondee.scalar.Percentage 4 | import inkapplications.spondee.scalar.decimalPercentage 5 | import kotlinx.serialization.builtins.serializer 6 | 7 | /** 8 | * Serialize a percentage as a fraction of of a decimal (0.0-1.0) 9 | */ 10 | object FractionalPercentageSerializer: DelegateSerializer(Double.serializer()) { 11 | override fun serialize(data: Percentage): Double = data.toDecimal().value.toDouble() 12 | override fun deserialize(data: Double): Percentage = data.decimalPercentage 13 | } 14 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/inkapplications/shade/serialization/HueError.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.serialization 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Error from the hue API 7 | */ 8 | @Serializable 9 | data class HueError( 10 | val description: String, 11 | ) 12 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/inkapplications/shade/serialization/MillisecondDurationSerializer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.serialization 2 | 3 | import kotlinx.serialization.builtins.serializer 4 | import kotlin.time.Duration 5 | import kotlin.time.Duration.Companion.milliseconds 6 | 7 | /** 8 | * Serialize a Duration as a Long in milliseconds. 9 | */ 10 | object MillisecondDurationSerializer: DelegateSerializer(Long.serializer()) { 11 | override fun serialize(data: Duration): Long = data.inWholeMilliseconds 12 | override fun deserialize(data: Long): Duration = data.milliseconds 13 | } 14 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/inkapplications/shade/serialization/MiredSerializer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.serialization 2 | 3 | import inkapplications.spondee.measure.ColorTemperature 4 | import inkapplications.spondee.measure.mireds 5 | import inkapplications.spondee.measure.toMireds 6 | import inkapplications.spondee.structure.convert 7 | import kotlinx.serialization.builtins.serializer 8 | import kotlin.math.roundToInt 9 | 10 | /** 11 | * Convert standard ColorTemperatures to/from Mireds 12 | * 13 | * Hue's API calls this a 'mirek' in an effort to make the most obscure 14 | * term for this unit, but it seems to be identical to a Mired. 15 | * Now you don't have to deal with it. 16 | */ 17 | object MiredSerializer: DelegateSerializer(Int.serializer()) { 18 | override fun serialize(data: ColorTemperature): Int { 19 | return data.toMireds().convert { roundToInt() } 20 | } 21 | 22 | override fun deserialize(data: Int): ColorTemperature { 23 | return data.mireds 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/inkapplications/shade/serialization/WholePercentageSerializer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.serialization 2 | 3 | import inkapplications.spondee.scalar.Percentage 4 | import inkapplications.spondee.scalar.percent 5 | import inkapplications.spondee.scalar.toWholePercentage 6 | import kotlinx.serialization.builtins.serializer 7 | 8 | /** 9 | * Serialize a percentage value as an integer. 10 | */ 11 | object WholePercentageSerializer: DelegateSerializer(Double.serializer()) { 12 | override fun serialize(data: Percentage): Double = data.toWholePercentage().value.toDouble() 13 | override fun deserialize(data: Double): Percentage = data.percent 14 | } 15 | 16 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "shade" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | include("auth") 5 | include("cli") 6 | include("core") 7 | include("devices") 8 | include("discover") 9 | include("events") 10 | include("grouped-lights") 11 | include("internals") 12 | include("lights") 13 | include("resources") 14 | include("rooms") 15 | include("scenes") 16 | include("serialization") 17 | include("structures") 18 | include("zones") 19 | -------------------------------------------------------------------------------- /shade-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./gradlew cli:installDist --quiet && \ 4 | cli/build/install/shade/bin/shade "$@" 5 | -------------------------------------------------------------------------------- /structures/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.serialization) 13 | api(libs.coroutines.core) 14 | api(libs.datetime) 15 | api(libs.spondee) 16 | api("com.github.ajalt.colormath:colormath:3.6.0") 17 | } 18 | } 19 | 20 | val commonTest by getting { 21 | dependencies { 22 | implementation(libs.test.core) 23 | implementation(libs.test.annotations) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/AuthToken.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * Wraps an API key used for authenticating with the Hue API 8 | */ 9 | @Serializable 10 | data class AuthToken( 11 | @SerialName("username") 12 | val applicationKey: String, 13 | @SerialName("clientkey") 14 | val clientKey: String? = null, 15 | ) { 16 | override fun toString(): String { 17 | return "AuthToken{}" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/HueConfigurationContainer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.coroutines.flow.StateFlow 4 | 5 | /** 6 | * Store configuration for communicating with the Hue API. 7 | */ 8 | interface HueConfigurationContainer { 9 | /** 10 | * The Hue IP or hostname to communicate with. 11 | */ 12 | val hostname: StateFlow 13 | 14 | /** 15 | * TLS configuration strategy. 16 | */ 17 | val securityStrategy: StateFlow 18 | 19 | /** 20 | * Auth token used in authenticated requests to the hue bridge 21 | */ 22 | val authToken: StateFlow 23 | 24 | /** 25 | * he Hue IP or hostname to communicate with. eg "192.168.1.5" 26 | */ 27 | suspend fun setHostname(hostname: String?) 28 | 29 | /** 30 | * Set the application key/token to use when communicating with the Hue bridge. 31 | */ 32 | suspend fun setAuthToken(token: AuthToken?) 33 | 34 | /** 35 | * TLS configuration strategy. 36 | */ 37 | suspend fun setSecurityStrategy(securityStrategy: SecurityStrategy) 38 | } 39 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/InMemoryConfigurationContainer.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | import kotlinx.coroutines.flow.StateFlow 5 | 6 | /** 7 | * Implements a configuration container by storing config in a thread-safe 8 | * memory storage structure. 9 | * 10 | * @param initialAuthToken Optional auth token to start with 11 | */ 12 | class InMemoryConfigurationContainer( 13 | initialHostname: String? = null, 14 | initialAuthToken: AuthToken? = null, 15 | initialSecurityStrategy: SecurityStrategy = SecurityStrategy.PlatformTrust, 16 | ): HueConfigurationContainer { 17 | private val mutableHostname = MutableStateFlow(initialHostname) 18 | private val mutableAuthToken = MutableStateFlow(initialAuthToken) 19 | private val mutableSecurityStrategy = MutableStateFlow(initialSecurityStrategy) 20 | override val hostname: StateFlow = mutableHostname 21 | override val securityStrategy: StateFlow = mutableSecurityStrategy 22 | override val authToken: StateFlow = mutableAuthToken 23 | 24 | override suspend fun setHostname(hostname: String?) { 25 | mutableHostname.value = hostname 26 | } 27 | 28 | override suspend fun setAuthToken(token: AuthToken?) { 29 | mutableAuthToken.value = token 30 | } 31 | 32 | override suspend fun setSecurityStrategy(securityStrategy: SecurityStrategy) { 33 | mutableSecurityStrategy.value = securityStrategy 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/PowerInfo.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Describes the power state of a device and associated capabilities. 7 | */ 8 | @Serializable 9 | data class PowerInfo( 10 | /** 11 | * Whether the device is currently powered on. 12 | */ 13 | val on: Boolean, 14 | ) 15 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/PowerValue.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Describes the power state of a device. 7 | */ 8 | @Serializable 9 | data class PowerValue( 10 | /** 11 | * Whether the device is currently powered on. 12 | */ 13 | val on: Boolean, 14 | ) 15 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/ResourceId.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Unique identifier representing a specific resource instance. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class ResourceId(val value: String) { 12 | override fun toString(): String = value 13 | } 14 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/ResourceReference.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | /** 7 | * A reference to another object in the API 8 | */ 9 | @Serializable 10 | data class ResourceReference( 11 | /** 12 | * The unique id of the referenced resource 13 | */ 14 | @SerialName("rid") 15 | val id: ResourceId, 16 | 17 | /** 18 | * The type of the referenced resource 19 | */ 20 | @SerialName("rtype") 21 | val type: ResourceType, 22 | ) { 23 | override fun toString(): String = "[$type:$id]" 24 | } 25 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/SegmentMetadata.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Configuration data for a segment of devices, such as a room or zone. 7 | */ 8 | @Serializable 9 | data class SegmentMetadata( 10 | /** 11 | * Category of type/purpose of the segment 12 | */ 13 | val archetype: SegmentArchetype, 14 | 15 | /** 16 | * Human readable name of the room. 17 | */ 18 | val name: String, 19 | ) 20 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/SegmentMetadataUpdate.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Configuration data for a segment of devices, such as a room or zone.@author 7 | * 8 | * This represents the modifyable options for [SegmentMetadata] as optional 9 | * properties. 10 | */ 11 | @Serializable 12 | data class SegmentMetadataUpdate( 13 | /** 14 | * Category of type/purpose of the segment 15 | */ 16 | val archetype: SegmentArchetype? = null, 17 | 18 | /** 19 | * Human readable name of the room. 20 | */ 21 | val name: String? = null, 22 | ) 23 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/UndocumentedApi.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | /** 4 | * Annotation used to designate APIs in the hue system that were not documented. 5 | * 6 | * Since these methods don't have documentation in Hue's official docs, the 7 | * data types and methods may change at any time, including on the Bridge 8 | * itself, meaning the code may break over time. Data structures are filled in 9 | * by interpreting responses and are likely incomplete and may be incorrect 10 | * or incomplete for different devices. 11 | * Changes made to methods with this annotation will NOT be considered a 12 | * breaking change between versions of the Shade SDK. 13 | * 14 | * Use with caution. 15 | */ 16 | @RequiresOptIn( 17 | message = "This uses an undocumented Hue API and may be unreliable, incorrect, and is subject to change at any time." 18 | ) 19 | @Retention(AnnotationRetention.BINARY) 20 | annotation class UndocumentedApi 21 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/UnknownEvent.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | /** 4 | * An event that was sent through the events api, but not deserialized by 5 | * any module. 6 | */ 7 | @UndocumentedApi 8 | data class UnknownEvent( 9 | /** 10 | * Designated type for the data. 11 | */ 12 | val type: String, 13 | 14 | /** 15 | * Raw JSON string of the event object. 16 | */ 17 | val json: String, 18 | ) 19 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/VersionString.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlin.jvm.JvmInline 5 | 6 | /** 7 | * Software major/minor/patch version string. 8 | */ 9 | @JvmInline 10 | @Serializable 11 | value class VersionString(val full: String) { 12 | private val pattern get() = Regex("^(\\d+)\\.(\\d+)\\.(\\d+)$") 13 | private val groups get() = pattern.matchEntire(full)?.groups 14 | val major get() = groups?.get(1) 15 | val minor get() = groups?.get(2) 16 | val patch get() = groups?.get(3) 17 | 18 | init { 19 | if (!pattern.matches(full)) { 20 | throw IllegalArgumentException("Version string does not match expected pattern. Got: $full") 21 | } 22 | } 23 | 24 | override fun toString(): String = full 25 | } 26 | -------------------------------------------------------------------------------- /structures/src/commonMain/kotlin/inkapplications/shade/structures/parameters/PowerParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures.parameters 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * Power settings for a device 7 | */ 8 | @Serializable 9 | data class PowerParameters( 10 | /** 11 | * Simple on/off state for the device 12 | */ 13 | val on: Boolean? = null, 14 | ) 15 | -------------------------------------------------------------------------------- /structures/src/jvmTest/kotlin/inkapplications/shade/structures/VersionStringTest.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.structures 2 | 3 | import junit.framework.TestCase.assertTrue 4 | import org.junit.Test 5 | import kotlin.test.assertEquals 6 | import kotlin.test.assertFalse 7 | 8 | class VersionStringTest { 9 | @Test 10 | fun testVersionParsing() { 11 | val version = VersionString("1.2.3") 12 | 13 | assertEquals("1", version.major?.value) 14 | assertEquals("2", version.minor?.value) 15 | assertEquals("3", version.patch?.value) 16 | } 17 | 18 | @Test 19 | fun testInvalidVersion() { 20 | val result = runCatching { 21 | VersionString("asdf") 22 | } 23 | 24 | assertFalse(result.isSuccess) 25 | assertTrue(result.exceptionOrNull() is IllegalArgumentException) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /zones/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("library") 3 | kotlin("plugin.serialization") 4 | id("ink.publishing") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | implementation(libs.serialization.json) 12 | implementation(projects.internals) 13 | implementation(projects.serialization) 14 | api(projects.structures) 15 | 16 | api(libs.coroutines.core) 17 | } 18 | } 19 | 20 | val commonTest by getting { 21 | dependencies { 22 | implementation(libs.test.core) 23 | implementation(libs.test.annotations) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /zones/src/commonMain/kotlin/inkapplications/shade/zones/ShadeZonesModule.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.zones 2 | 3 | import inkapplications.shade.internals.InternalsModule 4 | 5 | class ShadeZonesModule( 6 | internalsModule: InternalsModule, 7 | ) { 8 | val zones: ZoneControls = ShadeZones(internalsModule.hueHttpClient) 9 | } 10 | -------------------------------------------------------------------------------- /zones/src/commonMain/kotlin/inkapplications/shade/zones/parameters/ZoneCreateParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.zones.parameters 2 | 3 | import inkapplications.shade.structures.ResourceReference 4 | import inkapplications.shade.structures.SegmentMetadata 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Parameters used for creating a new Zone on the Hue bridge 9 | */ 10 | @Serializable 11 | data class ZoneCreateParameters( 12 | /** 13 | * Configuration metadata for the Zone. 14 | */ 15 | val metadata: SegmentMetadata, 16 | 17 | /** 18 | * Child resources to group by the Zone. 19 | */ 20 | val children: List, 21 | ) 22 | -------------------------------------------------------------------------------- /zones/src/commonMain/kotlin/inkapplications/shade/zones/parameters/ZoneUpdateParameters.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.zones.parameters 2 | 3 | import inkapplications.shade.structures.ResourceReference 4 | import inkapplications.shade.structures.SegmentMetadataUpdate 5 | import kotlinx.serialization.Serializable 6 | 7 | /** 8 | * Parameters for updating a Zone's information. 9 | */ 10 | @Serializable 11 | data class ZoneUpdateParameters( 12 | /** 13 | * Metadata configuration for the zone. 14 | */ 15 | val metadata: SegmentMetadataUpdate? = null, 16 | 17 | /** 18 | * Child services to group by the Zone. 19 | */ 20 | val children: List? = null, 21 | ) 22 | -------------------------------------------------------------------------------- /zones/src/commonMain/kotlin/inkapplications/shade/zones/structures/Zone.kt: -------------------------------------------------------------------------------- 1 | package inkapplications.shade.zones.structures 2 | 3 | import inkapplications.shade.structures.ResourceId 4 | import inkapplications.shade.structures.ResourceReference 5 | import inkapplications.shade.structures.SegmentMetadata 6 | import kotlinx.serialization.Serializable 7 | 8 | /** 9 | * State and capabilities of a zone resource. 10 | */ 11 | @Serializable 12 | data class Zone( 13 | /** 14 | * Unique v2 ID for the zone. 15 | */ 16 | val id: ResourceId, 17 | 18 | /** 19 | * References all services aggregating control and state of children 20 | * in the group. 21 | * 22 | * This includes all services grouped in the group hierarchy given by 23 | * child relation This includes all services of a device grouped in the 24 | * group hierarchy given by child relation Aggregation is per service type, 25 | * ie. every service type which can be grouped has a corresponding 26 | * definition of grouped type Supported types "light" 27 | */ 28 | val services: List, 29 | 30 | /** 31 | * Configuration metatdata for the room/zone type. 32 | */ 33 | val metadata: SegmentMetadata, 34 | 35 | /** 36 | * Children devices of this zone. 37 | */ 38 | val children: List, 39 | ) 40 | --------------------------------------------------------------------------------