Implemented MQTT events and added additional test cases

This commit is contained in:
Ziver Koc 2024-09-25 00:16:07 +02:00
parent 3ef524accd
commit a980bc4946
15 changed files with 364 additions and 26 deletions

View file

@ -0,0 +1,22 @@
package se.hal.test;
import se.hal.intf.HalDeviceData;
import static org.junit.Assert.assertEquals;
/**
* Class contains assertions that can be useful
*/
public class HalAssert {
public static void assertEqualsIgnoreTimestamp(HalDeviceData expected, HalDeviceData actual) {
if (expected != null) {
expected.setTimestamp(0);
}
if (actual != null) {
actual.setTimestamp(0);
}
assertEquals(expected, actual);
}
}

View file

@ -4,8 +4,12 @@ import se.hal.intf.HalDeviceConfig;
import se.hal.intf.HalDeviceData;
import se.hal.intf.HalDeviceReportListener;
import se.hal.plugin.mqtt.device.HalMqttDeviceConfig;
import se.hal.plugin.mqtt.device.HalMqttOnOffEventConfig;
import zutil.ObjectUtil;
import zutil.parser.DataNode;
import zutil.parser.json.JSONParser;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
@ -22,6 +26,16 @@ public class Zigbee2mqttDetector implements HalMqttDetector {
return Collections.emptyList();
List<HalMqttDeviceConfig> detectedDeviceConfigs = new ArrayList<>();
DataNode json = JSONParser.read(new String(data, StandardCharsets.UTF_8));
if (json.getString("state") != null) {
HalMqttOnOffEventConfig event = new HalMqttOnOffEventConfig(topic, "$.state");
event.setWriteTopicName(topic + "/set");
event.setValueOnString("ON");
event.setValueOffString("OFF");
detectedDeviceConfigs.add(event);
}
return detectedDeviceConfigs;
}
}

View file

@ -48,6 +48,7 @@
package se.hal.plugin.mqtt.device;
import se.hal.intf.HalDeviceConfig;
import se.hal.intf.HalDeviceData;
import se.hal.intf.HalEventController;
import se.hal.intf.HalSensorConfig;
@ -56,7 +57,7 @@ import zutil.ui.conf.Configurator;
import java.util.Objects;
public abstract class HalMqttDeviceConfig implements HalSensorConfig {
public abstract class HalMqttDeviceConfig implements HalDeviceConfig {
@Configurator.Configurable(value = "MQTT Topic Name")
private String topicName;
@ -102,7 +103,6 @@ public abstract class HalMqttDeviceConfig implements HalSensorConfig {
// Hal Methods
// --------------------------
@Override
public Class<? extends HalEventController> getDeviceControllerClass() {
return HalMqttController.class;
}

View file

@ -0,0 +1,54 @@
package se.hal.plugin.mqtt.device;
import se.hal.intf.HalEventConfig;
import se.hal.intf.HalEventController;
import se.hal.intf.HalSensorConfig;
import se.hal.plugin.mqtt.HalMqttController;
import zutil.ui.conf.Configurator;
import java.util.Objects;
public abstract class HalMqttEventConfig extends HalMqttDeviceConfig implements HalEventConfig {
@Configurator.Configurable(value = "MQTT Write Topic Name")
private String writeTopicName;
public HalMqttEventConfig() {}
public HalMqttEventConfig(String topic) {
super(topic);
}
public HalMqttEventConfig(String topic, String jsonPath) {
super(topic, jsonPath);
}
public String getWriteTopicName() {
return writeTopicName;
}
public void setWriteTopicName(String writeTopicName) {
this.writeTopicName = writeTopicName;
}
// --------------------------
// Java Methods
// --------------------------
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof HalMqttEventConfig)) return false;
if (!super.equals(o)) return false;
HalMqttEventConfig that = (HalMqttEventConfig) o;
return Objects.equals(writeTopicName, that.writeTopicName);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Objects.hashCode(writeTopicName);
return result;
}
}

