diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/Topics.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/Topics.kt index 763ee31..a6c5b9f 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/Topics.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/Topics.kt @@ -481,6 +481,9 @@ class Topics(vararg val topics: String) { /** MQTT topic string for the "ChangePersonMetadataValueMapi" command. */ val CHANGE_PERSON_METADATA_VALUE = "xs3/1/cmd/ChangePersonMetadataValueMapi" + /** MQTT topic string for the "ChangeInstallationPointMetadataValueMapi" command. */ + val CHANGE_INSTALLATION_POINT_METADATA_VALUE = + "xs3/1/cmd/ChangeInstallationPointMetadataValueMapi" /** MQTT topic string for the "ChangeAuthorizationProfileMetadataValueMapi" command. */ val CHANGE_AUTHORIZATION_PROFILE_METADATA_VALUE = "xs3/1/cmd/ChangeAuthorizationProfileMetadataValueMapi" diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectInstallationPointExt.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectInstallationPointExt.kt index 9bd34e6..938aad1 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectInstallationPointExt.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectInstallationPointExt.kt @@ -486,3 +486,32 @@ suspend fun XesarConnect.configureBluetoothStateAsync( requestConfig, ) } + +/** + * Changes the value of custom data field of an installation point. + * + * @param id The ID of the installation point. + * @param metadataId The metadataID of the data field. + * @param value The new value of the field. + * @param requestConfig The request configuration (optional). + */ +suspend fun XesarConnect.changeInstallationPointMetadataValueAsync( + id: UUID, + metadataId: UUID, + value: String, + requestConfig: XesarConnect.RequestConfig = buildRequestConfig(), +): SingleEventResult { + return sendCommandAsync( + Topics.Command.CHANGE_INSTALLATION_POINT_METADATA_VALUE, + Topics.Event.INSTALLATION_POINT_CHANGED, + true, + ChangeInstallationPointMetadataValueMapi( + config.uuidGenerator.generateId(), + id, + metadataId, + value, + token, + ), + requestConfig, + ) +} diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ChangeInstallationPointMetadataValueMapi.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ChangeInstallationPointMetadataValueMapi.kt new file mode 100644 index 0000000..cbce4c5 --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ChangeInstallationPointMetadataValueMapi.kt @@ -0,0 +1,23 @@ +package com.open200.xesar.connect.messages.command + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents a command POJO to change a custom data field value for an installation point. + * + * @param commandId The id of the command. + * @param id The id of the installation point. + * @param metadataId The id of the custom data field. + * @param value The new value of the custom data field. + * @param token The token of the command. + */ +@Serializable +data class ChangeInstallationPointMetadataValueMapi( + override val commandId: @Serializable(with = UUIDSerializer::class) UUID, + val id: @Serializable(with = UUIDSerializer::class) UUID, + val metadataId: @Serializable(with = UUIDSerializer::class) UUID, + val value: String, + val token: String, +) : Command diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/InstallationPointChanged.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/InstallationPointChanged.kt index 972d84b..81084bb 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/InstallationPointChanged.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/InstallationPointChanged.kt @@ -1,6 +1,7 @@ package com.open200.xesar.connect.messages.event import com.open200.xesar.connect.messages.BluetoothState +import com.open200.xesar.connect.messages.EntityMetadata import com.open200.xesar.connect.messages.PersonalLog import com.open200.xesar.connect.messages.query.TimeProfile import com.open200.xesar.connect.utils.UUIDSerializer @@ -28,6 +29,8 @@ import kotlinx.serialization.Serializable * @param timeProfileData The time profile data. * @param openDoor Whether the door is open. * @param bluetoothState The Bluetooth state of the installation point (optional). + * @param entityMetadata Contains the information for all defined custom data fields for the + * installation point. */ @Serializable data class InstallationPointChanged( @@ -50,4 +53,5 @@ data class InstallationPointChanged( val timeProfileData: TimeProfile? = null, val openDoor: Boolean? = null, val bluetoothState: BluetoothState? = null, + val entityMetadata: List? = null, ) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/query/InstallationPoint.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/query/InstallationPoint.kt index 1e41a4d..a7a2190 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/query/InstallationPoint.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/query/InstallationPoint.kt @@ -2,6 +2,7 @@ package com.open200.xesar.connect.messages.query import com.open200.xesar.connect.messages.BluetoothState import com.open200.xesar.connect.messages.ComponentType +import com.open200.xesar.connect.messages.EntityMetadata import com.open200.xesar.connect.utils.UUIDSerializer import java.util.* import kotlinx.serialization.Serializable @@ -34,6 +35,8 @@ import kotlinx.serialization.Serializable * @param accessId The access ID of the installation point (optional). * @param secure Indicates if the installation point is secure (optional). * @param bluetoothState The Bluetooth state of the installation point (optional). + * @param entityMetadata Contains the information for all defined custom data fields for the + * installation point. */ @Serializable data class InstallationPoint( @@ -60,6 +63,7 @@ data class InstallationPoint( val accessId: Long? = null, val secure: Boolean? = null, val bluetoothState: BluetoothState? = null, + val entityMetadata: List? = null, ) : QueryListResource, QueryElementResource { companion object { const val QUERY_RESOURCE = "installation-points" diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/InstallationPointElementTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/InstallationPointElementTest.kt index 0ae9143..2371580 100644 --- a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/InstallationPointElementTest.kt +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/InstallationPointElementTest.kt @@ -15,7 +15,7 @@ class InstallationPointElementTest : ) val installationPointString = - "{\"requestId\":\"00000000-1281-42c0-9a15-c5844850c748\",\"response\":{\"id\":\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\",\"name\":\"door 1 entry point\",\"description\":\"door 1\",\"installationId\":\"1a61d397-0afc-436a-8b75-521577d2aa02\",\"installationType\":\"door 1\",\"linkedInstallationPoints\":[\"ed6236d0-a47c-46be-8495-d4755c38f103\",\"7cbcddaa-50c3-48fb-8e5a-56bab47d8f81\"],\"onlineStatus\":\"connected\",\"componentType\":\"Cylinder\",\"releaseDurationShort\":5,\"releaseDurationLong\":20,\"logMode\":\"dontSave\",\"days\":30,\"manualOfficeMode\":false,\"shopMode\":false,\"openDoor\":true,\"bleStatus\":\"NO_BLE\",\"timeProfileName\":null,\"batteryCondition\":null,\"timeProfileId\":null,\"accessId\":null,\"secure\":null,\"bluetoothState\":null}}" + "{\"requestId\":\"00000000-1281-42c0-9a15-c5844850c748\",\"response\":{\"id\":\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\",\"name\":\"door 1 entry point\",\"description\":\"door 1\",\"installationId\":\"1a61d397-0afc-436a-8b75-521577d2aa02\",\"installationType\":\"door 1\",\"linkedInstallationPoints\":[\"ed6236d0-a47c-46be-8495-d4755c38f103\",\"7cbcddaa-50c3-48fb-8e5a-56bab47d8f81\"],\"onlineStatus\":\"connected\",\"componentType\":\"Cylinder\",\"releaseDurationShort\":5,\"releaseDurationLong\":20,\"logMode\":\"dontSave\",\"days\":30,\"manualOfficeMode\":false,\"shopMode\":false,\"openDoor\":true,\"bleStatus\":\"NO_BLE\",\"timeProfileName\":null,\"batteryCondition\":null,\"timeProfileId\":null,\"accessId\":null,\"secure\":null,\"bluetoothState\":null,\"entityMetadata\":[{\"id\":\"123e4567-e89b-12d3-a456-426614174000\",\"name\":\"type\",\"value\":\"double door\"},{\"id\":\"0f8fad5b-d9cb-469f-a165-70867728950e\",\"name\":\"color\",\"value\":null}]}}" test("encoding QueryResponseElement for an installation Point") { val installationPointEncoded = encodeQueryElement(installationPoint) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/InstallationPointListTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/InstallationPointListTest.kt index 45fae75..4e1a904 100644 --- a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/InstallationPointListTest.kt +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/InstallationPointListTest.kt @@ -29,7 +29,7 @@ class InstallationPointListTest : ) val installationPointString = - "{\"requestId\":\"00000000-1281-40ae-89d7-5c541d77a757\",\"response\":{\"data\":[{\"id\":\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\",\"name\":\"door 1 entry point\",\"description\":\"door 1\",\"installationId\":\"1a61d397-0afc-436a-8b75-521577d2aa02\",\"installationType\":\"door 1\",\"linkedInstallationPoints\":[\"ed6236d0-a47c-46be-8495-d4755c38f103\",\"7cbcddaa-50c3-48fb-8e5a-56bab47d8f81\"],\"onlineStatus\":\"connected\",\"componentType\":\"Cylinder\",\"releaseDurationShort\":5,\"releaseDurationLong\":20,\"logMode\":\"dontSave\",\"days\":30,\"manualOfficeMode\":false,\"shopMode\":false,\"openDoor\":true,\"bleStatus\":\"NO_BLE\",\"timeProfileName\":null,\"batteryCondition\":null,\"timeProfileId\":null,\"accessId\":null,\"secure\":null,\"bluetoothState\":null},{\"id\":\"a4c838a8-f6be-49e0-abee-c1d3b2897279\",\"name\":\"door 2 entry point\",\"description\":\"door 2\",\"installationId\":\"0cefd48b-969e-43eb-aad6-98553288eb4d\",\"installationType\":\"door 2\",\"linkedInstallationPoints\":[\"ed6236d0-a47c-46be-8495-d4755c38f103\",\"7cbcddaa-50c3-48fb-8e5a-56bab47d8f81\"],\"onlineStatus\":\"connected\",\"componentType\":\"Cylinder\",\"releaseDurationShort\":5,\"releaseDurationLong\":20,\"logMode\":\"dontSave\",\"days\":30,\"manualOfficeMode\":false,\"shopMode\":false,\"openDoor\":true,\"bleStatus\":\"NO_BLE\",\"timeProfileName\":null,\"batteryCondition\":null,\"timeProfileId\":null,\"accessId\":null,\"secure\":null,\"bluetoothState\":null}],\"totalCount\":2,\"filterCount\":2}}" + "{\"requestId\":\"00000000-1281-40ae-89d7-5c541d77a757\",\"response\":{\"data\":[{\"id\":\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\",\"name\":\"door 1 entry point\",\"description\":\"door 1\",\"installationId\":\"1a61d397-0afc-436a-8b75-521577d2aa02\",\"installationType\":\"door 1\",\"linkedInstallationPoints\":[\"ed6236d0-a47c-46be-8495-d4755c38f103\",\"7cbcddaa-50c3-48fb-8e5a-56bab47d8f81\"],\"onlineStatus\":\"connected\",\"componentType\":\"Cylinder\",\"releaseDurationShort\":5,\"releaseDurationLong\":20,\"logMode\":\"dontSave\",\"days\":30,\"manualOfficeMode\":false,\"shopMode\":false,\"openDoor\":true,\"bleStatus\":\"NO_BLE\",\"timeProfileName\":null,\"batteryCondition\":null,\"timeProfileId\":null,\"accessId\":null,\"secure\":null,\"bluetoothState\":null,\"entityMetadata\":[{\"id\":\"123e4567-e89b-12d3-a456-426614174000\",\"name\":\"type\",\"value\":\"double door\"},{\"id\":\"0f8fad5b-d9cb-469f-a165-70867728950e\",\"name\":\"color\",\"value\":null}]},{\"id\":\"a4c838a8-f6be-49e0-abee-c1d3b2897279\",\"name\":\"door 2 entry point\",\"description\":\"door 2\",\"installationId\":\"0cefd48b-969e-43eb-aad6-98553288eb4d\",\"installationType\":\"door 2\",\"linkedInstallationPoints\":[\"ed6236d0-a47c-46be-8495-d4755c38f103\",\"7cbcddaa-50c3-48fb-8e5a-56bab47d8f81\"],\"onlineStatus\":\"connected\",\"componentType\":\"Cylinder\",\"releaseDurationShort\":5,\"releaseDurationLong\":20,\"logMode\":\"dontSave\",\"days\":30,\"manualOfficeMode\":false,\"shopMode\":false,\"openDoor\":true,\"bleStatus\":\"NO_BLE\",\"timeProfileName\":null,\"batteryCondition\":null,\"timeProfileId\":null,\"accessId\":null,\"secure\":null,\"bluetoothState\":null,\"entityMetadata\":[{\"id\":\"123e4567-e89b-12d3-a456-426614174000\",\"name\":\"type\",\"value\":\"double door\"},{\"id\":\"0f8fad5b-d9cb-469f-a165-70867728950e\",\"name\":\"color\",\"value\":null}]}],\"totalCount\":2,\"filterCount\":2}}" test("encoding installationPointList for a list of installation Points") { val installationPointEncoded = encodeQueryList(installationPointList) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ChangeInstallationPointMetadataValueTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ChangeInstallationPointMetadataValueTest.kt new file mode 100644 index 0000000..c403c11 --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ChangeInstallationPointMetadataValueTest.kt @@ -0,0 +1,105 @@ +package com.open200.xesar.connect.it.command + +import com.open200.xesar.connect.Topics +import com.open200.xesar.connect.XesarConnect +import com.open200.xesar.connect.XesarMqttClient +import com.open200.xesar.connect.extension.changeInstallationPointMetadataValueAsync +import com.open200.xesar.connect.it.MosquittoContainer +import com.open200.xesar.connect.messages.EntityMetadata +import com.open200.xesar.connect.messages.event.ApiEvent +import com.open200.xesar.connect.messages.event.InstallationPointChanged +import com.open200.xesar.connect.messages.event.encodeEvent +import io.kotest.common.runBlocking +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.testcontainers.perProject +import io.kotest.matchers.equals.shouldBeEqual +import io.mockk.coEvery +import java.util.* +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch + +class ChangeInstallationPointMetadataValueTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + test("change installation point metadata value") { + coEvery { config.uuidGenerator.generateId() } + .returns(UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757")) + + val installationPointId = UUID.fromString("11111111-2222-3333-4444-555555555555") + val metadataId = UUID.fromString("aaaaaaaa-0000-0000-0000-000000000001") + + runBlocking { + val simulatedBackendReady = CompletableDeferred() + val commandReceived = CompletableDeferred() + + launch { + XesarMqttClient.connectAsync(config).await().use { client -> + client.subscribeAsync(arrayOf(Topics.ALL_TOPICS)).await() + + client.onMessage = { topic, payload -> + when (topic) { + Topics.Command.CHANGE_INSTALLATION_POINT_METADATA_VALUE -> { + commandReceived.complete(payload.decodeToString()) + } + } + } + + simulatedBackendReady.complete(Unit) + + val commandContent = commandReceived.await() + + commandContent.shouldBeEqual( + "{\"commandId\":\"00000000-1281-40ae-89d7-5c541d77a757\",\"id\":\"11111111-2222-3333-4444-555555555555\",\"metadataId\":\"aaaaaaaa-0000-0000-0000-000000000001\",\"value\":\"Test Value\",\"token\":\"JDJhJDEwJDFSNEljZ2FaRUNXUXBTQ25XN05KbE9qRzFHQ1VjMzkvWTBVcFpZb1M4Vmt0dnJYZ0tJVFBx\"}" + ) + + val apiEvent = + ApiEvent( + UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757"), + InstallationPointChanged( + aggregateId = installationPointId, + entityMetadata = + listOf( + EntityMetadata( + id = metadataId, + name = "test", + value = "Test Value", + ) + ), + ), + ) + + client + .publishAsync( + Topics.Event.INSTALLATION_POINT_CHANGED, + encodeEvent(apiEvent), + ) + .await() + } + } + + launch { + simulatedBackendReady.await() + + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.INSTALLATION_POINT_CHANGED)).await() + + val result = + api.changeInstallationPointMetadataValueAsync( + id = installationPointId, + metadataId = metadataId, + value = "Test Value", + ) + .await() + + result.aggregateId.shouldBeEqual(installationPointId) + result.entityMetadata!! + .single { it.id == metadataId } + .value + ?.shouldBeEqual("Test Value") + } + } + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/util/fixture/InstallationPointFixture.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/util/fixture/InstallationPointFixture.kt index bd6dce0..843f40d 100644 --- a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/util/fixture/InstallationPointFixture.kt +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/util/fixture/InstallationPointFixture.kt @@ -1,6 +1,7 @@ package com.open200.xesar.connect.util.fixture import com.open200.xesar.connect.messages.ComponentType +import com.open200.xesar.connect.messages.EntityMetadata import com.open200.xesar.connect.messages.query.InstallationPoint import com.open200.xesar.connect.messages.query.OnlineStatus import java.util.* @@ -28,5 +29,18 @@ object InstallationPointFixture { shopMode = false, openDoor = true, bleStatus = "NO_BLE", + entityMetadata = + listOf( + EntityMetadata( + id = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), + name = "type", + value = "double door", + ), + EntityMetadata( + id = UUID.fromString("0f8fad5b-d9cb-469f-a165-70867728950e"), + name = "color", + value = null, + ), + ), ) }