From da6f2996e64de501e1429a4856851e7d41fa5a57 Mon Sep 17 00:00:00 2001 From: zero88 Date: Mon, 8 Feb 2021 22:16:44 +0700 Subject: [PATCH] Add QWE IoT data project --- build.gradle.kts | 2 +- data/README.md | 5 + data/build.gradle.kts | 5 + .../zero88/qwe/iot/data/IoTEntities.java | 13 ++ .../github/zero88/qwe/iot/data/IoTEntity.java | 22 ++ .../github/zero88/qwe/iot/data/IoTEnum.java | 5 + .../github/zero88/qwe/iot/data/IoTObject.java | 5 + .../zero88/qwe/iot/data/IoTProperty.java | 5 + .../zero88/qwe/iot/data/TimeseriesData.java | 3 + .../iot/data/converter/DataTypeConverter.java | 15 ++ .../qwe/iot/data/converter/IoTConverter.java | 60 +++++ .../data/converter/IoTEntityConverter.java | 14 ++ .../data/converter/IoTPropertyConverter.java | 15 ++ .../qwe/iot/data/entity/AbstractDevice.java | 39 ++++ .../qwe/iot/data/entity/AbstractEntities.java | 35 +++ .../qwe/iot/data/entity/AbstractPoint.java | 31 +++ .../iot/data/entity/AbstractPointData.java | 32 +++ .../qwe/iot/data/entity/HasObjectType.java | 12 + .../zero88/qwe/iot/data/entity/IDevice.java | 58 +++++ .../zero88/qwe/iot/data/entity/INetwork.java | 27 +++ .../zero88/qwe/iot/data/entity/IPoint.java | 29 +++ .../qwe/iot/data/entity/IPointData.java | 35 +++ .../qwe/iot/data/enums/DeviceStatus.java | 31 +++ .../zero88/qwe/iot/data/enums/DeviceType.java | 26 +++ .../iot/data/enums/HistorySettingType.java | 25 +++ .../zero88/qwe/iot/data/enums/PointKind.java | 24 ++ .../zero88/qwe/iot/data/enums/PointType.java | 42 ++++ .../iot/data/enums/TransducerCategory.java | 27 +++ .../qwe/iot/data/enums/TransducerType.java | 25 +++ .../iot/data/property/PointPresentValue.java | 73 ++++++ .../iot/data/property/PointPriorityArray.java | 207 ++++++++++++++++++ .../data/property/PointPropertyMetadata.java | 27 +++ .../qwe/iot/data/property/PointValue.java | 47 ++++ .../qwe/iot/data/unified/ParticularData.java | 17 ++ .../qwe/iot/data/unified/UnifiedDevice.java | 25 +++ .../iot/data/unified/UnifiedIoTEntity.java | 37 ++++ .../qwe/iot/data/unified/UnifiedNetwork.java | 28 +++ .../qwe/iot/data/unified/UnifiedPoint.java | 23 ++ .../qwe/iot/data/unit/BooleanDataType.java | 54 +++++ .../zero88/qwe/iot/data/unit/DataType.java | 145 ++++++++++++ .../qwe/iot/data/unit/DataTypeCategory.java | 87 ++++++++ .../qwe/iot/data/unit/InternalDataType.java | 7 + .../qwe/iot/data/unit/NumberDataType.java | 65 ++++++ .../zero88/qwe/iot/data/unit/UnitAlias.java | 201 +++++++++++++++++ .../data/property/PointPresentValueTest.java | 106 +++++++++ .../data/property/PointPriorityArrayTest.java | 104 +++++++++ .../qwe/iot/data/unit/DataTypeTest.java | 95 ++++++++ .../qwe/iot/data/unit/UnitDisplayTest.java | 97 ++++++++ settings.gradle.kts | 10 +- 49 files changed, 2114 insertions(+), 8 deletions(-) create mode 100644 data/README.md create mode 100644 data/build.gradle.kts create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/IoTEntities.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/IoTEntity.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/IoTEnum.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/IoTObject.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/IoTProperty.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/TimeseriesData.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/converter/DataTypeConverter.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTConverter.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTEntityConverter.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTPropertyConverter.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractDevice.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractEntities.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractPoint.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractPointData.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/HasObjectType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/IDevice.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/INetwork.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/IPoint.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/entity/IPointData.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/enums/DeviceStatus.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/enums/DeviceType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/enums/HistorySettingType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/enums/PointKind.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/enums/PointType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/enums/TransducerCategory.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/enums/TransducerType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPresentValue.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPriorityArray.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPropertyMetadata.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/property/PointValue.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unified/ParticularData.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedDevice.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedIoTEntity.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedNetwork.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedPoint.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unit/BooleanDataType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unit/DataType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unit/DataTypeCategory.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unit/InternalDataType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unit/NumberDataType.java create mode 100644 data/src/main/java/io/github/zero88/qwe/iot/data/unit/UnitAlias.java create mode 100644 data/src/test/java/io/github/zero88/qwe/iot/data/property/PointPresentValueTest.java create mode 100644 data/src/test/java/io/github/zero88/qwe/iot/data/property/PointPriorityArrayTest.java create mode 100644 data/src/test/java/io/github/zero88/qwe/iot/data/unit/DataTypeTest.java create mode 100644 data/src/test/java/io/github/zero88/qwe/iot/data/unit/UnitDisplayTest.java diff --git a/build.gradle.kts b/build.gradle.kts index ff168d1..2eeb15b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,7 +43,7 @@ subprojects { project.ext.set("description", findProperty("description") ?: "A Vertx framework for microservice: ${project.name}") afterEvaluate { - if (setOf("iot", "connectors", "bacnet").contains(project.name)) { + if (setOf("service").contains(project.name)) { project.tasks.forEach { it.enabled = false } } else { println("- Project Name: ${project.ext.get("baseName")}") diff --git a/data/README.md b/data/README.md new file mode 100644 index 0000000..0327441 --- /dev/null +++ b/data/README.md @@ -0,0 +1,5 @@ +# IoT Data + +## Overview + +Defines IoT `data type`, `unit`, `converter` diff --git a/data/build.gradle.kts b/data/build.gradle.kts new file mode 100644 index 0000000..cdaea27 --- /dev/null +++ b/data/build.gradle.kts @@ -0,0 +1,5 @@ +dependencies { + api(ZeroLibs.qwe_protocol) + + testImplementation(testFixtures(ZeroLibs.qwe_base)) +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEntities.java b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEntities.java new file mode 100644 index 0000000..3c367c8 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEntities.java @@ -0,0 +1,13 @@ +package io.github.zero88.qwe.iot.data; + +import java.util.Collection; + +import io.github.zero88.qwe.dto.JsonData; + +public interface IoTEntities> extends JsonData { + + IoTEntities add(T ioTEntity); + + Collection entities(); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEntity.java b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEntity.java new file mode 100644 index 0000000..48a0f7f --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEntity.java @@ -0,0 +1,22 @@ +package io.github.zero88.qwe.iot.data; + +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.protocol.HasProtocol; + +import lombok.NonNull; + +/** + * Represents for an {@code IoT virtual entity} + * + * @param Type of entity key + */ +public interface IoTEntity extends IoTObject, HasProtocol, JsonData { + + /** + * Gets entity key + * + * @return entity key + */ + @NonNull K key(); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEnum.java b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEnum.java new file mode 100644 index 0000000..3f91cd4 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTEnum.java @@ -0,0 +1,5 @@ +package io.github.zero88.qwe.iot.data; + +import io.github.zero88.qwe.dto.PlainType; + +public interface IoTEnum extends IoTProperty, PlainType {} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/IoTObject.java b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTObject.java new file mode 100644 index 0000000..235d16c --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTObject.java @@ -0,0 +1,5 @@ +package io.github.zero88.qwe.iot.data; + +import java.io.Serializable; + +public interface IoTObject extends Serializable {} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/IoTProperty.java b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTProperty.java new file mode 100644 index 0000000..2808c88 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/IoTProperty.java @@ -0,0 +1,5 @@ +package io.github.zero88.qwe.iot.data; + +public interface IoTProperty extends IoTObject { + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/TimeseriesData.java b/data/src/main/java/io/github/zero88/qwe/iot/data/TimeseriesData.java new file mode 100644 index 0000000..2b3303e --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/TimeseriesData.java @@ -0,0 +1,3 @@ +package io.github.zero88.qwe.iot.data; + +public interface TimeseriesData {} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/converter/DataTypeConverter.java b/data/src/main/java/io/github/zero88/qwe/iot/data/converter/DataTypeConverter.java new file mode 100644 index 0000000..53801d7 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/converter/DataTypeConverter.java @@ -0,0 +1,15 @@ +package io.github.zero88.qwe.iot.data.converter; + +import io.github.zero88.qwe.iot.data.unit.DataType; + +/** + * Represents a converter between normal data type and the equivalent data type in another {@code protocol} + * + * @param Type of Data type + * @param Type of specific protocol data type + * @see DataType + * @since 1.0.0 + */ +public interface DataTypeConverter extends IoTConverter { + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTConverter.java b/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTConverter.java new file mode 100644 index 0000000..9ed0bbd --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTConverter.java @@ -0,0 +1,60 @@ +package io.github.zero88.qwe.iot.data.converter; + +import java.io.Serializable; + +import io.github.zero88.qwe.iot.data.IoTObject; +import io.github.zero88.qwe.protocol.HasProtocol; +import io.github.zero88.qwe.protocol.Protocol; + +import lombok.NonNull; + +/** + * Represents a {@code converter} between two equivalent types of {@code IoT object} with different protocol + * + * @param Type of IoT object + * @param Type of IoT object + * @see Protocol + * @see IoTObject + * @since 1.0.0 + */ +public interface IoTConverter extends HasProtocol, Serializable { + + /** + * Translate a {@code protocol} object to a {@code QWE IoT concept} + * + * @param object {@code protocol} object + * @return The QWE IoT concept + * @apiNote if cannot translate, output can be {@code null} or throw {@link IllegalArgumentException} depends on + * detail implementation + * @since 1.0.0 + */ + T serialize(U object); + + /** + * Translate {@code QWE IoT concept} to a {@code protocol} object + * + * @param concept The QWE IoT concept + * @return The protocol object + * @apiNote if cannot translate, output can be {@code null} or throw {@link IllegalArgumentException} depends on + * detail implementation + * @since 1.0.0 + */ + U deserialize(T concept); + + /** + * The {@code QWE IoT concept} type + * + * @return the class + * @since 1.0.0 + */ + @NonNull Class fromType(); + + /** + * The {@code protocol} object type + * + * @return the class + * @since 1.0.0 + */ + @NonNull Class toType(); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTEntityConverter.java b/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTEntityConverter.java new file mode 100644 index 0000000..c317f9e --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTEntityConverter.java @@ -0,0 +1,14 @@ +package io.github.zero88.qwe.iot.data.converter; + +import io.github.zero88.qwe.iot.data.IoTEntity; + +/** + * Represents a {@code converter} between two equivalent types of {@code IoT entity} with different protocol + * + * @param Type of IoT entity + * @param Type of IoT entity + * @since 1.0.0 + */ +public interface IoTEntityConverter extends IoTConverter { + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTPropertyConverter.java b/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTPropertyConverter.java new file mode 100644 index 0000000..872653b --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/converter/IoTPropertyConverter.java @@ -0,0 +1,15 @@ +package io.github.zero88.qwe.iot.data.converter; + +import io.github.zero88.qwe.iot.data.IoTProperty; + +/** + * Represents a {@code converter} between two equivalent types of {@code IoT entity} with different protocol + * + * @param Type of IoT property + * @param Type of IoT property or mixin from specific protocol + * @see IoTProperty + * @since 1.0.0 + */ +public interface IoTPropertyConverter extends IoTConverter { + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractDevice.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractDevice.java new file mode 100644 index 0000000..e47b1f2 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractDevice.java @@ -0,0 +1,39 @@ +package io.github.zero88.qwe.iot.data.entity; + +import io.github.zero88.qwe.iot.data.enums.DeviceStatus; +import io.github.zero88.qwe.iot.data.enums.DeviceType; +import io.vertx.core.json.JsonObject; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Accessors; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +@FieldNameConstants +@Accessors(fluent = true) +public abstract class AbstractDevice implements IDevice { + + @JsonProperty("_" + Fields.key) + private final K key; + @NonNull + @JsonProperty("_" + Fields.networkId) + private final String networkId; + @NonNull + @JsonProperty("_" + Fields.address) + private final JsonObject address; + @NonNull + @JsonProperty("_" + Fields.type) + private final DeviceType type; + @NonNull + @JsonProperty("_" + Fields.name) + private final String name; + @NonNull + @JsonProperty("_" + Fields.status) + private final DeviceStatus status; + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractEntities.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractEntities.java new file mode 100644 index 0000000..ab25734 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractEntities.java @@ -0,0 +1,35 @@ +package io.github.zero88.qwe.iot.data.entity; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import io.github.zero88.qwe.iot.data.IoTEntities; +import io.github.zero88.qwe.iot.data.IoTEntity; +import io.vertx.core.json.JsonObject; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.NonNull; + +public abstract class AbstractEntities> implements IoTEntities { + + private final Map data = new HashMap<>(); + + @Override + public IoTEntities add(T ioTEntity) { + data.put(ioTEntity.key(), ioTEntity); + return this; + } + + @Override + public Collection entities() { + return data.values(); + } + + @Override + public JsonObject toJson(@NonNull ObjectMapper mapper) { + return mapper.convertValue(data, JsonObject.class); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractPoint.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractPoint.java new file mode 100644 index 0000000..f32254b --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractPoint.java @@ -0,0 +1,31 @@ +package io.github.zero88.qwe.iot.data.entity; + +import io.github.zero88.qwe.iot.data.enums.PointType; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Accessors; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +@FieldNameConstants +@Accessors(fluent = true) +public abstract class AbstractPoint implements IPoint { + + @JsonProperty("_" + Fields.key) + private final K key; + @NonNull + @JsonProperty("_" + Fields.networkId) + private final String networkId; + @NonNull + @JsonProperty("_" + Fields.deviceId) + private final String deviceId; + @NonNull + @JsonProperty("_" + Fields.type) + private final PointType type; + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractPointData.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractPointData.java new file mode 100644 index 0000000..ea3aa7b --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/AbstractPointData.java @@ -0,0 +1,32 @@ +package io.github.zero88.qwe.iot.data.entity; + +import io.github.zero88.qwe.iot.data.property.PointPresentValue; +import io.github.zero88.qwe.iot.data.property.PointPriorityArray; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Accessors; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +@FieldNameConstants +@Accessors(fluent = true) +public abstract class AbstractPointData implements IPointData { + + @JsonProperty(Fields.key) + private final K key; + @NonNull + @JsonProperty(Fields.pointId) + private final String pointId; + @NonNull + @JsonProperty(Fields.presentValue) + private final PointPresentValue presentValue; + @NonNull + @JsonProperty(Fields.priorityValue) + private final PointPriorityArray priorityValue; + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/HasObjectType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/HasObjectType.java new file mode 100644 index 0000000..be73d26 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/HasObjectType.java @@ -0,0 +1,12 @@ +package io.github.zero88.qwe.iot.data.entity; + +import lombok.NonNull; + +/** + * @param Type of IoT object type + */ +public interface HasObjectType { + + @NonNull T type(); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IDevice.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IDevice.java new file mode 100644 index 0000000..a1f2007 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IDevice.java @@ -0,0 +1,58 @@ +package io.github.zero88.qwe.iot.data.entity; + +import java.util.Collection; +import java.util.Collections; + +import io.github.zero88.qwe.iot.data.IoTEntity; +import io.github.zero88.qwe.iot.data.enums.DeviceStatus; +import io.github.zero88.qwe.iot.data.enums.DeviceType; +import io.vertx.core.json.JsonObject; + +import lombok.NonNull; + +/** + * Represents for a {@code semantic IoT device} entity + * + * @param Type of device key + */ +public interface IDevice extends IoTEntity, HasObjectType { + + /** + * Retrieve a network identifier that device belongs to + * + * @return network identifier + */ + @NonNull String networkId(); + + /** + * Device address + * + * @return device address + */ + @NonNull JsonObject address(); + + /** + * Device name + * + * @return device name + */ + @NonNull String name(); + + /** + * Device status + * + * @return device status + */ + @NonNull DeviceStatus status(); + + /** + * Return list of points that belongs to network + * + * @return list of points, might empty + * @see IPoint + */ + default @NonNull Collection points() { + return Collections.emptyList(); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/INetwork.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/INetwork.java new file mode 100644 index 0000000..4f8c625 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/INetwork.java @@ -0,0 +1,27 @@ +package io.github.zero88.qwe.iot.data.entity; + +import java.util.Collection; +import java.util.Collections; + +import io.github.zero88.qwe.iot.data.IoTEntity; + +import lombok.NonNull; + +/** + * Represents for a {@code semantic IoT network} entity + * + * @param Type of network key + */ +public interface INetwork extends IoTEntity, HasObjectType { + + /** + * Return list of devices that belongs to network + * + * @return list of devices, might empty + * @see IDevice + */ + default @NonNull Collection devices() { + return Collections.emptyList(); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IPoint.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IPoint.java new file mode 100644 index 0000000..757effc --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IPoint.java @@ -0,0 +1,29 @@ +package io.github.zero88.qwe.iot.data.entity; + +import io.github.zero88.qwe.iot.data.IoTEntity; +import io.github.zero88.qwe.iot.data.enums.PointType; + +import lombok.NonNull; + +/** + * Represents for a {@code semantic IoT point} entity + * + * @param Type of point key + */ +public interface IPoint extends IoTEntity, HasObjectType { + + /** + * Retrieve a network identifier that point belongs to + * + * @return network identifier + */ + @NonNull String networkId(); + + /** + * Retrieve a device identifier that point belongs to + * + * @return device identifier + */ + @NonNull String deviceId(); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IPointData.java b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IPointData.java new file mode 100644 index 0000000..7ce1610 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/entity/IPointData.java @@ -0,0 +1,35 @@ +package io.github.zero88.qwe.iot.data.entity; + +import io.github.zero88.qwe.iot.data.IoTEntity; +import io.github.zero88.qwe.iot.data.TimeseriesData; +import io.github.zero88.qwe.iot.data.property.PointPresentValue; +import io.github.zero88.qwe.iot.data.property.PointPriorityArray; + +import lombok.NonNull; + +public interface IPointData extends IoTEntity, TimeseriesData { + + /** + * Retrieve a point identifier that point data belongs to + * + * @return point identifier + */ + @NonNull String pointId(); + + /** + * Define present value + * + * @return present value + * @see PointPresentValue + */ + PointPresentValue presentValue(); + + /** + * Define point priority value + * + * @return point priority value + * @see PointPriorityArray + */ + PointPriorityArray priorityValue(); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/enums/DeviceStatus.java b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/DeviceStatus.java new file mode 100644 index 0000000..9f5d54c --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/DeviceStatus.java @@ -0,0 +1,31 @@ +package io.github.zero88.qwe.iot.data.enums; + +import io.github.zero88.qwe.dto.EnumType; +import io.github.zero88.qwe.dto.EnumType.AbstractEnumType; +import io.github.zero88.qwe.iot.data.IoTEnum; +import io.github.zero88.utils.Strings; + +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DeviceStatus extends AbstractEnumType implements IoTEnum { + + public static final DeviceStatus UP = new DeviceStatus("UP"); + public static final DeviceStatus DOWN = new DeviceStatus("DOWN"); + public static final DeviceStatus BUSY = new DeviceStatus("BUSY"); + public static final DeviceStatus UNREACHABLE = new DeviceStatus("UNREACHABLE"); + public static final DeviceStatus UNKNOWN = new DeviceStatus("UNKNOWN"); + + private DeviceStatus(String type) { + super(type); + } + + public static DeviceStatus parse(String action) { + return Strings.isBlank(action) ? UNKNOWN : EnumType.factory(action, DeviceStatus.class); + } + + @JsonValue + public String status() { + return this.type(); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/enums/DeviceType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/DeviceType.java new file mode 100644 index 0000000..41a7659 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/DeviceType.java @@ -0,0 +1,26 @@ +package io.github.zero88.qwe.iot.data.enums; + +import io.github.zero88.qwe.dto.EnumType; +import io.github.zero88.qwe.dto.EnumType.AbstractEnumType; +import io.github.zero88.qwe.iot.data.IoTEnum; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public final class DeviceType extends AbstractEnumType implements IoTEnum { + + public static final DeviceType MACHINE = new DeviceType("MACHINE"); + public static final DeviceType GATEWAY = new DeviceType("GATEWAY"); + public static final DeviceType EQUIPMENT = new DeviceType("EQUIPMENT"); + public static final DeviceType HVAC = new DeviceType("HVAC"); + public static final DeviceType DROPLET = new DeviceType("DROPLET"); + + private DeviceType(String type) { + super(type); + } + + @JsonCreator + public static DeviceType factory(String type) { + return EnumType.factory(type, DeviceType.class, MACHINE); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/enums/HistorySettingType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/HistorySettingType.java new file mode 100644 index 0000000..b1e4c22 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/HistorySettingType.java @@ -0,0 +1,25 @@ +package io.github.zero88.qwe.iot.data.enums; + +import io.github.zero88.qwe.dto.EnumType; +import io.github.zero88.qwe.dto.EnumType.AbstractEnumType; +import io.github.zero88.qwe.iot.data.IoTEnum; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public final class HistorySettingType extends AbstractEnumType implements IoTEnum { + + public static final HistorySettingType COV = new HistorySettingType("COV"); + public static final HistorySettingType PERIOD = new HistorySettingType("PERIOD", "PERIODIC"); + + private HistorySettingType(String type) { super(type); } + + private HistorySettingType(String type, String... aliases) { + super(type, aliases); + } + + @JsonCreator + public static HistorySettingType factory(String type) { + return EnumType.factory(type, HistorySettingType.class); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/enums/PointKind.java b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/PointKind.java new file mode 100644 index 0000000..fb1d776 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/PointKind.java @@ -0,0 +1,24 @@ +package io.github.zero88.qwe.iot.data.enums; + +import io.github.zero88.qwe.dto.EnumType; +import io.github.zero88.qwe.dto.EnumType.AbstractEnumType; +import io.github.zero88.qwe.iot.data.IoTEnum; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public final class PointKind extends AbstractEnumType implements IoTEnum { + + public static final PointKind UNKNOWN = new PointKind("UNKNOWN"); + + private PointKind(String type) { + super(type); + } + + public static PointKind def() { return UNKNOWN; } + + @JsonCreator + public static PointKind factory(String name) { + return EnumType.factory(name, PointKind.class, def()); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/enums/PointType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/PointType.java new file mode 100644 index 0000000..8470466 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/PointType.java @@ -0,0 +1,42 @@ +package io.github.zero88.qwe.iot.data.enums; + +import io.github.zero88.qwe.dto.EnumType; +import io.github.zero88.qwe.dto.EnumType.AbstractEnumType; +import io.github.zero88.qwe.iot.data.IoTEnum; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public final class PointType extends AbstractEnumType implements IoTEnum { + + public static final PointType ANALOG_INPUT = new PointType("ANALOG_INPUT", "analog-input"); + public static final PointType ANALOG_OUTPUT = new PointType("ANALOG_OUTPUT", "analog-output"); + public static final PointType ANALOG_VALUE = new PointType("ANALOG_VALUE", "analog-value"); + + public static final PointType BINARY_INPUT = new PointType("BINARY_INPUT", "binary-input"); + public static final PointType BINARY_OUTPUT = new PointType("BINARY_OUTPUT", "binary-output"); + public static final PointType BINARY_VALUE = new PointType("BINARY_VALUE", "binary-value"); + + public static final PointType MULTI_STATE_INPUT = new PointType("MULTI_STATE_INPUT", "multi-state-input"); + public static final PointType MULTI_STATE_OUTPUT = new PointType("MULTI_STATE_OUTPUT", "multi-state-output"); + public static final PointType MULTI_STATE_VALUE = new PointType("MULTI_STATE_VALUE", "multi-state-value"); + + public static final PointType COMMAND = new PointType("COMMAND", "command"); + public static final PointType CALENDAR = new PointType("CALENDAR", "calendar"); + public static final PointType SCHEDULE = new PointType("SCHEDULE", "schedule"); + + public static final PointType UNKNOWN = new PointType("UNKNOWN"); + + private PointType(String type) { super(type); } + + private PointType(String type, String... aliases) { super(type, aliases); } + + public static PointType def() { + return UNKNOWN; + } + + @JsonCreator + public static PointType factory(String type) { + return EnumType.factory(type, PointType.class, def()); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/enums/TransducerCategory.java b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/TransducerCategory.java new file mode 100644 index 0000000..af99fb5 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/TransducerCategory.java @@ -0,0 +1,27 @@ +package io.github.zero88.qwe.iot.data.enums; + +import io.github.zero88.qwe.dto.EnumType; +import io.github.zero88.qwe.dto.EnumType.AbstractEnumType; +import io.github.zero88.qwe.iot.data.IoTEnum; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public final class TransducerCategory extends AbstractEnumType implements IoTEnum { + + public static final TransducerCategory TEMP = new TransducerCategory("TEMP"); + public static final TransducerCategory HUMIDITY = new TransducerCategory("HUMIDITY"); + public static final TransducerCategory MOTION = new TransducerCategory("MOTION"); + public static final TransducerCategory VELOCITY = new TransducerCategory("VELOCITY"); + public static final TransducerCategory SWITCH = new TransducerCategory("SWITCH"); + public static final TransducerCategory RELAY = new TransducerCategory("RELAY"); + + private TransducerCategory(String type) { + super(type); + } + + @JsonCreator + public static TransducerCategory factory(String name) { + return EnumType.factory(name, TransducerCategory.class); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/enums/TransducerType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/TransducerType.java new file mode 100644 index 0000000..3f6284d --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/enums/TransducerType.java @@ -0,0 +1,25 @@ +package io.github.zero88.qwe.iot.data.enums; + +import io.github.zero88.qwe.dto.EnumType; +import io.github.zero88.qwe.dto.EnumType.AbstractEnumType; +import io.github.zero88.qwe.iot.data.IoTEnum; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public final class TransducerType extends AbstractEnumType implements IoTEnum { + + public static final TransducerType SENSOR = new TransducerType("SENSOR"); + public static final TransducerType ACTUATOR = new TransducerType("ACTUATOR"); + + private TransducerType(String type) { + super(type); + } + + public static TransducerType def() { return SENSOR; } + + @JsonCreator + public static TransducerType factory(String name) { + return EnumType.factory(name, TransducerType.class, def()); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPresentValue.java b/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPresentValue.java new file mode 100644 index 0000000..676d9a6 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPresentValue.java @@ -0,0 +1,73 @@ +package io.github.zero88.qwe.iot.data.property; + +import java.util.Optional; + +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.iot.data.IoTProperty; +import io.github.zero88.qwe.iot.data.TimeseriesData; +import io.reactivex.annotations.Nullable; +import io.vertx.core.json.JsonObject; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; + +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldNameConstants; +import lombok.extern.jackson.Jacksonized; + +/** + * Represents for Point value. + * + * @since 1.0.0 + */ +@Getter +@Jacksonized +@FieldNameConstants +@Builder(builderClassName = "Builder") +public final class PointPresentValue implements JsonData, IoTProperty, TimeseriesData { + + private final int priority; + @JsonUnwrapped + private final PointValue pointValue; + + public static PointPresentValue def() { + return PointPresentValue.builder().priority(PointPriorityArray.DEFAULT_PRIORITY).build(); + } + + @Nullable + public static PointPresentValue from(@NonNull JsonObject data) { + if (data.containsKey(Fields.priority) || data.containsKey(PointValue.Fields.rawValue) || + data.containsKey(PointValue.Fields.value)) { + return PointPresentValue.builder() + .priority(data.getInteger(Fields.priority)) + .pointValue(PointValue.from(data)) + .build(); + } + return null; + } + + public static class Builder { + + @Setter + @Accessors(fluent = true) + private Integer priority; + @Setter + @Accessors(fluent = true) + private String value; + @Setter + @Accessors(fluent = true) + private Double rawValue; + + public PointPresentValue build() { + int p = PointPriorityArray.validateAndGet(priority); + PointValue pv = Optional.ofNullable(pointValue) + .orElseGet(() -> PointValue.builder().value(value).rawValue(rawValue).build()); + return new PointPresentValue(p, pv); + } + + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPriorityArray.java b/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPriorityArray.java new file mode 100644 index 0000000..1ff27f2 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPriorityArray.java @@ -0,0 +1,207 @@ +package io.github.zero88.qwe.iot.data.property; + +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.iot.data.IoTProperty; +import io.github.zero88.qwe.iot.data.TimeseriesData; +import io.github.zero88.utils.Functions; +import io.vertx.core.json.JsonObject; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import lombok.NoArgsConstructor; +import lombok.NonNull; + +/** + * Point Priority Value + * + * @see HayStack Write Level + * @see BACNet priority array + * @since 1.0.0 + */ +@NoArgsConstructor +public final class PointPriorityArray implements JsonData, IoTProperty, TimeseriesData { + + /** + * The constant DEFAULT_PRIORITY. + */ + public static final int DEFAULT_PRIORITY = 16; + /** + * The constant MIN_PRIORITY. + */ + public static final int MIN_PRIORITY = 1; + /** + * The constant MAX_PRIORITY. + */ + public static final int MAX_PRIORITY = 17; + private static final String INVALID_PRIORITY = "Priority is only in range [1, 17]"; + private static final String INVALID_VALUE = "Value must be number"; + private final SortedMap val = init(); + + @JsonCreator + PointPriorityArray(Map map) { + val.putAll(map.entrySet() + .stream() + .collect(TreeMap::new, (m, entry) -> m.put(validateAndGetKey(entry.getKey()), + validateAndGetValue(entry.getValue())), Map::putAll)); + } + + private static SortedMap init() { + final SortedMap val = new TreeMap<>(); + for (int i = PointPriorityArray.MIN_PRIORITY; i <= PointPriorityArray.MAX_PRIORITY; i++) { + val.put(i, null); + } + return val; + } + + private static boolean isValid(int priority) { + return priority <= MAX_PRIORITY && priority >= MIN_PRIORITY; + } + + static int validateAndGet(Integer priority) { + if (Objects.isNull(priority)) { + return DEFAULT_PRIORITY; + } + if (isValid(priority)) { + return priority; + } + throw new IllegalArgumentException(INVALID_PRIORITY); + } + + /** + * Add point priority value with {@link #DEFAULT_PRIORITY}. + * + * @param value the value + * @return a reference to this, so the API can be used fluently + * @since 1.0.0 + */ + public PointPriorityArray add(int value) { + return add((double) value); + } + + /** + * Add point priority value with {@link #DEFAULT_PRIORITY}. + * + * @param value the value + * @return a reference to this, so the API can be used fluently + * @since 1.0.0 + */ + public PointPriorityArray add(Double value) { + return add(DEFAULT_PRIORITY, value); + } + + /** + * Add point priority value. + * + * @param priority the priority + * @param value the value + * @return a reference to this, so the API can be used fluently + * @since 1.0.0 + */ + public PointPriorityArray add(int priority, int value) { + return add(priority, (double) value); + } + + /** + * Add point priority value. + * + * @param priority the priority + * @param value the value + * @return a reference to this, so the API can be used fluently + * @since 1.0.0 + */ + public PointPriorityArray add(int priority, Double value) { + this.val.put(validateAndGet(priority), value); + return this; + } + + /** + * Get value by {@link #DEFAULT_PRIORITY}. + * + * @return the value + * @since 1.0.0 + */ + public Double get() { + return get(DEFAULT_PRIORITY); + } + + /** + * Get value by given priority. + * + * @param priority the priority + * @return the double + * @since 1.0.0 + */ + public Double get(int priority) { + return val.get(validateAndGet(priority)); + } + + /** + * Find highest value point value. + * + * @return the highest point value + * @since 1.0.0 + */ + public PointPresentValue findHighestValue() { + return val.entrySet() + .stream() + .filter(entry -> Objects.nonNull(entry.getValue())) + .findFirst() + .map(entry -> PointPresentValue.builder().priority(entry.getKey()).rawValue(entry.getValue()).build()) + .orElse(PointPresentValue.def()); + } + + @Override + public JsonObject toJson() { + return JsonData.MAPPER.convertValue(val, JsonObject.class); + } + + public int hashCode() { + final int PRIME_KEY = 31; + final int PRIME_VALUE = 43; + final int PRIME = 59; + int result = 1; + return val.entrySet() + .stream() + .map(entry -> entry.getKey() * PRIME_KEY + entry.getValue().hashCode() * PRIME_VALUE) + .reduce(result, (r, i) -> r + PRIME * i); + } + + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PointPriorityArray)) { + return false; + } + final PointPriorityArray other = (PointPriorityArray) o; + return val.entrySet().stream().allMatch(entry -> { + final Double v1 = val.get(entry.getKey()); + final Double v2 = other.val.get(entry.getKey()); + return v1 == null && v2 == null || (v1 != null && v1.equals(v2)); + }); + } + + @Override + public String toString() { + return toJson().encode(); + } + + private int validateAndGetKey(@NonNull Object priority) { + return validateAndGet(Functions.getOrThrow(() -> Functions.toInt().apply(priority.toString()), + () -> new IllegalArgumentException(INVALID_PRIORITY))); + } + + private Double validateAndGetValue(Object value) { + if (Objects.isNull(value)) { + return null; + } + return Functions.getOrThrow(() -> Functions.toDouble().apply(value.toString()), + () -> new IllegalArgumentException(INVALID_VALUE)); + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPropertyMetadata.java b/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPropertyMetadata.java new file mode 100644 index 0000000..7198940 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointPropertyMetadata.java @@ -0,0 +1,27 @@ +package io.github.zero88.qwe.iot.data.property; + +import io.github.zero88.qwe.iot.data.IoTProperty; +import io.github.zero88.qwe.iot.data.enums.PointKind; +import io.github.zero88.qwe.iot.data.enums.PointType; +import io.github.zero88.qwe.iot.data.enums.TransducerCategory; +import io.github.zero88.qwe.iot.data.enums.TransducerType; +import io.github.zero88.qwe.iot.data.unit.DataType; +import io.github.zero88.qwe.iot.data.unit.UnitAlias; + +import lombok.Builder; +import lombok.Getter; +import lombok.experimental.Accessors; + +@Getter +@Accessors(fluent = true) +@Builder(builderClassName = "Builder") +public class PointPropertyMetadata implements IoTProperty { + + private final PointType pointType; + private final PointKind pointKind; + private final TransducerType transducerType; + private final TransducerCategory transducerCategory; + private final DataType unit; + private final UnitAlias unitAlias; + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointValue.java b/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointValue.java new file mode 100644 index 0000000..5608c76 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/property/PointValue.java @@ -0,0 +1,47 @@ +package io.github.zero88.qwe.iot.data.property; + +import java.util.Optional; + +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.iot.data.IoTProperty; +import io.github.zero88.qwe.iot.data.TimeseriesData; +import io.github.zero88.utils.Functions; +import io.vertx.core.json.JsonObject; + +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; +import lombok.experimental.FieldNameConstants; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +@FieldNameConstants +public final class PointValue implements JsonData, IoTProperty, TimeseriesData { + + private final String value; + private final Double rawValue; + + public static PointValue from(@NonNull JsonObject json) { + return PointValue.builder() + .rawValue(json.getDouble(PointValue.Fields.rawValue)) + .value(json.getString(PointValue.Fields.value)) + .build(); + } + + public static class PointValueBuilder { + + public PointValue build() { + String v = Optional.ofNullable(this.value) + .orElse(Optional.ofNullable(rawValue).map(Object::toString).orElse(null)); + Double r = Optional.ofNullable(rawValue) + .orElse(Optional.ofNullable(this.value) + .map(x -> Functions.getOrDefault((Double) null, () -> Double.valueOf(x))) + .orElse(null)); + return new PointValue(v, r); + } + + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unified/ParticularData.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/ParticularData.java new file mode 100644 index 0000000..4a00c78 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/ParticularData.java @@ -0,0 +1,17 @@ +package io.github.zero88.qwe.iot.data.unified; + +import java.util.HashMap; + +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.iot.data.IoTEntity; +import io.github.zero88.qwe.protocol.Protocol; + +import lombok.RequiredArgsConstructor; + +//TODO: Need verify step +@RequiredArgsConstructor +public final class ParticularData extends HashMap implements JsonData { + + private final Class clazz; + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedDevice.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedDevice.java new file mode 100644 index 0000000..fbb6fe9 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedDevice.java @@ -0,0 +1,25 @@ +package io.github.zero88.qwe.iot.data.unified; + +import java.util.UUID; + +import io.github.zero88.qwe.iot.data.entity.AbstractDevice; +import io.github.zero88.qwe.iot.data.entity.IDevice; + +import lombok.Builder.Default; +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Jacksonized +@SuperBuilder +@Accessors(fluent = true) +public class UnifiedDevice extends AbstractDevice implements UnifiedIoTEntity { + + @NonNull + @Default + private final ParticularData particularData = new ParticularData<>(IDevice.class); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedIoTEntity.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedIoTEntity.java new file mode 100644 index 0000000..c92369e --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedIoTEntity.java @@ -0,0 +1,37 @@ +package io.github.zero88.qwe.iot.data.unified; + +import java.util.UUID; + +import io.github.zero88.qwe.iot.data.IoTEntity; +import io.github.zero88.qwe.protocol.Protocol; +import io.github.zero88.utils.UUID64; + +import lombok.NonNull; + +/** + * Represents for an {@code unified IoT} entity that is used for persistence + */ +public interface UnifiedIoTEntity extends IoTEntity { + + /** + * UUID in Base64 format + * + * @return UUID in base64 format + */ + default @NonNull String keyAsString() { + return UUID64.uuidToBase64(key()); + } + + default @NonNull Protocol protocol() { + return Protocol.Unification; + } + + /** + * A particular data + * + * @return a particular data + * @see ParticularData + */ + @NonNull ParticularData particularData(); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedNetwork.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedNetwork.java new file mode 100644 index 0000000..f06dd45 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedNetwork.java @@ -0,0 +1,28 @@ +package io.github.zero88.qwe.iot.data.unified; + +import java.util.UUID; + +import io.github.zero88.qwe.iot.data.entity.INetwork; + +import lombok.Builder; +import lombok.Builder.Default; +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Accessors; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Builder +@Jacksonized +@Accessors(fluent = true) +public class UnifiedNetwork implements INetwork, UnifiedIoTEntity { + + @NonNull + @Default + private final UUID key = UUID.randomUUID(); + @NonNull + private final String type; + @Default + private final ParticularData particularData = new ParticularData<>(INetwork.class); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedPoint.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedPoint.java new file mode 100644 index 0000000..22f1475 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unified/UnifiedPoint.java @@ -0,0 +1,23 @@ +package io.github.zero88.qwe.iot.data.unified; + +import java.util.UUID; + +import io.github.zero88.qwe.iot.data.entity.AbstractPoint; +import io.github.zero88.qwe.iot.data.entity.IPoint; + +import lombok.Builder.Default; +import lombok.Getter; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Jacksonized +@SuperBuilder +@Accessors(fluent = true) +public class UnifiedPoint extends AbstractPoint implements UnifiedIoTEntity { + + @Default + private final ParticularData particularData = new ParticularData<>(IPoint.class); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unit/BooleanDataType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/BooleanDataType.java new file mode 100644 index 0000000..46230f2 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/BooleanDataType.java @@ -0,0 +1,54 @@ +package io.github.zero88.qwe.iot.data.unit; + +import java.util.Objects; +import java.util.Optional; + +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.Base; +import io.github.zero88.utils.Strings; + +import lombok.NonNull; + +public final class BooleanDataType extends NumberDataType { + + BooleanDataType() { + super("bool", null, Base.TYPE); + } + + BooleanDataType(@NonNull BooleanDataType dt, UnitAlias unitAlias) { + super(dt.type(), dt.unit(), dt.category(), Optional.ofNullable(unitAlias).orElse(dt.alias())); + } + + BooleanDataType(@NonNull BooleanDataType dt) { + super(dt.type(), dt.unit(), dt.category(), dt.alias()); + } + + @Override + public Double parse(Object data) { + if (Objects.isNull(data)) { + return 0d; + } + if (data instanceof Number) { + return ((Number) data).doubleValue() > 0 ? 1d : 0d; + } + if (data instanceof Boolean) { + return Boolean.TRUE == data ? 1d : 0d; + } + if (data instanceof String) { + return Boolean.TRUE.equals(Boolean.valueOf((String) data)) ? 1d : 0d; + } + return 0d; + } + + @Override + public @NonNull String display(Double value) { + Double val = Optional.ofNullable(value).orElse(0d); + if (Objects.nonNull(alias())) { + String label = alias().eval(val); + if (Strings.isNotBlank(label)) { + return label; + } + } + return val > 0 ? "TRUE" : "FALSE"; + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unit/DataType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/DataType.java new file mode 100644 index 0000000..cbf669e --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/DataType.java @@ -0,0 +1,145 @@ +package io.github.zero88.qwe.iot.data.unit; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import io.github.classgraph.ClassInfo; +import io.github.zero88.qwe.dto.EnumType; +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.Base; +import io.github.zero88.utils.Reflections.ReflectionClass; +import io.github.zero88.utils.Reflections.ReflectionField; +import io.github.zero88.utils.Strings; +import io.vertx.core.json.JsonObject; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import lombok.NonNull; + +@JsonInclude(Include.NON_EMPTY) +@JsonSerialize(as = DataType.class) +public interface DataType extends EnumType, Cloneable { + + String SEP = "::"; + + static Stream available() { + return ReflectionClass.stream(DataType.class.getPackage().getName(), DataTypeCategory.class, + ClassInfo::isInterface) + .flatMap(clazz -> ReflectionField.streamConstants(clazz, InternalDataType.class)); + } + + @NonNull + static DataType def() { return Base.NUMBER; } + + @NonNull + static DataType factory(String dittoValue) { + if (Strings.isBlank(dittoValue)) { + return def(); + } + final String[] split = dittoValue.split(SEP); + return factory(split[0], split.length > 1 ? split[1] : null); + } + + @NonNull + static DataType factory(String type, String unit) { + return factory(type, unit, Base.NUMBER.category(), null); + } + + @NonNull + @JsonCreator + static DataType factory(@JsonProperty(value = "type") String type, @JsonProperty(value = "symbol") String unit, + @JsonProperty(value = "category") String category, + @JsonProperty(value = "alias") Map alias) { + final DataType dt = available().filter(t -> t.type().equalsIgnoreCase(type)) + .findAny() + .orElseGet(() -> new NumberDataType(type, unit)); + if (dt instanceof BooleanDataType) { + return new BooleanDataType((BooleanDataType) dt, UnitAlias.create(alias)); + } + return new NumberDataType(dt).setCategory(category).setAlias(UnitAlias.create(alias)); + } + + @NonNull + static DataType factory(@NonNull JsonObject dataType, UnitAlias label) { + return factory(dataType.getString("type"), dataType.getString("symbol"), dataType.getString("category"), + null).setAlias(label); + } + + static DataType clone(@NonNull DataType type) { + if (type instanceof BooleanDataType) { + return new BooleanDataType((BooleanDataType) type); + } + return new NumberDataType(type); + } + + @NonNull + @JsonProperty(value = "symbol") + String unit(); + + @NonNull + @JsonProperty(value = "category") + String category(); + + @JsonProperty(value = "alias") + UnitAlias alias(); + + DataType setAlias(UnitAlias alias); + + /** + * Try parse given data to double value + * + * @param data given data + * @return double value + */ + default Double parse(Object data) { + if (Objects.isNull(data)) { + return 0d; + } + if (data instanceof Number) { + return ((Number) data).doubleValue(); + } + if (data instanceof String) { + return Double.valueOf(((String) data).replaceAll(unit(), "")); + } + return 0d; + } + + /** + * Decor value by alias or unit type + * + * @param value given value + * @return display value in string + */ + default @NonNull String display(Double value) { + if (Objects.isNull(value)) { + return ""; + } + if (Objects.nonNull(alias())) { + String label = alias().eval(value); + if (Strings.isNotBlank(label)) { + return label; + } + } + if (Strings.isBlank(unit())) { + return String.valueOf(value); + } + return value + " " + unit(); + } + + default Collection alternatives() { return null; } + + @Override + default JsonObject toJson() { + final JsonObject json = EnumType.super.toJson(); + if (Objects.nonNull(alias())) { + json.put("alias", alias().toJson()); + } + return json; + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unit/DataTypeCategory.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/DataTypeCategory.java new file mode 100644 index 0000000..89ec71e --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/DataTypeCategory.java @@ -0,0 +1,87 @@ +package io.github.zero88.qwe.iot.data.unit; + +import io.github.zero88.utils.Reflections.ReflectionField; +import io.github.zero88.utils.Strings; + +import lombok.NonNull; + +public interface DataTypeCategory extends DataType { + + T convert(V v); + + @Override + default @NonNull String type() { + return ReflectionField.constantByName(this.getClass(), "TYPE"); + } + + interface Base extends DataTypeCategory { + + String TYPE = "ALL"; + DataType NUMBER = new NumberDataType(); + DataType PERCENTAGE = new NumberDataType("percentage", "%"); + DataType BOOLEAN = new BooleanDataType(); + + } + + + interface Power extends DataTypeCategory { + + String TYPE = Strings.toSnakeCaseUC(Power.class.getSimpleName()); + DataType KWH = new NumberDataType("kilowatt_hour", "kWh", TYPE); + DataType DBM = new NumberDataType("dBm", "dBm", TYPE); + + } + + + interface Pressure extends DataTypeCategory { + + String TYPE = Strings.toSnakeCaseUC(Pressure.class.getSimpleName()); + DataType HPA = new NumberDataType("hectopascal", "hPa", TYPE); + + } + + + interface Temperature extends DataTypeCategory { + + String TYPE = Strings.toSnakeCaseUC(Temperature.class.getSimpleName()); + DataType FAHRENHEIT = new NumberDataType("fahrenheit", "°F", TYPE); + DataType CELSIUS = new NumberDataType("celsius", "°C", TYPE); + + } + + + interface Velocity extends DataTypeCategory { + + String TYPE = Strings.toSnakeCaseUC(Velocity.class.getSimpleName()); + DataType M_PER_SECOND = new NumberDataType("meters_per_second", "m/s", TYPE); + DataType KM_PER_HOUR = new NumberDataType("kilometers_per_hour", "km/h", TYPE); + DataType MILE_PER_HOUR = new NumberDataType("miles_per_hour", "mph", TYPE); + + } + + + interface AngularVelocity extends Velocity { + + String TYPE = Strings.toSnakeCaseUC(AngularVelocity.class.getSimpleName()); + DataType RPM = new NumberDataType("revolutions_per_minute", "rpm", TYPE); + DataType RAD_PER_SECOND = new NumberDataType("radians_per_second", "rad/s", TYPE); + + } + + + interface Illumination extends DataTypeCategory { + + String TYPE = Strings.toSnakeCaseUC(Illumination.class.getSimpleName()); + DataType LUX = new NumberDataType("lux", "lx", TYPE); + + } + + + interface ElectricPotential extends DataTypeCategory { + + String TYPE = Strings.toSnakeCaseUC(ElectricPotential.class.getSimpleName()); + DataType VOLTAGE = new NumberDataType("volt", "V", TYPE); + + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unit/InternalDataType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/InternalDataType.java new file mode 100644 index 0000000..cea815b --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/InternalDataType.java @@ -0,0 +1,7 @@ +package io.github.zero88.qwe.iot.data.unit; + +interface InternalDataType extends DataType { + + InternalDataType setCategory(String category); + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unit/NumberDataType.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/NumberDataType.java new file mode 100644 index 0000000..f640ee1 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/NumberDataType.java @@ -0,0 +1,65 @@ +package io.github.zero88.qwe.iot.data.unit; + +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.Base; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.EqualsAndHashCode.Include; +import lombok.Getter; +import lombok.NonNull; + +@AllArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class NumberDataType implements InternalDataType, DataType { + + @Getter + @Include + private final String type; + private final String unit; + private String category; + private UnitAlias alias; + + NumberDataType() { + this("number", null); + } + + NumberDataType(String type, String unit) { + this(type, unit, Base.TYPE, null); + } + + NumberDataType(String type, String unit, String category) { + this(type, unit, category, null); + } + + NumberDataType(DataType dt) { + this.type = dt.type(); + this.unit = dt.unit(); + this.category = dt.category(); + this.alias = dt.alias(); + } + + @Override + public String type() { return type; } + + @Override + public final String unit() { return unit; } + + @Override + public @NonNull String category() { return category; } + + @Override + public UnitAlias alias() { return alias; } + + @Override + public DataType setAlias(UnitAlias alias) { + this.alias = alias; + return this; + } + + @Override + public InternalDataType setCategory(String category) { + this.category = category; + return this; + } + +} diff --git a/data/src/main/java/io/github/zero88/qwe/iot/data/unit/UnitAlias.java b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/UnitAlias.java new file mode 100644 index 0000000..1a92906 --- /dev/null +++ b/data/src/main/java/io/github/zero88/qwe/iot/data/unit/UnitAlias.java @@ -0,0 +1,201 @@ +package io.github.zero88.qwe.iot.data.unit; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.iot.data.IoTProperty; +import io.github.zero88.utils.Strings; +import io.vertx.core.json.JsonObject; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@NoArgsConstructor +public final class UnitAlias implements JsonData, IoTProperty { + + private static final Comparator COMPARATOR = Comparator.comparingInt((Node n) -> n.op.priority) + .thenComparingDouble((Node n) -> n.with); + private final SortedSet nodes = new TreeSet<>(COMPARATOR); + + @JsonCreator + private UnitAlias(Map map) { + nodes.addAll(map.entrySet().stream().map(entry -> new Node(entry.getKey(), entry.getValue())) + .collect(Collectors.toSet())); + } + + static UnitAlias create(Map map) { + return Objects.isNull(map) ? null : new UnitAlias(map); + } + + private static String validateAndGetKey(String expression) { + final Entry expr = validateExpr(expression); + return Node.toKey(expr.getKey(), expr.getValue()); + } + + private static Entry validateExpr(String expression) { + String expr = Strings.requireNotBlank(expression, "Expression cannot be blank"); + Optional binaryRelation = Stream.of(BinaryRelation.values()) + .sorted(((o1, o2) -> o2.symbol.compareTo(o1.symbol))) + .filter(br -> expr.length() > 1 && br.symbol.length() == 2 + ? expr.substring(0, 2).equals(br.symbol) + : expr.charAt(0) == br.symbol.charAt(0)) + .findFirst(); + String rightChild = binaryRelation.map(r -> expr.substring(r.symbol.length())).orElse(expr).trim(); + try { + return new SimpleEntry<>(binaryRelation.orElse(BinaryRelation.EQUALS), Double.parseDouble(rightChild)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Expression cannot parse. Only support some operators: " + + Stream.of(BinaryRelation.values()) + .map(BinaryRelation::getSymbol) + .collect(Collectors.joining(", ")), e); + } + } + + public UnitAlias add(String expr, String display) { + this.nodes.add(new Node(expr, display)); + return this; + } + + public String get(String expr) { + return nodes.stream() + .filter(n -> n.toString().equals(validateAndGetKey(expr))) + .map(n -> n.label) + .findFirst() + .orElse(null); + } + + public String eval(int value) { + return eval((double) value); + } + + public String eval(@NonNull Double value) { + SortedSet collect = nodes.stream() + .filter(node -> node.eval(value)) + .collect(() -> new TreeSet<>(COMPARATOR), SortedSet::add, SortedSet::addAll); + if (collect.isEmpty()) { + return null; + } + if (collect.size() == 1) { + return collect.first().label; + } + final Optional any = collect.stream().filter(n -> n.op == BinaryRelation.EQUALS).findAny(); + if (any.isPresent()) { + return any.get().label; + } + return collect.stream() + .min(Comparator.comparingDouble((Node o) -> Math.abs(value - o.with)) + .thenComparingDouble((Node n) -> n.with)) + .map(n -> n.label) + .orElse(null); + } + + @Override + public JsonObject toJson() { + return nodes.stream() + .collect(JsonObject::new, (json, node) -> json.put(node.toString(), node.label), + (json1, json2) -> json2.mergeIn(json1, true)); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + return nodes.stream().map(Node::hashCode).reduce(result, (r, n) -> r + PRIME * n); + } + + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof UnitAlias)) { + return false; + } + final UnitAlias other = (UnitAlias) o; + return nodes.stream() + .map(Node::toString) + .collect(Collectors.joining("-")) + .equals(other.nodes.stream().map(Node::toString).collect(Collectors.joining("-"))); + } + + @Getter + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + enum BinaryRelation { + + EQUALS("=", 1), + GREATER_THAN(">", 2), + LESS_THAN("<", 3), + GREATER_OR_EQUAL(">=", 4), + LESS_OR_EQUAL("<=", 5), + INEQUALITY("<>", 6); + + private final String symbol; + private final int priority; + } + + + @EqualsAndHashCode(onlyExplicitlyIncluded = true) + static final class Node { + + @NonNull + @EqualsAndHashCode.Include + private final BinaryRelation op; + @NonNull + @EqualsAndHashCode.Include + private final Double with; + private final String label; + + private Node(String expression, String label) { + final Entry entry = validateExpr(expression); + this.op = entry.getKey(); + this.with = entry.getValue(); + this.label = validateAndGetValue(label); + } + + static String toKey(BinaryRelation binaryRelation, Double value) { + return binaryRelation.symbol + " " + value.toString(); + } + + static String validateAndGetValue(String display) { + final String v = Strings.requireNotBlank(display, "Display value cannot be null"); + if (v.equalsIgnoreCase("null")) { + throw new IllegalArgumentException("Display value cannot be null"); + } + return v; + } + + boolean eval(@NonNull Double value) { + int compare = value.compareTo(with); + if (compare == 0) { + return op == BinaryRelation.EQUALS || op == BinaryRelation.GREATER_OR_EQUAL || + op == BinaryRelation.LESS_OR_EQUAL; + } + if (compare < 0) { + return op == BinaryRelation.LESS_THAN || op == BinaryRelation.LESS_OR_EQUAL || + op == BinaryRelation.INEQUALITY; + } + return op == BinaryRelation.GREATER_THAN || op == BinaryRelation.GREATER_OR_EQUAL || + op == BinaryRelation.INEQUALITY; + } + + @Override + public String toString() { + return toKey(op, with); + } + + } + +} diff --git a/data/src/test/java/io/github/zero88/qwe/iot/data/property/PointPresentValueTest.java b/data/src/test/java/io/github/zero88/qwe/iot/data/property/PointPresentValueTest.java new file mode 100644 index 0000000..43172f8 --- /dev/null +++ b/data/src/test/java/io/github/zero88/qwe/iot/data/property/PointPresentValueTest.java @@ -0,0 +1,106 @@ +package io.github.zero88.qwe.iot.data.property; + +import org.json.JSONException; +import org.junit.Assert; +import org.junit.Test; + +import io.github.zero88.qwe.JsonHelper; +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.iot.data.property.PointPresentValue.Fields; +import io.vertx.core.json.JsonObject; + +public class PointPresentValueTest { + + @Test + public void test_serialize() throws JSONException { + final JsonObject e = new JsonObject().put(Fields.priority, 1) + .put(PointValue.Fields.value, "0") + .put(PointValue.Fields.rawValue, 0); + System.out.println(e); + JsonHelper.assertJson(e, PointPresentValue.builder().priority(1).value("0").build().toJson()); + } + + @Test + public void test_deserialize_no_priority() { + final PointPresentValue ppv = JsonData.from("{\"value\":\"1\"}", PointPresentValue.class); + Assert.assertEquals(16, ppv.getPriority()); + Assert.assertEquals("1", ppv.getPointValue().getValue()); + Assert.assertEquals(Double.valueOf(1.0), ppv.getPointValue().getRawValue()); + } + + @Test + public void test_deserialize_no_raw() { + final PointPresentValue pv = JsonData.from("{\"priority\":1,\"value\":\"1\"}", PointPresentValue.class); + Assert.assertEquals(1, pv.getPriority()); + Assert.assertEquals("1", pv.getPointValue().getValue()); + Assert.assertEquals(Double.valueOf(1.0), pv.getPointValue().getRawValue()); + } + + @Test + public void test_deserialize_no_value() { + final PointPresentValue pv = JsonData.from("{\"priority\":1,\"rawValue\": 2.0}", PointPresentValue.class); + Assert.assertEquals(1, pv.getPriority()); + Assert.assertEquals("2.0", pv.getPointValue().getValue()); + Assert.assertEquals(Double.valueOf(2.0), pv.getPointValue().getRawValue()); + } + + @Test + public void test_deserialize_value_is_raw() { + final PointPresentValue pv = JsonData.from("{\"priority\":1,\"value\":10}", PointPresentValue.class); + Assert.assertEquals(1, pv.getPriority()); + Assert.assertEquals("10", pv.getPointValue().getValue()); + Assert.assertEquals(Double.valueOf(10.0), pv.getPointValue().getRawValue()); + } + + @Test + public void test_deserialize_full() { + final PointPresentValue pv = JsonData.from("{\"priority\":1,\"value\":\"3\",\"rawValue\":3}", + PointPresentValue.class); + Assert.assertEquals(1, pv.getPriority()); + Assert.assertEquals("3", pv.getPointValue().getValue()); + Assert.assertEquals(Double.valueOf(3.0), pv.getPointValue().getRawValue()); + } + + @Test + public void test_deserialize_only_value_is_string() { + final PointPresentValue pv = JsonData.from("{\"priority\":1,\"value\":\"active\"}", PointPresentValue.class); + Assert.assertEquals(1, pv.getPriority()); + Assert.assertEquals("active", pv.getPointValue().getValue()); + Assert.assertNull(pv.getPointValue().getRawValue()); + } + + @Test + public void test_deserialize_value_is_string_and_raw_has_value() { + final PointPresentValue pv = JsonData.from("{\"priority\":1,\"value\":\"on\",\"rawValue\":4}", + PointPresentValue.class); + Assert.assertEquals(1, pv.getPriority()); + Assert.assertEquals("on", pv.getPointValue().getValue()); + Assert.assertEquals(Double.valueOf(4), pv.getPointValue().getRawValue()); + } + + @Test + public void test_deserialize_from_invalid_json() { + JsonObject json = new JsonObject().put("a", 1).put("b", 2); + Assert.assertNull(PointPresentValue.from(json)); + } + + @Test + public void test_deserialize_from_json() { + JsonObject json = new JsonObject().put("priority", 10).put("value", 2); + PointPresentValue pv = PointPresentValue.from(json); + Assert.assertNotNull(pv); + Assert.assertEquals(10, pv.getPriority()); + Assert.assertEquals("2", pv.getPointValue().getValue()); + Assert.assertEquals(Double.valueOf(2), pv.getPointValue().getRawValue()); + } + + @Test + public void test_create_def() { + PointPresentValue pv = PointPresentValue.def(); + Assert.assertNotNull(pv); + Assert.assertEquals(16, pv.getPriority()); + Assert.assertNull(pv.getPointValue().getValue()); + Assert.assertNull(pv.getPointValue().getRawValue()); + } + +} diff --git a/data/src/test/java/io/github/zero88/qwe/iot/data/property/PointPriorityArrayTest.java b/data/src/test/java/io/github/zero88/qwe/iot/data/property/PointPriorityArrayTest.java new file mode 100644 index 0000000..1fc5a32 --- /dev/null +++ b/data/src/test/java/io/github/zero88/qwe/iot/data/property/PointPriorityArrayTest.java @@ -0,0 +1,104 @@ +package io.github.zero88.qwe.iot.data.property; + +import org.json.JSONException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import io.github.zero88.qwe.JsonHelper; +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.exceptions.CarlException; +import io.vertx.core.json.JsonObject; + +public class PointPriorityArrayTest { + + private PointPriorityArray ppa; + + @Before + public void setup() { + ppa = new PointPriorityArray().add(8).add(9, 9.0).add(2, 3.5).add(10, 10.5); + } + + @Test + public void test_serialize() throws JSONException { + JsonHelper.assertJson(new JsonObject("{\"1\":null,\"2\":3.5,\"3\":null,\"4\":null,\"5\":null,\"6\":null," + + "\"7\":null,\"8\":null,\"9\":9.0,\"10\":10.5,\"11\":null,\"12\":null," + + "\"13\":null,\"14\":null,\"15\":null,\"16\":8.0,\"17\":null}"), + ppa.toJson()); + } + + @Test(expected = IllegalArgumentException.class) + public void test_add_invalid() { + ppa.add(18, 4); + } + + @Test + public void test_deserialize() throws JSONException { + PointPriorityArray from = JsonData.from("{\"2\":3.5,\"16\":8.0,\"9\":9.0,\"10\":10.5}", + PointPriorityArray.class); + JsonHelper.assertJson(new JsonObject("{\"1\":null,\"2\":3.5,\"3\":null,\"4\":null,\"5\":null,\"6\":null," + + "\"7\":null,\"8\":null,\"9\":9.0,\"10\":10.5,\"11\":null,\"12\":null," + + "\"13\":null,\"14\":null,\"15\":null,\"16\":8.0,\"17\":null}"), + from.toJson()); + Assert.assertEquals(ppa, from); + Double aDouble = from.get(9); + Double expected = 9.0d; + Assert.assertEquals(aDouble, expected); + } + + @Test + public void test_get_highest_value() { + final PointPresentValue highestValue = ppa.findHighestValue(); + Assert.assertEquals(2, highestValue.getPriority()); + Assert.assertEquals(3.5d, highestValue.getPointValue().getRawValue(), 0.0); + } + + @Test + public void test_get_do_not_add_value_when_data_is_null() throws JSONException { + final PointPriorityArray pointPriorityArray = ppa.add(1, null); + JsonHelper.assertJson(new JsonObject("{\"1\":null,\"2\":3.5,\"3\":null,\"4\":null,\"5\":null,\"6\":null," + + "\"7\":null,\"8\":null,\"9\":9.0,\"10\":10.5,\"11\":null,\"12\":null," + + "\"13\":null,\"14\":null,\"15\":null,\"16\":8.0,\"17\":null}"), + pointPriorityArray.toJson()); + } + + @Test + public void test_get_next_highest_value_with_highest_is_null() { + final PointPresentValue highestValue = ppa.add(1, null).findHighestValue(); + Assert.assertEquals(2, highestValue.getPriority()); + Assert.assertEquals(3.5d, highestValue.getPointValue().getRawValue(), 0.0); + } + + @Test(expected = IllegalArgumentException.class) + public void test_deserialize_invalid_priority() throws Throwable { + try { + JsonData.from(new JsonObject("{\"90\":3.5}"), PointPriorityArray.class); + } catch (CarlException e) { + final Throwable rootCause = e.getCause().getCause().getCause().getCause(); + Assert.assertEquals("Priority is only in range [1, 17]", rootCause.getMessage()); + throw rootCause; + } + } + + @Test(expected = IllegalArgumentException.class) + public void test_deserialize_invalid_value() throws Throwable { + try { + JsonData.from(new JsonObject("{\"12\":\"444.5s\"}"), PointPriorityArray.class); + } catch (CarlException e) { + final Throwable rootCause = e.getCause().getCause().getCause().getCause(); + Assert.assertEquals("Value must be number", rootCause.getMessage()); + throw rootCause; + } + } + + @Test + public void test_merge() throws Throwable { + final PointPriorityArray merge = JsonData.merge(ppa.toJson(), new JsonObject( + "{\"9\":55,\"10\":14, \"11\":null,\"12\":\"444.5\"}"), PointPriorityArray.class); + JsonHelper.assertJson(new JsonObject("{\"1\":null,\"2\":3.5,\"3\":null,\"4\":null,\"5\":null,\"6\":null," + + "\"7\":null,\"8\":null,\"9\":55.0,\"10\":14.0,\"11\":null,\"12\":444.5," + + "\"13\":null,\"14\":null,\"15\":null,\"16\":8.0,\"17\":null}"), + merge.toJson()); + } + +} diff --git a/data/src/test/java/io/github/zero88/qwe/iot/data/unit/DataTypeTest.java b/data/src/test/java/io/github/zero88/qwe/iot/data/unit/DataTypeTest.java new file mode 100644 index 0000000..c398c9e --- /dev/null +++ b/data/src/test/java/io/github/zero88/qwe/iot/data/unit/DataTypeTest.java @@ -0,0 +1,95 @@ +package io.github.zero88.qwe.iot.data.unit; + +import org.json.JSONException; +import org.junit.Assert; +import org.junit.Test; + +import io.github.zero88.qwe.JsonHelper; +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.AngularVelocity; +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.Base; +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.ElectricPotential; +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.Illumination; +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.Power; +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.Pressure; +import io.github.zero88.qwe.iot.data.unit.DataTypeCategory.Temperature; +import io.vertx.core.json.JsonObject; + +public class DataTypeTest { + + @Test + public void test_serialize_dataType() throws JSONException { + JsonHelper.assertJson(new JsonObject("{\"type\":\"number\", \"category\":\"ALL\"}"), Base.NUMBER.toJson()); + JsonHelper.assertJson(new JsonObject("{\"type\":\"percentage\", \"symbol\": \"%\", \"category\":\"ALL\"}"), + Base.PERCENTAGE.toJson()); + JsonHelper.assertJson( + new JsonObject("{\"type\":\"celsius\", \"symbol\": \"°C\", \"category\":\"TEMPERATURE\"}"), + Temperature.CELSIUS.toJson()); + JsonHelper.assertJson( + new JsonObject("{\"type\":\"volt\", \"symbol\": \"V\", \"category\":\"ELECTRIC_POTENTIAL\"}"), + ElectricPotential.VOLTAGE.toJson()); + JsonHelper.assertJson(new JsonObject("{\"type\":\"dBm\", \"symbol\": \"dBm\", \"category\":\"POWER\"}"), + Power.DBM.toJson()); + JsonHelper.assertJson( + new JsonObject("{\"type\":\"hectopascal\", \"symbol\": \"hPa\", \"category\":\"PRESSURE\"}"), + Pressure.HPA.toJson()); + JsonHelper.assertJson(new JsonObject("{\"type\":\"lux\", \"symbol\": \"lx\", \"category\":\"ILLUMINATION\"}"), + Illumination.LUX.toJson()); + JsonHelper.assertJson( + new JsonObject("{\"type\":\"kilowatt_hour\", \"symbol\": \"kWh\", \"category\":\"POWER\"}"), + Power.KWH.toJson()); + JsonHelper.assertJson(new JsonObject( + "{\"type\":\"revolutions_per_minute\", \"symbol\": \"rpm\", " + "\"category" + + "\":\"ANGULAR_VELOCITY\"}"), + AngularVelocity.RPM.toJson()); + JsonHelper.assertJson(new JsonObject("{\"type\":\"bool\",\"category\":\"ALL\"}"), Base.BOOLEAN.toJson()); + } + + @Test + public void test_deserialize_numberType() { + assertNumberDataType("{\"type\":\"number\"}", "number", null); + assertNumberDataType("{\"type\":\"percentage\"}", "percentage", "%"); + assertNumberDataType("{\"type\":\"celsius\"}", "celsius", "°C"); + assertNumberDataType("{\"type\":\"volt\"}", "volt", "V"); + assertNumberDataType("{\"type\":\"dBm\"}", "dBm", "dBm"); + assertNumberDataType("{\"type\":\"hectopascal\"}", "hectopascal", "hPa"); + assertNumberDataType("{\"type\":\"lux\"}", "lux", "lx"); + assertNumberDataType("{\"type\":\"kilowatt_hour\"}", "kilowatt_hour", "kWh"); + assertNumberDataType("{\"type\":\"unknown\", \"symbol\": \"xxx\"}", "unknown", "xxx"); + } + + private void assertNumberDataType(String from, String type, String unit) { + final DataType dt = JsonData.from(from, DataType.class); + Assert.assertTrue(dt instanceof NumberDataType); + Assert.assertEquals(type, dt.type()); + Assert.assertEquals(unit, dt.unit()); + Assert.assertNull(dt.alias()); + } + + @Test + public void test_deserialize_booleanType() { + final DataType dt = JsonData.from("{\"type\":\"bool\"}", DataType.class); + Assert.assertTrue(dt instanceof BooleanDataType); + Assert.assertEquals("bool", dt.type()); + Assert.assertNull(dt.unit()); + Assert.assertEquals(1d, dt.parse("true"), 0.0); + Assert.assertEquals(1d, dt.parse(10), 0.0); + Assert.assertEquals(1d, dt.parse(0.5), 0.0); + Assert.assertEquals(0d, dt.parse(false), 0.0); + Assert.assertEquals("FALSE", dt.display(dt.parse(false))); + } + + @Test + public void test_deserialize_with_display() { + final UnitAlias alias = new UnitAlias().add(">0", "ON").add("<=0", "OFF"); + final DataType dt = JsonData.from("{\"type\":\"bool\", \"alias\":" + alias.toJson().encode() + "}", + DataType.class); + Assert.assertTrue(dt instanceof BooleanDataType); + Assert.assertEquals("bool", dt.type()); + Assert.assertNull(dt.unit()); + Assert.assertEquals("ON", dt.display(dt.parse(1))); + Assert.assertEquals("ON", dt.display(dt.parse(0.5))); + Assert.assertEquals("OFF", dt.display(dt.parse(0))); + } + +} diff --git a/data/src/test/java/io/github/zero88/qwe/iot/data/unit/UnitDisplayTest.java b/data/src/test/java/io/github/zero88/qwe/iot/data/unit/UnitDisplayTest.java new file mode 100644 index 0000000..9d0682b --- /dev/null +++ b/data/src/test/java/io/github/zero88/qwe/iot/data/unit/UnitDisplayTest.java @@ -0,0 +1,97 @@ +package io.github.zero88.qwe.iot.data.unit; + +import org.json.JSONException; +import org.junit.Assert; +import org.junit.Test; + +import io.github.zero88.qwe.JsonHelper; +import io.github.zero88.qwe.dto.JsonData; +import io.github.zero88.utils.Functions; +import io.vertx.core.json.JsonObject; + +public class UnitDisplayTest { + + @Test + public void test_valid() { + Assert.assertEquals("abc", new UnitAlias().add("1", "abc").get("1")); + Assert.assertEquals("abc", new UnitAlias().add("=1", "abc").get("1")); + Assert.assertEquals("abc", new UnitAlias().add(">1", "abc").get("> 1")); + Assert.assertEquals("abc", new UnitAlias().add("<1", "abc").get("< 1")); + Assert.assertEquals("abc", new UnitAlias().add(">=1", "abc").get(">= 1")); + Assert.assertEquals("abc", new UnitAlias().add("<=1", "abc").get("<= 1")); + Assert.assertEquals("abc", new UnitAlias().add("<>1.4", "abc").get("<> 1.4")); + } + + @Test + public void test_parse_invalid() { + Functions.getIfThrow(() -> new UnitAlias().add("", "123"), + t -> Assert.assertEquals("Expression cannot be blank", t.getMessage())); + Functions.getIfThrow(() -> new UnitAlias().add("vv.123", "ttt"), t -> Assert.assertEquals( + "Expression cannot parse. Only support some operators: =, >, <, >=, <=, <>", t.getMessage())); + Functions.getIfThrow(() -> new UnitAlias().add("1", ""), + t -> Assert.assertEquals("Display value cannot be null", t.getMessage())); + Functions.getIfThrow(() -> new UnitAlias().add(">=1", "null"), + t -> Assert.assertEquals("Display value cannot be null", t.getMessage())); + } + + @Test + public void test_serialize() throws JSONException { + final JsonObject object = new UnitAlias().add("1", "abc") + .add("=1", "xxx") + .add(">1", "eee") + .add("<1", "ddd") + .add("=5", "xyz") + .add("<=10", "heh") + .toJson(); + final JsonObject expected = new JsonObject( + "{\"= 1.0\":\"abc\",\"= 5.0\":\"xyz\",\"> 1.0\":\"eee\",\"< 1.0\":\"ddd\",\"<= 10.0\":\"heh\"}"); + JsonHelper.assertJson(expected, object); + } + + @Test + public void test_deserialize() { + UnitAlias expected = new UnitAlias().add("=1", "xxx").add(">1", "eee").add("<1", "ddd").add("<=10", "heh"); + UnitAlias parse = JsonData.from( + new JsonObject("{\"< 1.0\":\"ddd\",\"<= 10.0\":\"heh\",\"> 1.0\":\"eee\",\"= 1.0\":\"xxx\"}"), + UnitAlias.class); + Assert.assertEquals(expected, parse); + } + + @Test + public void test_merge() throws JSONException { + UnitAlias d1 = new UnitAlias().add("=1", "xxx").add("<=10", "ho").add("5", "xyz"); + UnitAlias d2 = new UnitAlias().add("=1", "xxx").add(">1", "eee").add("<1", "ddd").add("<=10", "heh"); + final UnitAlias merge = JsonData.merge(d2.toJson(), d1.toJson(), UnitAlias.class); + final JsonObject expected = new JsonObject( + "{\"= 1.0\":\"xxx\",\"= 5.0\":\"xyz\",\"> 1.0\":\"eee\",\"< 1.0\":\"ddd\",\"<= 10.0\":\"ho\"}"); + JsonHelper.assertJson(expected, merge.toJson()); + } + + @Test + public void test_find_label() { + final UnitAlias display = new UnitAlias().add("1", "abc") + .add("3", "xxx") + .add(">1", "eee") + .add("<1", "ddd") + .add("<=10", "heh"); + Assert.assertEquals("ddd", display.eval(0)); + Assert.assertEquals("abc", display.eval(1)); + Assert.assertEquals("xxx", display.eval(3)); + Assert.assertEquals("eee", display.eval(2)); + Assert.assertEquals("eee", display.eval(5)); + Assert.assertEquals("heh", display.eval(9)); + Assert.assertEquals("heh", display.eval(10)); + Assert.assertEquals("eee", display.eval(100)); + } + + @Test + public void test_not_found_label() { + final UnitAlias display = new UnitAlias().add("1", "abc").add("<1", "ddd").add("<=10", "heh"); + Assert.assertEquals("ddd", display.eval(-10)); + Assert.assertEquals("abc", display.eval(1)); + Assert.assertEquals("heh", display.eval(2)); + Assert.assertEquals("heh", display.eval(10)); + Assert.assertNull(display.eval(11)); + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 8b489c9..55d1ec9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,12 +8,8 @@ */ rootProject.name = "qwe-iot" + include(":data", ":connector") -include( - "connectors:bacnet:mixin", - "connectors:bacnet:base", - "connectors:bacnet:simulator", - "connectors:bacnet:service", - "connectors:bacnet:server" -) +include(":connector:bacnet", ":connector:bacnet:mixin", ":connector:bacnet:simulator") +include("service:bacnet-api", "service:bacnet-server")