View file

@ -60,7 +60,7 @@ import zutil.parser.json.JSONParser;
import java.nio.charset.StandardCharsets;
public class HalMqttHumidityDeviceConfig extends HalMqttDeviceConfig {
public class HalMqttHumidityDeviceConfig extends HalMqttSensorConfig {
public HalMqttHumidityDeviceConfig() {}
public HalMqttHumidityDeviceConfig(String topic) {
@ -74,11 +74,6 @@ public class HalMqttHumidityDeviceConfig extends HalMqttDeviceConfig {
// Hal Methods
// --------------------------
@Override
public Class<? extends HalEventController> getDeviceControllerClass() {
return HalMqttController.class;
}
@Override
public Class<? extends HalDeviceData> getDeviceDataClass() {
return HumiditySensorData.class;
@ -86,15 +81,20 @@ public class HalMqttHumidityDeviceConfig extends HalMqttDeviceConfig {
@Override
public HumiditySensorData getDeviceData(byte[] data) {
if(ObjectUtil.isEmpty(data)) {
return null;
}
if (!ObjectUtil.isEmpty(getJsonPath())) {
String dataStr = new String(data, StandardCharsets.UTF_8);
DataNode json = JSONParser.read(dataStr);
DataNode deviceDataValue = DataNodePath.search(getJsonPath(), json);
if (deviceDataValue != null)
if (deviceDataValue != null) {
return new HumiditySensorData(deviceDataValue.getDouble(), System.currentTimeMillis());
else
} else {
return null;
}
}
return new HumiditySensorData(Converter.toInt(data), System.currentTimeMillis());
}

View file

@ -0,0 +1,85 @@
package se.hal.plugin.mqtt.device;
import se.hal.intf.HalAbstractController;
import se.hal.intf.HalDeviceData;
import se.hal.struct.devicedata.HumiditySensorData;
import se.hal.struct.devicedata.OnOffEventData;
import zutil.ObjectUtil;
import zutil.converter.Converter;
import zutil.parser.DataNode;
import zutil.parser.DataNodePath;
import zutil.parser.json.JSONParser;
import zutil.ui.conf.Configurator;
import java.nio.charset.StandardCharsets;
public class HalMqttOnOffEventConfig extends HalMqttEventConfig {
@Configurator.Configurable(value = "Match value for state ON")
private String valueOnString = "ON";
@Configurator.Configurable(value = "Match value for state OFF")
private String valueOffString = "OFF";
public HalMqttOnOffEventConfig() {}
public HalMqttOnOffEventConfig(String topic) {
super(topic);
}
public HalMqttOnOffEventConfig(String topic, String jsonPath) {
super(topic, jsonPath);
}
public String getValueOnString() {
return valueOnString;
}
public void setValueOnString(String valueOnString) {
this.valueOnString = valueOnString;
}
public String getValueOffString() {
return valueOffString;
}
public void setValueOffString(String valueOffString) {
this.valueOffString = valueOffString;
}
// --------------------------
// Hal Methods
// --------------------------
@Override
public Class<? extends HalDeviceData> getDeviceDataClass() {
return OnOffEventData.class;
}
@Override
public HalDeviceData getDeviceData(byte[] data) {
if(ObjectUtil.isEmpty(data)) {
return null;
}
String dataStr = new String(data, StandardCharsets.UTF_8);
if (!ObjectUtil.isEmpty(getJsonPath())) {
DataNode json = JSONParser.read(dataStr);
DataNode deviceDataValue = DataNodePath.search(getJsonPath(), json);
if (ObjectUtil.isEmpty(deviceDataValue)) {
return null;
}
dataStr = deviceDataValue.getString().toUpperCase();
}
if (valueOnString.equalsIgnoreCase(dataStr)) {
return new OnOffEventData(true, System.currentTimeMillis());
} else if (valueOffString.equalsIgnoreCase(dataStr)) {
return new OnOffEventData(false, System.currentTimeMillis());
} else {
return null;
}
}
}

View file

@ -60,7 +60,7 @@ import zutil.parser.json.JSONParser;
import java.nio.charset.StandardCharsets;
public class HalMqttParticularMatterDeviceConfig extends HalMqttDeviceConfig {
public class HalMqttParticularMatterDeviceConfig extends HalMqttSensorConfig {
public HalMqttParticularMatterDeviceConfig() {}
public HalMqttParticularMatterDeviceConfig(String topic) {
@ -74,11 +74,6 @@ public class HalMqttParticularMatterDeviceConfig extends HalMqttDeviceConfig {
// Hal Methods
// --------------------------
@Override
public Class<? extends HalEventController> getDeviceControllerClass() {
return HalMqttController.class;
}
@Override
public Class<? extends HalDeviceData> getDeviceDataClass() {
return ParticulateMatterSensorData.class;
@ -86,15 +81,20 @@ public class HalMqttParticularMatterDeviceConfig extends HalMqttDeviceConfig {
@Override
public ParticulateMatterSensorData getDeviceData(byte[] data) {
if(ObjectUtil.isEmpty(data)) {
return null;
}
if (!ObjectUtil.isEmpty(getJsonPath())) {
String dataStr = new String(data, StandardCharsets.UTF_8);
DataNode json = JSONParser.read(dataStr);
DataNode deviceDataValue = DataNodePath.search(getJsonPath(), json);
if (deviceDataValue != null)
if (deviceDataValue != null) {
return new ParticulateMatterSensorData(deviceDataValue.getDouble(), System.currentTimeMillis());
else
} else {
return null;
}
}
return new ParticulateMatterSensorData(Converter.toInt(data), System.currentTimeMillis());
}

View file

@ -0,0 +1,25 @@
package se.hal.plugin.mqtt.device;
import se.hal.intf.HalEventController;
import se.hal.intf.HalSensorConfig;
import se.hal.plugin.mqtt.HalMqttController;
public abstract class HalMqttSensorConfig extends HalMqttDeviceConfig implements HalSensorConfig {
public HalMqttSensorConfig() {}
public HalMqttSensorConfig(String topic) {
super(topic);
}
public HalMqttSensorConfig(String topic, String jsonPath) {
super(topic, jsonPath);
}
// --------------------------
// Hal Methods
// --------------------------
@Override
public Class<? extends HalEventController> getDeviceControllerClass() {
return HalMqttController.class;
}
}

View file

@ -60,7 +60,7 @@ import zutil.parser.json.JSONParser;
import java.nio.charset.StandardCharsets;
public class HalMqttTemperatureDeviceConfig extends HalMqttDeviceConfig {
public class HalMqttTemperatureDeviceConfig extends HalMqttSensorConfig {
public HalMqttTemperatureDeviceConfig() {}
public HalMqttTemperatureDeviceConfig(String topic) {
@ -74,11 +74,6 @@ public class HalMqttTemperatureDeviceConfig extends HalMqttDeviceConfig {
// Hal Methods
// --------------------------
@Override
public Class<? extends HalEventController> getDeviceControllerClass() {
return HalMqttController.class;
}
@Override
public Class<? extends HalDeviceData> getDeviceDataClass() {
return TemperatureSensorData.class;
@ -86,15 +81,20 @@ public class HalMqttTemperatureDeviceConfig extends HalMqttDeviceConfig {
@Override
public TemperatureSensorData getDeviceData(byte[] data) {
if(ObjectUtil.isEmpty(data)) {
return null;
}
if (!ObjectUtil.isEmpty(getJsonPath())) {
String dataStr = new String(data, StandardCharsets.UTF_8);
DataNode json = JSONParser.read(dataStr);
DataNode deviceDataValue = DataNodePath.search(getJsonPath(), json);
if (deviceDataValue != null)
if (deviceDataValue != null) {
return new TemperatureSensorData(deviceDataValue.getDouble(), System.currentTimeMillis());
else
} else {
return null;
}
}
return new TemperatureSensorData(Converter.toInt(data), System.currentTimeMillis());
}

View file

@ -12,6 +12,8 @@
{"se.hal.intf.HalSensorConfig": "se.hal.plugin.mqtt.device.HalMqttParticularMatterDeviceConfig"},
{"se.hal.intf.HalSensorConfig": "se.hal.plugin.mqtt.device.HalMqttTemperatureDeviceConfig"},
{"se.hal.intf.HalEventConfig": "se.hal.plugin.mqtt.device.HalMqttOnOffEventConfig"},
{"se.hal.intf.HalWebPage": "se.hal.plugin.mqtt.page.MqttOverviewPage"}
]
}

View file

@ -2,6 +2,7 @@ package se.hal.plugin.mqtt.detector;
import org.junit.Test;
import se.hal.plugin.mqtt.device.HalMqttDeviceConfig;
import se.hal.plugin.mqtt.device.HalMqttOnOffEventConfig;
import se.hal.plugin.mqtt.device.HalMqttParticularMatterDeviceConfig;
import se.hal.test.MockHalDeviceReportListener;
@ -22,6 +23,11 @@ public class Zigbee2mqttDetectorTest {
devices = detector.parseTopic("invalid/topic", new byte[]{});
assertEquals(0, devices.size());
devices = detector.parseTopic("zigbee2mqtt/Kitchen Plant Light", "{\"power\":10.48,\"state\":\"ON\"}".getBytes(StandardCharsets.UTF_8));
assertEquals(1, devices.size());
assertEquals(new HalMqttOnOffEventConfig("zigbee2mqtt/Kitchen Plant Light", "$.state"), devices.get(0));
}
/*
@Test

View file

@ -0,0 +1,25 @@
package se.hal.plugin.mqtt.device;
import org.junit.Test;
import se.hal.struct.devicedata.HumiditySensorData;
import se.hal.struct.devicedata.TemperatureSensorData;
import zutil.converter.Converter;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.*;
import static se.hal.test.HalAssert.assertEqualsIgnoreTimestamp;
public class HalMqttHumidityDeviceConfigTest {
@Test
public void getDeviceDataRaw() {
HalMqttHumidityDeviceConfig config = new HalMqttHumidityDeviceConfig();
assertEqualsIgnoreTimestamp(null, config.getDeviceData(null));
assertEqualsIgnoreTimestamp(null, config.getDeviceData("".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(new HumiditySensorData(0, 0), config.getDeviceData(Converter.toBytes(0)));
assertEqualsIgnoreTimestamp(new HumiditySensorData(10, 0), config.getDeviceData(Converter.toBytes(10)));
}
}

View file

@ -0,0 +1,56 @@
package se.hal.plugin.mqtt.device;
import org.junit.Test;
import se.hal.struct.devicedata.OnOffEventData;
import java.nio.charset.StandardCharsets;
import static se.hal.test.HalAssert.assertEqualsIgnoreTimestamp;
public class HalMqttOnOffEventConfigTest {
private static final OnOffEventData ON_EVENT = new OnOffEventData(true, 0);
private static final OnOffEventData OFF_EVENT = new OnOffEventData(false, 0);
@Test
public void getDeviceDataRaw() {
HalMqttOnOffEventConfig config = new HalMqttOnOffEventConfig();
assertEqualsIgnoreTimestamp(null, config.getDeviceData(null));
assertEqualsIgnoreTimestamp(null, config.getDeviceData("".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(null, config.getDeviceData("unknown".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(OFF_EVENT, config.getDeviceData("OFF".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(OFF_EVENT, config.getDeviceData("off".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(ON_EVENT, config.getDeviceData("ON".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(ON_EVENT, config.getDeviceData("on".getBytes(StandardCharsets.UTF_8)));
config.setValueOnString("online");
config.setValueOffString("offline");
assertEqualsIgnoreTimestamp(OFF_EVENT, config.getDeviceData("OFFLINE".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(OFF_EVENT, config.getDeviceData("offline".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(ON_EVENT, config.getDeviceData("ONLINE".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(ON_EVENT, config.getDeviceData("online".getBytes(StandardCharsets.UTF_8)));
}
@Test
public void getDeviceDataJson() {
HalMqttOnOffEventConfig config = new HalMqttOnOffEventConfig();
config.setJsonPath("$.state");
assertEqualsIgnoreTimestamp(null, config.getDeviceData("{\"power\":10.48,\"state\":\"\"}".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(null, config.getDeviceData("{\"power\":10.48,\"state\":\"unknown\"}".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(OFF_EVENT, config.getDeviceData("{\"power\":10.48,\"state\":\"OFF\"}".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(OFF_EVENT, config.getDeviceData("{\"power\":10.48,\"state\":\"off\"}".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(ON_EVENT, config.getDeviceData("{\"power\":10.48,\"state\":\"ON\"}".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(ON_EVENT, config.getDeviceData("{\"power\":10.48,\"state\":\"on\"}".getBytes(StandardCharsets.UTF_8)));
config.setValueOnString("online");
config.setValueOffString("offline");
assertEqualsIgnoreTimestamp(OFF_EVENT, config.getDeviceData("{\"power\":10.48,\"state\":\"OFFLINE\"}".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(OFF_EVENT, config.getDeviceData("{\"power\":10.48,\"state\":\"offline\"}".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(ON_EVENT, config.getDeviceData("{\"power\":10.48,\"state\":\"ONLINE\"}".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(ON_EVENT, config.getDeviceData("{\"power\":10.48,\"state\":\"online\"}".getBytes(StandardCharsets.UTF_8)));
}
}

View file

@ -0,0 +1,25 @@
package se.hal.plugin.mqtt.device;
import org.junit.Test;
import se.hal.struct.devicedata.HumiditySensorData;
import se.hal.struct.devicedata.ParticulateMatterSensorData;
import zutil.converter.Converter;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.*;
import static se.hal.test.HalAssert.assertEqualsIgnoreTimestamp;
public class HalMqttParticularMatterDeviceConfigTest {
@Test
public void getDeviceDataRaw() {
HalMqttParticularMatterDeviceConfig config = new HalMqttParticularMatterDeviceConfig();
assertEqualsIgnoreTimestamp(null, config.getDeviceData(null));
assertEqualsIgnoreTimestamp(null, config.getDeviceData("".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(new ParticulateMatterSensorData(0, 0), config.getDeviceData(Converter.toBytes(0)));
assertEqualsIgnoreTimestamp(new ParticulateMatterSensorData(10, 0), config.getDeviceData(Converter.toBytes(10)));
}
}

View file

@ -0,0 +1,24 @@
package se.hal.plugin.mqtt.device;
import org.junit.Test;
import se.hal.struct.devicedata.TemperatureSensorData;
import zutil.converter.Converter;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.*;
import static se.hal.test.HalAssert.assertEqualsIgnoreTimestamp;
public class HalMqttTemperatureDeviceConfigTest {
@Test
public void getDeviceDataRaw() {
HalMqttTemperatureDeviceConfig config = new HalMqttTemperatureDeviceConfig();
assertEqualsIgnoreTimestamp(null, config.getDeviceData(null));
assertEqualsIgnoreTimestamp(null, config.getDeviceData("".getBytes(StandardCharsets.UTF_8)));
assertEqualsIgnoreTimestamp(new TemperatureSensorData(0, 0), config.getDeviceData(Converter.toBytes(0)));
assertEqualsIgnoreTimestamp(new TemperatureSensorData(10, 0), config.getDeviceData(Converter.toBytes(10)));
}
}