diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f6bcbbb..daffa893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Change Log 2.3.0 ----- +## Added: + + - HomeKit module for integrating with HomeKit ecosystem. + ### Fixed: - Native targets now use Curl based Http client, which should support HTTPS diff --git a/core/api/core.api b/core/api/core.api index a161cd74..acb47971 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -11,6 +11,7 @@ public final class inkapplications/shade/core/Shade { public final fun getConfiguration ()Linkapplications/shade/structures/HueConfigurationContainer; public final fun getDevices ()Linkapplications/shade/devices/DeviceControls; public final fun getGroupedLights ()Linkapplications/shade/groupedlights/GroupedLightControls; + public final fun getHomekit ()Linkapplications/shade/homekit/HomekitControls; public final fun getLights ()Linkapplications/shade/lights/LightControls; public final fun getOnlineDiscovery ()Linkapplications/shade/discover/BridgeDiscovery; public final fun getResources ()Linkapplications/shade/resources/ResourceControls; diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1d340b68..9abdfc51 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -19,6 +19,7 @@ kotlin { api(projects.scenes) api(projects.structures) api(projects.zones) + api(projects.homekit) api(libs.kimchi.logger) } diff --git a/core/src/commonMain/kotlin/inkapplications/shade/core/Shade.kt b/core/src/commonMain/kotlin/inkapplications/shade/core/Shade.kt index 661a12f5..60c22df9 100644 --- a/core/src/commonMain/kotlin/inkapplications/shade/core/Shade.kt +++ b/core/src/commonMain/kotlin/inkapplications/shade/core/Shade.kt @@ -5,6 +5,7 @@ import inkapplications.shade.devices.ShadeDevicesModule import inkapplications.shade.discover.DiscoverModule import inkapplications.shade.events.EventsModule import inkapplications.shade.groupedlights.ShadeGroupedLightsModule +import inkapplications.shade.homekit.ShadeHomekitModule import inkapplications.shade.internals.InternalsModule import inkapplications.shade.lights.ShadeLightsModule import inkapplications.shade.resources.ShadeResourcesModule @@ -57,4 +58,5 @@ class Shade( val groupedLights = ShadeGroupedLightsModule(internalsModule, eventsModule).groupedLights val resources = ShadeResourcesModule(internalsModule).resources val scenes = ShadeScenesModule(internalsModule).scenes + val homekit = ShadeHomekitModule(internalsModule).homekit } diff --git a/homekit/api/homekit.api b/homekit/api/homekit.api new file mode 100644 index 00000000..e903dc73 --- /dev/null +++ b/homekit/api/homekit.api @@ -0,0 +1,135 @@ +public abstract interface class inkapplications/shade/homekit/HomekitControls { + public abstract fun getHomekit-klA6Vuc (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun listHomekit (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun updateHomekit-bKh5c1I (Ljava/lang/String;Linkapplications/shade/homekit/parameters/HomekitUpdateParameters;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class inkapplications/shade/homekit/ShadeHomekitModule { + public fun (Linkapplications/shade/internals/InternalsModule;)V + public final fun getHomekit ()Linkapplications/shade/homekit/HomekitControls; +} + +public final class inkapplications/shade/homekit/parameters/HomekitUpdateParameters { + public static final field Companion Linkapplications/shade/homekit/parameters/HomekitUpdateParameters$Companion; + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-135DQNc ()Ljava/lang/String; + public final fun copy-R0FzwdQ (Ljava/lang/String;)Linkapplications/shade/homekit/parameters/HomekitUpdateParameters; + public static synthetic fun copy-R0FzwdQ$default (Linkapplications/shade/homekit/parameters/HomekitUpdateParameters;Ljava/lang/String;ILjava/lang/Object;)Linkapplications/shade/homekit/parameters/HomekitUpdateParameters; + public fun equals (Ljava/lang/Object;)Z + public final fun getAction-135DQNc ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class inkapplications/shade/homekit/parameters/HomekitUpdateParameters$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Linkapplications/shade/homekit/parameters/HomekitUpdateParameters$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Linkapplications/shade/homekit/parameters/HomekitUpdateParameters; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Linkapplications/shade/homekit/parameters/HomekitUpdateParameters;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class inkapplications/shade/homekit/parameters/HomekitUpdateParameters$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class inkapplications/shade/homekit/structures/Homekit { + public static final field Companion Linkapplications/shade/homekit/structures/Homekit$Companion; + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-XbiYvy0 ()Ljava/lang/String; + public final fun component2-1y3a6N0 ()Ljava/lang/String; + public final fun component3-iTYh7UM ()Ljava/lang/String; + public final fun copy-r9E5AgY (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Linkapplications/shade/homekit/structures/Homekit; + public static synthetic fun copy-r9E5AgY$default (Linkapplications/shade/homekit/structures/Homekit;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Linkapplications/shade/homekit/structures/Homekit; + public fun equals (Ljava/lang/Object;)Z + public final fun getId-XbiYvy0 ()Ljava/lang/String; + public final fun getStatus-iTYh7UM ()Ljava/lang/String; + public final fun getType-1y3a6N0 ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class inkapplications/shade/homekit/structures/Homekit$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Linkapplications/shade/homekit/structures/Homekit$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Linkapplications/shade/homekit/structures/Homekit; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Linkapplications/shade/homekit/structures/Homekit;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class inkapplications/shade/homekit/structures/Homekit$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class inkapplications/shade/homekit/structures/HomekitAction { + public static final field Companion Linkapplications/shade/homekit/structures/HomekitAction$Companion; + public static final synthetic fun box-impl (Ljava/lang/String;)Linkapplications/shade/homekit/structures/HomekitAction; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public final fun getKey ()Ljava/lang/String; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public final synthetic class inkapplications/shade/homekit/structures/HomekitAction$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Linkapplications/shade/homekit/structures/HomekitAction$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize-_n2LV64 (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize-gK-Otuo (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class inkapplications/shade/homekit/structures/HomekitAction$Companion { + public final fun getReset-_IGrqpU ()Ljava/lang/String; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class inkapplications/shade/homekit/structures/HomekitStatus { + public static final field Companion Linkapplications/shade/homekit/structures/HomekitStatus$Companion; + public static final synthetic fun box-impl (Ljava/lang/String;)Linkapplications/shade/homekit/structures/HomekitStatus; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public final fun getKey ()Ljava/lang/String; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public final synthetic class inkapplications/shade/homekit/structures/HomekitStatus$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Linkapplications/shade/homekit/structures/HomekitStatus$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize-5d-ibaU (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize-4oD0JvU (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class inkapplications/shade/homekit/structures/HomekitStatus$Companion { + public final fun getPaired-iTYh7UM ()Ljava/lang/String; + public final fun getPairing-iTYh7UM ()Ljava/lang/String; + public final fun getUnpaired-iTYh7UM ()Ljava/lang/String; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + diff --git a/homekit/build.gradle.kts b/homekit/build.gradle.kts new file mode 100644 index 00000000..c3624bbb --- /dev/null +++ b/homekit/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + id("library") + kotlin("plugin.serialization") + id("ink.publishing") +} + +kotlin { + sourceSets { + val commonMain by getting { + dependencies { + implementation(libs.serialization.json) + implementation(projects.internals) + implementation(projects.serialization) + api(projects.structures) + + api(libs.coroutines.core) + } + } + + val commonTest by getting { + dependencies { + implementation(libs.test.core) + implementation(libs.test.annotations) + } + } + } +} diff --git a/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/HomekitControls.kt b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/HomekitControls.kt new file mode 100644 index 00000000..f920f2d8 --- /dev/null +++ b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/HomekitControls.kt @@ -0,0 +1,31 @@ +package inkapplications.shade.homekit + +import inkapplications.shade.homekit.parameters.HomekitUpdateParameters +import inkapplications.shade.homekit.structures.Homekit +import inkapplications.shade.structures.ResourceId +import inkapplications.shade.structures.ResourceReference + +/** + * Actions to get homekit resources in the hue system. + */ +interface HomekitControls { + /** + * Get the state of a single homekit resource. + * + * @param id The resource ID of the homekit resource to fetch data for. + */ + suspend fun getHomekit(id: ResourceId): Homekit + + /** + * Get a list of homekit resources configured on the hue service. + */ + suspend fun listHomekit(): List + + /** + * Invoke an action on the specified homekit resource. + * + * @param id The resource ID of the homekit resource to be updated + * @param parameters Data about the homekit resource to be updated + */ + suspend fun updateHomekit(id: ResourceId, parameters: HomekitUpdateParameters): ResourceReference +} diff --git a/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/ShadeHomekit.kt b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/ShadeHomekit.kt new file mode 100644 index 00000000..9bea63e3 --- /dev/null +++ b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/ShadeHomekit.kt @@ -0,0 +1,36 @@ +package inkapplications.shade.homekit + +import inkapplications.shade.homekit.parameters.HomekitUpdateParameters +import inkapplications.shade.homekit.structures.Homekit +import inkapplications.shade.internals.HueHttpClient +import inkapplications.shade.internals.getData +import inkapplications.shade.internals.putData +import inkapplications.shade.structures.ResourceId +import inkapplications.shade.structures.ResourceReference + +/** + * Implements homekit controls via the hue client. + */ +internal class ShadeHomekit( + private val hueHttpClient: HueHttpClient, +) : HomekitControls { + override suspend fun getHomekit(id: ResourceId): Homekit { + return hueHttpClient.getData>("resource", "homekit", id.value).single() + } + + override suspend fun listHomekit(): List { + return hueHttpClient.getData("resource", "homekit") + } + + override suspend fun updateHomekit( + id: ResourceId, + parameters: HomekitUpdateParameters + ): ResourceReference { + val response: List = hueHttpClient.putData( + body = parameters, + pathSegments = arrayOf("resource", "homekit", id.value), + ) + + return response.single() + } +} diff --git a/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/ShadeHomekitModule.kt b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/ShadeHomekitModule.kt new file mode 100644 index 00000000..d029b45b --- /dev/null +++ b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/ShadeHomekitModule.kt @@ -0,0 +1,12 @@ +package inkapplications.shade.homekit + +import inkapplications.shade.internals.InternalsModule + +/** + * Provides Access to homekit control services. + */ +class ShadeHomekitModule( + internalsModule: InternalsModule, +) { + val homekit: HomekitControls = ShadeHomekit(internalsModule.hueHttpClient) +} diff --git a/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/parameters/HomekitUpdateParameters.kt b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/parameters/HomekitUpdateParameters.kt new file mode 100644 index 00000000..c70f39ea --- /dev/null +++ b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/parameters/HomekitUpdateParameters.kt @@ -0,0 +1,16 @@ +package inkapplications.shade.homekit.parameters + +import inkapplications.shade.homekit.structures.HomekitAction +import kotlinx.serialization.Serializable + +/** + * Options for updating a homekit resource. + */ +@Serializable +data class HomekitUpdateParameters( + /** + * Action to perform on the homekit resource. + */ + val action: HomekitAction? = null, +) + diff --git a/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/Homekit.kt b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/Homekit.kt new file mode 100644 index 00000000..620d7078 --- /dev/null +++ b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/Homekit.kt @@ -0,0 +1,27 @@ +package inkapplications.shade.homekit.structures + +import inkapplications.shade.structures.ResourceId +import inkapplications.shade.structures.ResourceType +import kotlinx.serialization.Serializable + +/** + * State of the homekit resource. + */ +@Serializable +data class Homekit( + /** + * Unique identifier representing a specific homekit resource instance. + */ + val id: ResourceId, + + /** + * Type of the supported resources. + */ + val type: ResourceType = ResourceType.Homekit, + + /** + * Status indicating whether homekit is paired, currently open for pairing, or unpaired. + */ + val status: HomekitStatus, +) + diff --git a/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/HomekitAction.kt b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/HomekitAction.kt new file mode 100644 index 00000000..efc266b0 --- /dev/null +++ b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/HomekitAction.kt @@ -0,0 +1,25 @@ +package inkapplications.shade.homekit.structures + +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline + +/** + * Action that can be performed on a homekit resource. + */ +@JvmInline +@Serializable +value class HomekitAction(val key: String) { + override fun toString(): String = key + + companion object { + /** + * Reset homekit. + * + * Removes all pairings and reset state and Bonjour service to + * factory settings. + * + * The Homekit will start functioning after approximately 10 seconds. + */ + val Reset = HomekitAction("homekit_reset") + } +} diff --git a/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/HomekitStatus.kt b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/HomekitStatus.kt new file mode 100644 index 00000000..07ee0077 --- /dev/null +++ b/homekit/src/commonMain/kotlin/inkapplications/shade/homekit/structures/HomekitStatus.kt @@ -0,0 +1,36 @@ +package inkapplications.shade.homekit.structures + +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline + +/** + * Status indicating whether homekit is paired, currently open for pairing, or unpaired. + * + * Transitions: + * - unpaired > pairing – pushlink button press or power cycle + * - pairing > paired – through HAP + * - pairing > unpaired – 10 minutes + * - paired > unpaired – homekit reset + */ +@JvmInline +@Serializable +value class HomekitStatus(val key: String) { + override fun toString(): String = key + + companion object { + /** + * Homekit is paired with the bridge. + */ + val Paired = HomekitStatus("paired") + + /** + * Homekit is currently in pairing mode. + */ + val Pairing = HomekitStatus("pairing") + + /** + * Homekit is not paired with the bridge. + */ + val Unpaired = HomekitStatus("unpaired") + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 6b32f664..a32864c9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,6 +8,7 @@ include("devices") include("discover") include("events") include("grouped-lights") +include("homekit") include("internals") include("lights") include("resources")