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 a6c5b9f..c027a71 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,8 @@ 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 "ChangeZoneMetadataValueMapi" command. */ + val CHANGE_ZONE_METADATA_VALUE = "xs3/1/cmd/ChangeZoneMetadataValueMapi" /** MQTT topic string for the "ChangeInstallationPointMetadataValueMapi" command. */ val CHANGE_INSTALLATION_POINT_METADATA_VALUE = "xs3/1/cmd/ChangeInstallationPointMetadataValueMapi" diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectZoneExt.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectZoneExt.kt index dc920c4..bebff88 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectZoneExt.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectZoneExt.kt @@ -167,6 +167,35 @@ suspend fun XesarConnect.removeInstallationPointFromZoneAsync( ) } +/** + * Changes the value of custom data field of a zone. + * + * @param id The ID of the zone. + * @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.changeZoneMetadataValueAsync( + id: UUID, + metadataId: UUID, + value: String, + requestConfig: XesarConnect.RequestConfig = buildRequestConfig(), +): SingleEventResult { + return sendCommandAsync( + Topics.Command.CHANGE_ZONE_METADATA_VALUE, + Topics.Event.ZONE_CHANGED, + true, + ChangeZoneMetadataValueMapi( + config.uuidGenerator.generateId(), + id, + metadataId, + value, + token, + ), + requestConfig, + ) +} + /** * Retrieves a cold stream of [Zone] objects, fetching them incrementally in smaller,more manageable * chunks rather than retrieving the entire dataset at once. Use [Query.Params.pageLimit] to choose diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ChangeZoneMetadataValueMapi.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ChangeZoneMetadataValueMapi.kt new file mode 100644 index 0000000..022179e --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ChangeZoneMetadataValueMapi.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 a zone. + * + * @param commandId The id of the command. + * @param id The id of the zone. + * @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 ChangeZoneMetadataValueMapi( + 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/ZoneChanged.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/ZoneChanged.kt index 380d879..46a6f48 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/ZoneChanged.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/ZoneChanged.kt @@ -1,5 +1,6 @@ package com.open200.xesar.connect.messages.event +import com.open200.xesar.connect.messages.EntityMetadata import com.open200.xesar.connect.utils.UUIDSerializer import java.util.* import kotlinx.serialization.Serializable @@ -10,10 +11,12 @@ import kotlinx.serialization.Serializable * @param name The name of the zone. * @param description The description of the zone. * @param id The id of the zone. + * @param entityMetadata Contains the information for all defined custom data fields for the zone. */ @Serializable data class ZoneChanged( val name: String, val description: String, @Serializable(with = UUIDSerializer::class) val id: UUID, + val entityMetadata: List? = null, ) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/query/Zone.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/query/Zone.kt index 3df3440..9e31083 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/query/Zone.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/query/Zone.kt @@ -1,9 +1,22 @@ package com.open200.xesar.connect.messages.query +import com.open200.xesar.connect.messages.EntityMetadata import com.open200.xesar.connect.utils.UUIDSerializer import java.util.* import kotlinx.serialization.Serializable +/** + * Represents a zone. + * + * @param installationPoints The list of installation points of the zone (optional). + * @param partitionId The partition identifier of the zone. + * @param installationPointCount The installationPointCount of the zone (optional). + * @param name The name of the zone. + * @param description The description of the zone (optional). + * @param id The id of the zone. + * @param entityMetadata Contains the information for all defined custom data fields for the zone + * (optional). + */ @Serializable data class Zone( val installationPoints: List<@Serializable(with = UUIDSerializer::class) UUID> = emptyList(), @@ -12,6 +25,7 @@ data class Zone( val name: String, val description: String? = null, @Serializable(with = UUIDSerializer::class) val id: UUID, + val entityMetadata: List? = null, ) : QueryListResource, QueryElementResource { companion object { diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/ZoneElementTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/ZoneElementTest.kt index 2f4ff5c..3e8782a 100644 --- a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/ZoneElementTest.kt +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/ZoneElementTest.kt @@ -16,7 +16,7 @@ class ZoneElementTest : ) val zoneString = - "{\"requestId\":\"d385ab22-0f51-4b97-9ecd-b8ff3fd4fcb6\",\"response\":{\"installationPoints\":[\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\"],\"partitionId\":\"7b4399a0-21ce-4bee-ba43-e06e291248d2\",\"installationPointCount\":0,\"name\":\"zone name\",\"description\":\"zone description\",\"id\":\"497f6eca-6276-4993-bfeb-53cbbbba6f08\"}}" + "{\"requestId\":\"d385ab22-0f51-4b97-9ecd-b8ff3fd4fcb6\",\"response\":{\"installationPoints\":[\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\"],\"partitionId\":\"7b4399a0-21ce-4bee-ba43-e06e291248d2\",\"installationPointCount\":0,\"name\":\"zone name\",\"description\":\"zone description\",\"id\":\"497f6eca-6276-4993-bfeb-53cbbbba6f08\",\"entityMetadata\":[{\"id\":\"123e4567-e89b-12d3-a456-426614174000\",\"name\":\"type\",\"value\":\"floor\"},{\"id\":\"0f8fad5b-d9cb-469f-a165-70867728950e\",\"name\":\"city\",\"value\":null}]}}" test("encoding QueryElement for a zone") { val zoneEncoded = encodeQueryElement(zone) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/ZoneListTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/ZoneListTest.kt index 0c04c38..bcc62db 100644 --- a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/ZoneListTest.kt +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/query/ZoneListTest.kt @@ -33,7 +33,7 @@ class ZoneListTest : ) val zoneString = - "{\"requestId\":\"00000000-1281-40ae-89d7-5c541d77a757\",\"response\":{\"data\":[{\"installationPoints\":[\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\"],\"partitionId\":\"7b4399a0-21ce-4bee-ba43-e06e291248d2\",\"installationPointCount\":0,\"name\":\"zone name\",\"description\":\"zone description\",\"id\":\"497f6eca-6276-4993-bfeb-53cbbbba6f08\"},{\"installationPoints\":[\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\",\"f6a5bdf2-7c7d-11ee-b962-0242ac120002\"],\"partitionId\":\"7b4399a0-21ce-4bee-ba43-e06e291248d2\",\"installationPointCount\":0,\"name\":\"zone name\",\"description\":\"zone description\",\"id\":\"a4c838a8-f6be-49e0-abee-c1d3b2897279\"}],\"totalCount\":2,\"filterCount\":2}}" + "{\"requestId\":\"00000000-1281-40ae-89d7-5c541d77a757\",\"response\":{\"data\":[{\"installationPoints\":[\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\"],\"partitionId\":\"7b4399a0-21ce-4bee-ba43-e06e291248d2\",\"installationPointCount\":0,\"name\":\"zone name\",\"description\":\"zone description\",\"id\":\"497f6eca-6276-4993-bfeb-53cbbbba6f08\",\"entityMetadata\":[{\"id\":\"123e4567-e89b-12d3-a456-426614174000\",\"name\":\"type\",\"value\":\"floor\"},{\"id\":\"0f8fad5b-d9cb-469f-a165-70867728950e\",\"name\":\"city\",\"value\":null}]},{\"installationPoints\":[\"7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a\",\"f6a5bdf2-7c7d-11ee-b962-0242ac120002\"],\"partitionId\":\"7b4399a0-21ce-4bee-ba43-e06e291248d2\",\"installationPointCount\":0,\"name\":\"zone name\",\"description\":\"zone description\",\"id\":\"a4c838a8-f6be-49e0-abee-c1d3b2897279\",\"entityMetadata\":[{\"id\":\"123e4567-e89b-12d3-a456-426614174000\",\"name\":\"type\",\"value\":\"floor\"},{\"id\":\"0f8fad5b-d9cb-469f-a165-70867728950e\",\"name\":\"city\",\"value\":null}]}],\"totalCount\":2,\"filterCount\":2}}" test("encoding QueryList for a list of zones") { val zoneEncoded = encodeQueryList(zoneList) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ChangeZoneMetadataValueTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ChangeZoneMetadataValueTest.kt new file mode 100644 index 0000000..a0bfd90 --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ChangeZoneMetadataValueTest.kt @@ -0,0 +1,104 @@ +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.changeZoneMetadataValueAsync +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.ZoneChanged +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 ChangeZoneMetadataValueTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + test("change zone metadata value") { + coEvery { config.uuidGenerator.generateId() } + .returns(UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757")) + + val zoneId = 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_ZONE_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\":\"Top Secret\",\"token\":\"JDJhJDEwJDFSNEljZ2FaRUNXUXBTQ25XN05KbE9qRzFHQ1VjMzkvWTBVcFpZb1M4Vmt0dnJYZ0tJVFBx\"}" + ) + + val apiEvent = + ApiEvent( + UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757"), + ZoneChanged( + name = "Test Zone", + description = "Test Description", + id = zoneId, + entityMetadata = + listOf( + EntityMetadata( + id = metadataId, + name = "security clearance required", + value = "Top Secret", + ) + ), + ), + ) + + client + .publishAsync(Topics.Event.ZONE_CHANGED, encodeEvent(apiEvent)) + .await() + } + } + + launch { + simulatedBackendReady.await() + + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.ZONE_CHANGED)).await() + + val result = + api.changeZoneMetadataValueAsync( + id = zoneId, + metadataId = metadataId, + value = "Top Secret", + ) + .await() + + result.id.shouldBeEqual(zoneId) + result.entityMetadata!! + .single { it.id == metadataId } + .value + ?.shouldBeEqual("Top Secret") + } + } + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/util/fixture/ZoneFixture.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/util/fixture/ZoneFixture.kt index df71a14..6273b9f 100644 --- a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/util/fixture/ZoneFixture.kt +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/util/fixture/ZoneFixture.kt @@ -1,5 +1,6 @@ package com.open200.xesar.connect.util.fixture +import com.open200.xesar.connect.messages.EntityMetadata import com.open200.xesar.connect.messages.query.Zone import java.util.* @@ -13,5 +14,18 @@ object ZoneFixture { description = "zone description", id = UUID.fromString("497f6eca-6276-4993-bfeb-53cbbbba6f08"), installationPoints = listOf(UUID.fromString("7ca59670-bd30-4ea9-9bd1-2103a9bd2f2a")), + entityMetadata = + listOf( + EntityMetadata( + id = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), + name = "type", + value = "floor", + ), + EntityMetadata( + id = UUID.fromString("0f8fad5b-d9cb-469f-a165-70867728950e"), + name = "city", + value = null, + ), + ), ) }